Avoon 운영 트리는 git 저장소가 아니다. 저장소에는 없고 서버에만 있는 파일이 서비스를 받치고 있다는 뜻이다. 소스를 rsync -a --delete로 통째로 밀어 넣는 방식은 간단해 보였는데, 간단해 보이는 쪽이 대개 더 무섭다.
그래서 -n을 붙여 먼저 돌렸다. node_modules와.git만 제외하고 나머지는 손대지 않았다. deleting으로 시작하는 줄이 1,645개 나왔다. 실제로 실행했다면 그만큼이 사라졌을 것이다.
지워질 1,645개가 무엇이었나
가장 큰 덩어리는 릴리스 도구였다..release-tools 아래 디렉터리 18개에 파일 1,081개가 쌓여 있다. 릴리스마다 검증된 스크립트 한 벌을 새 디렉터리에 풀어 두는 구조라 회차가 쌓이면 개수도 쌓인다. 복구 스크립트와 systemd 유닛이 전부 여기 있다.
나머지 중에 눈에 걸린 것들이 있다. 푸시 발송에 쓰는 fcm-service-account.json과 그 백업,.env.prod 백업 네 개,.release-recovery.env, 그리고.last-successful-image-tag였다. 마지막 파일에는 롤백 대상 이미지 태그가 적힌다. 지우면 되돌아갈 지점을 잃는다.
저장소를 기준으로 보면 이 파일들은 전부 "없는 파일"이다. --delete는 원본에 없으면 지우는 게 일이니 잘못 동작한 것도 아니다. 잘못은 원본과 대상이 같은 목록을 담고 있다고 가정한 쪽에 있었다.
- .release-tools 아래 18개 디렉터리, 파일 1,081개
- fcm-service-account.json과 백업 한 벌
- .env.prod 백업 네 개
- .release-recovery.env와.last-successful-image-tag
앵커 없는 assets/ 는 하위 assets까지 데려간다
삭제를 막으려면 제외 목록을 쓰게 되는데, 여기에 또 함정이 있다. 최상위 assets 디렉터리를 빼려고 제외 목록에 assets/ 라고만 적으면 rsync는 경로 어디서든 이름이 맞는 디렉터리를 전부 제외한다. apps/api/assets/ 도 같이 빠진다는 뜻이다.
확인해 봤다. 최상위 assets와 apps/api/assets/fonts를 만들어 두고 제외 패턴만 바꿔 가며 전송 목록을 찍었다. assets/ 로 적으면 폰트 파일이 목록에서 사라지고, /assets/ 로 앞에 슬래시를 붙이면 최상위만 빠지고 폰트는 남는다. 슬래시 하나 차이다.
API 이미지 Dockerfile은 apps/api/assets/fonts를 그대로 COPY한다. 건강 요약 PDF에 들어가는 이모지 폰트가 거기 있다. 소스에서 그 디렉터리가 빠지면 빌드는 성공하고 PDF 생성 시점에 글자가 비어서 나온다. 빌드가 빨간불로 알려 주는 실패가 아니라서 더 나쁘다.
COPY --from=build --chown=avoon:avoon /app/apps/api/dist ./apps/api/dist
COPY --from=build --chown=avoon:avoon /app/apps/api/assets/fonts ./apps/api/assets/fonts
COPY --from=build --chown=avoon:avoon /app/apps/api/package.json ./apps/api/package.json 코드 발췌 · 제외 패턴 하나가 가운데 줄의 원본을 통째로 없앤다.
지울 수 있는 구조를 아예 쓰지 않기로 했다
결론은 rsync를 쓰지 않는 것이었다. Avoon은 git ls-files로 고른 파일만 tar로 묶어 릴리스마다 새 디렉터리에 푼다. 기존 디렉터리를 건드리지 않으니 구조적으로 삭제가 일어날 수 없다. 전송 뒤에는 sha256으로 대조한다.
--delete가 필요한 상황이 없다는 말은 아니다. 캐시와 빌드 산출물이 서버에 쌓이는 트리라면 정리는 해야 한다. 다만 그때도 순서는 같다. -n으로 먼저 돌리고, deleting 줄을 눈으로 읽고, 남겨야 할 것에는 앞에 슬래시를 붙인 제외 패턴을 준다.
이 세 단계가 1분이 안 걸린다. 서버에만 있는 파일을 복구하는 데 드는 시간과 비교하면 계산이 안 되는 수준이다. 운영 트리를 처음 만지는 자리에서는 이 1분을 건너뛰지 않는 게 좋다.
삭제 목록을 읽는 데는 1분이면 충분했다
- 운영 트리에 --delete를 쓰기 전에 -n으로 삭제 목록을 뽑아 눈으로 읽는다
- 제외 패턴은 앞에 슬래시를 붙여 최상위로 고정한다
- 서버에만 있는 자격 증명과 롤백 표식은 저장소 기준으로 보면 전부 없는 파일이다
- 삭제가 일어날 수 없는 전송 구조를 고를 수 있으면 그쪽이 낫다