유니티 빌드 파이프라인 3 - 배포 Job, 유저 배포 Git과 디버그 백업

이번 글에서는 아래 내용을 정리한다.

  • 예전 회사의 패치 폴더 구조(하위 버전부터 덮어쓰기)와 Git 스냅샷 구조의 차이
  • 배포 Job이 하는 일: 스냅샷, 유저용 파일 선별, 암호화, 커밋과 태그와 push, 디버그 백업
  • 커밋 메시지에 무엇을 남기나
  • _exeOut은 읽기만 하고, dev는 배포하지 않는다는 규칙
  • 빌드가 _exeOut을 바꾸는 중에 배포가 읽는 경합을 막은 방법
  • 배포 에이전트를 외부에 열린 2019 서버에 두면서 조심한 것

예전 구조와 비교

예전 회사의 패치는 버전 폴더에 바뀐 파일만 넣고, 최종 파일은 하위 버전부터 상위 버전까지 순서대로 복사해 덮어써서 만들었다. 누적 차분 구조다. 이번에 만든 것은 배포 Git의 태그 하나가 그 버전의 완성 상태를 담는 스냅샷 구조다. 서버 개발로 비유하면 전자는 바이너리 로그를 순서대로 재생하는 것, 후자는 시점별 스냅샷을 꺼내는 것이다.

패치 폴더 겹치기 배포 Git과 태그
한 버전이 담는 것 바뀐 파일만 그 버전의 파일 전체
최종 파일 만들기 순서대로 덮어써야 함 git checkout v200 한 번
버전 간 차이 폴더를 열면 보임 git diff v199 v200 –stat
파일 삭제 삭제 목록이 없으면 표현 불가 자연스럽게 기록
순서와 누락 오류 폴더 하나 빠지면 잘못된 최종본 불가능. 태그가 해시로 완결
받는 쪽 갱신 새 폴더만 다운로드 git pull이 바뀐 파일만

스냅샷이 차분의 상위 개념이다. 스냅샷 둘이 있으면 차분은 계산해 낼 수 있지만, 차분들에서 완성본을 얻으려면 매번 겹쳐야 하고 그 과정이 틀릴 수 있다. 예전 구조가 차분이었던 진짜 이유는 유저 패치 용량이었는데, 유니티는 Data 폴더의 에셋 파일이 빌드마다 통째로 바뀌어 차분 폴더를 만들어도 거의 전체 복사가 되고, 스토어(Steam)가 바이트 단위 차분 패치를 알아서 한다. 우리 배포 Git은 팀과 테스터용 전달 경로다.

배포 Job이 하는 일

인자는 PROJECT(목록에서 선택), VERSION(예: 200, 선택), MESSAGE(한 줄, 선택)다. 배포 에이전트는 Server 2019에 붙였다. 그 서버에 유저용 폴더를 이미 만들어 두었기 때문이다.

1. \\서버\exeOut\<PROJECT>\_exeOut 을 배포 서버 로컬(D:\DeployOut\_staging\<PROJECT>)에 한 번만 복사해 스냅샷
   복사 전후로 _ci\complete.txt가 같아야 하고, build-info의 result=SUCCESS, build_type=release, exe가 있어야 한다
2. D:\DeployOut\<PROJECT> 를 배포 저장소 <PROJECT>-deploy 의 작업 복사본으로 맞춤 (원격 기준 reset)
3. 스냅샷에서 유저용 파일만 미러 복사
   제외: BackUpThisFolder 폴더, BurstDebugInformation 폴더, _ci, *.pdb
4. 기획 데이터 암호화 (빌드에서 이미 됐으면 건너뜀. 4편)
5. deploy-info.txt 작성, 커밋, VERSION이 있으면 태그 vN, push
6. 마지막으로 \\서버\exeOut\<PROJECT>\<yyyyMMdd-HHhmmmsss>\ 에 디버그 파일만 백업
   *.exe, *.pdb, *.map, 루트 *.dll, Data\Managed\*.dll, 디버그 폴더 두 개, _ci, deploy-info.txt
   5번까지 실패하면 백업을 만들지 않는다

유저에게 나간 파일은 배포 Git에, 그 빌드의 심볼은 exeOut 백업에 있다. 둘을 합치면 그 배포판을 완전히 재구성할 수 있고, 크래시 덤프를 심볼과 맞출 수도 있다.

배포 저장소는 .gitattributes로 모든 파일을 LFS에 두고, 루트의 deploy-info.txt, README.md, .gitattributes만 텍스트로 예외를 준다. UnityPlayer.dll처럼 매번 같은 파일은 LFS가 중복 저장하지 않지만, Data 폴더 안의 에셋은 빌드마다 바뀌어 배포당 수십에서 수백 MB가 쌓인다. Forgejo 데이터 디스크가 100GB이니 수백 회는 문제없고, 그 뒤엔 저장소를 새로 만드는 정리가 필요하다.

