website: 배포 이력 기록 — 어드민 인프라 대시보드(#451) 가시성 재료
CD 파이프라인이 배포를 성공/롤백/수동개입 중 어느 쪽으로 끝내든, 그 순간 앱과 DB가 서로 어긋나지 않았는지 기록으로 남기는 job을 추가한 PR이다.
#451에서 어드민 인프라 대시보드를 논의하다가 나온 요구사항이었다. 기존에도 배포 상태를 볼 방법은 있었지만 5분 간격 폴링이라, 예를 들어 롤백으로 앱은 이전 버전으로 되돌아갔는데 마이그레이션은 이미 적용된 채로 남아있는 상황 같은 걸 잡아내기엔 늦었다. 필요한 건 배포가 끝나는 그 순간의 스냅샷이었다.
기존 migration-check job은 "이번 배포가 위험한가"를 true/false로만 판정하고 있었다. 이 값 자체는 CD의 안전장치로 이미 검증돼 있었기 때문에 판정 로직은 건드리지 않기로 했다. 대신 이미 하고 있던 grep 검사를 파일 단위로도 기록하도록 확장했다. ADDED_JSON에 각 마이그레이션 파일명과 additive/destructive 여부를 담고, TOTAL_MIGRATION_COUNT로 해당 SHA 시점의 전체 마이그레이션 파일 개수도 같이 출력하게 했다. 이 총 개수가 나중에 "앱이 기대하는 마이그레이션 버전"의 기준값이 된다.
그 다음이 이 PR의 핵심인 record-deploy-status job이다. needs에 config, migration-check, build, deploy, smoke-test, confirm, rollback, manual-intervention을 전부 걸고 if: always()로 실행되게 했다. 배포가 confirm으로 끝나든, rollback으로 끝나든, 그마저 실패해서 수동개입이 필요하든, 빌드 단계에서 막히든 상관없이 무조건 돌아야 기록이 빠지지 않기 때문이다. 판정 로직은 새로운 상태를 만드는 대신 이미 존재하는 job들의 result(success/failure/skipped)를 조합해서 confirmed / rolled_back / rollback_failed / manual_intervention_needed / migration_check_blocked / build_failed 중 하나로 분류하는 방식을 택했다.
기록의 핵심은 두 숫자를 나란히 남기는 것이었다. migration-check가 계산한 "앱이 기대하는 마이그레이션 개수"와, 배포 직후 서버에서 flyway_schema_history 테이블을 직접 SELECT해서 얻은 "DB에 실제 적용된 개수"다. 이 둘이 다르면 그 자체가 앱-DB 불일치 신호가 된다. DB 파일이 없거나 조회가 실패하는 경우엔 0으로 두기로 했는데, 없는 걸 있는 척 감추기보다는 화면에서 "어긋남"으로 명확히 드러나는 쪽이 맞다고 판단했다.
라이브 검증은 workflow_dispatch로 이 브랜치를 직접 stage에 배포해보는 방식으로 했다. 정상 플로우는 그대로 통과했는데, 1차 시도에서 Permission denied가 났다. 원인은 infra/data/에서 이미 겪어본 패턴이었다 — docker-compose의 볼륨 마운트가 먼저 실행되면서, 아직 존재하지 않는 호스트 디렉터리(logs/deploy-history)를 root 소유로 자동 생성해버린 것. 그 뒤에 스크립트에서 mkdir -p를 해봐야 이미 root 소유라 ubuntu 권한으로는 못 쓴다. 서버에서 chown ubuntu:ubuntu로 한 번 해결하고, 재발을 막기 위해 코드에 이 함정을 그대로 주석으로 남겼다. mkdir -p는 앞으로 디렉터리가 정상적으로 ubuntu 소유일 때를 위한 대비일 뿐, 최초 1회 root 소유 생성 문제 자체를 막지는 못한다는 점도 같이 적어뒀다. 재실행 후에는 expectedMigrationCount: 29, actualMigrationCount: 29로 앱-DB 일치가 정확히 기록되는 걸 확인했다.
삭제형 마이그레이션이나 실제 롤백, 수동개입 경로는 이번엔 라이브로 재현하지 않았다. 위험한 마이그레이션을 실제로 만들어야 검증되는 시나리오라 범위 밖으로 뒀고, 그 판정 로직 자체는 기존 CD에서 이미 검증된 걸 그대로 재사용했으니 우선순위를 낮췄다.
docker-compose.yml에는 logs/deploy-history 디렉터리를 백엔드 컨테이너에 읽기 전용으로 마운트하는 볼륨을 추가했다. stage/prod 두 컨테이너가 같은 디렉터리를 보게 해서, 나중에 백엔드가 이 파일을 읽어 API로 내려줄 때 한 화면에서 두 환경의 배포 이력을 같이 보여줄 수 있게 해뒀다. 백엔드가 이 로그 파일을 실제로 읽어 API로 서빙하는 것과, 어드민 화면에서 그래프로 보여주는 부분은 이번 PR 범위 밖이고 후속 작업으로 남겨뒀다.