슬롯 두 개와 스위치 하나

같은 API 컨테이너를 api-blue와 api-green 두 슬롯으로 정의하고, Nginx가 그중 하나만 upstream으로 바라봅니다. 이렇게 하면 배포가 서비스 교체가 아니라 스위치 전환이 됩니다.

# deploy-bluegreen.sh의 골격
1. 현재 활성 슬롯 확인 (.deploy-slot 파일)
2. 비활성 슬롯에 새 이미지 pull, 컨테이너 기동
3. 헬스체크 통과까지 대기 (타임아웃 300초)
   실패하면 여기서 중단. 트래픽은 여전히 이전 슬롯에 있다
4. Nginx upstream 설정 교체, nginx -t && nginx -s reload
5. 이전 슬롯 정지, .deploy-slot 갱신

이 흐름에서 제일 중요한 건 3번이 게이트라는 점입니다. 새 버전이 뜨지 못하면 전환 자체가 일어나지 않으니, 실패한 배포의 기본값이 장애가 아니라 아무 일도 없음이 됩니다. 4번의 nginx -t도 같은 맥락입니다. 설정을 리로드하기 전에 반드시 검증부터 합니다. 전환 스위치가 고장 나면 슬롯이 둘이어도 소용없기 때문입니다.

롤백은 기능이 아니라 성질이다

배포 단위를 git 브랜치의 최신이 아니라 이미지 태그로 잡았습니다. CI를 통과한 커밋만 GHCR에 태그된 이미지로 올라가고, 배포 스크립트는 태그를 인자로 받습니다. 이렇게 하면 롤백이라는 별도 기능이 필요 없어집니다. 롤백은 그냥 이전 태그로 하는 평범한 배포이기 때문입니다. 같은 스크립트, 같은 헬스체크 게이트, 같은 전환을 탑니다.

# 롤백 = 이전 태그를 지정한 평범한 배포
./scripts/deploy-from-tag.sh --api-tag <어제의-태그>

되돌리는 경로가 나아가는 경로와 같아지고 나서야 새벽의 롤백이 무섭지 않게 됐습니다.

사람은 어디에 남는가

스테이징은 dev 브랜치 푸시로 자동 배포되지만 프로덕션은 GitHub environment의 승인 게이트를 거칩니다. 자동화의 목적이 사람을 없애는 건 아니라고 생각합니다. 사람의 개입 지점을 제일 값싼 순간, 그러니까 실행 전 승인 버튼으로 옮기는 것에 가깝습니다.

배포가 나간 뒤에는 봇이 실제 프로덕션 URL을 스모크 체크해서 Discord로 결과를 알립니다. 배포 성공은 CI의 말이 아니라 프로덕션의 응답으로 확인해야 한다고 배웠기 때문입니다.

한 대짜리 인프라를 변호하며

그냥 PaaS 쓰지 그랬냐는 질문은 정당합니다. 다만 이 규모, 그러니까 단일 서비스에 예측 가능한 트래픽이라면 EC2 한 대에 직접 만든 blue-green이 PaaS 대비 비용이 몇 분의 일이고, 배포 파이프라인의 모든 단계를 제가 설명할 수 있습니다. 인프라를 옮겨야 할 트래픽이 오면 옮길 겁니다. 그때도 무엇을 자동화하고 어디에 게이트를 둘지에 대한 판단은 그대로 가져갈 수 있습니다.