커밋 메시지

첫 줄은 사람이 적은 MESSAGE, 그 아래에 어느 빌드에서 왔는지가 자동으로 붙는다. 개발 저장소의 커밋 메시지도 그대로 들어간다. 빌드 Job이 git log -1의 본문을 _ci 폴더에 commit-message.txt로 남겨 두기 때문이다.

첫 배포 테스트 (v1)

deploy: PROJECT=MyGame VERSION=1  (Jenkins deploy #1, 20260928-15h49m21s)
source: _exeOut = unity-build #3 dev 20260928-15h48m43s
dev commit: 40cf9d09 (main)

    작업 커밋

encryption: encrypted 0 files (no StreamingAssets\Data folder in this build)
changes: 220 added, 0 modified, 0 deleted

한글이 깨진 적이 있다. PowerShell 5.1의 Get-Content는 기본 인코딩이 ANSI라 UTF-8로 쓴 build-info를 읽으면 한글이 망가진다. -Encoding UTF8을 붙였다.

두 가지 규칙

_exeOut은 읽기만 한다. 배포 Job은 _exeOut을 복사만 하고 절대 바꾸지 않는다. 암호화도 커밋도 배포 서버의 작업 복사본에서 한다. 백업 폴더만 새로 생긴다.

dev는 배포하지 않는다. 처음에 배포 Job에 BUILD_TYPE 선택지를 넣어 dev도 배포할 수 있게 만들었다가 바로 걷어냈다. 배포는 _exeOut(release)만 읽고 선택지가 없다. _exeOut에 dev 빌드가 들어 있으면 시작 단계에서 실패한다. 테스트로 dev 빌드를 _exeOut에 둔 채 배포를 눌러 보니 only release builds are deployed로 멈추고 백업도 만들지 않았다. dev 빌드가 _exeOutDEV로 따로 나가는 이유가 이것이다.

경합을 막는 법

빌드 Job은 _exeOut을 지우고 다시 채운다. 그 사이에 배포 Job이 읽으면 반쯤 복사된 폴더를 가져갈 수 있다. 두 Job 사이에 잠금은 없다. 코드를 읽다가 이 문제를 발견했고, 두 쪽을 함께 고쳤다.

빌드 쪽은 임시 폴더 _exeOut.tmp에 전부 복사하고, _ci\complete.txt를 맨 마지막에 쓴 뒤, 옛 _exeOut을 지우고 이름을 바꾼다. 복사 도중 죽으면 .tmp만 남고 옛 _exeOut은 멀쩡하다.

배포 쪽은 _exeOut을 로컬 스테이징 폴더에 한 번만 복사하고, 복사 전과 후의 complete.txt가 같은지, 그 안의 빌드 번호가 build-info와 같은지 확인한다. 이후 모든 단계는 스테이징만 읽는다. _exeOut이 교체되는 순간에 걸리면 source missing이나 changed while copying으로 깨끗하게 실패하고 다시 누르면 된다.

폴더를 지우고 채우는 방식은 그 사이에 읽는 쪽이 있으면 반드시 문제가 된다. 임시 폴더에 채운 뒤 이름 바꾸기로 교체하고 완료 표식 파일을 마지막에 쓰면, 잠금 서버 없이도 충분하다.

외부에 열린 서버에 에이전트를 두면서

Server 2019는 블로그와 FTPS로 인터넷에 열린 서버다. 여기에 배포 에이전트를 두면 배포 때마다 Forgejo 토큰이 이 서버에 내려온다. 그래서 세 가지를 지켰다.

  • 에이전트 서비스는 관리자가 아닌 별도 계정으로 돈다.
  • 배포용 토큰은 배포 저장소에만 쓰기 권한이 있다. 소스 읽기 토큰은 읽기 전용이다.
  • 토큰은 명령줄에 나오지 않는다. git push 주소에 토큰을 박는 방식은 편하지만 프로세스 목록과 셸 이력에 남는다. 대신 GIT_ASKPASS에 환경 변수 값을 echo하는 작은 스크립트를 넣고 값은 환경 변수로만 준다. LFS도 같은 경로로 인증한다.

그런데 이 서버에서 딱 한 번 실수를 했다. 처음 설계 때 만든 SMB 공유가 파일 서버 역할을 설치하면서 445 포트를 내부망 전체에 열었다. 그 이야기는 5편의 점검에서 다룬다.

마치며

배포는 결국 필요한 파일만 골라 스냅샷을 남기고, 그 스냅샷이 어디서 왔는지 적어 두는 일이다. Git이 태그와 이력을 공짜로 주니 버전 관리는 저절로 따라왔다. 다음 글은 그 파일들 중 기획 데이터를 어떻게 암호화하고 키를 어디에 두는지다.