롤링 배포 및 롤백 절차 - 100-hours-a-week/KTB4-16th-Cloud GitHub Wiki

v2 롤링 배포와 롤백 절차

v2 CD 파이프라인이 MIG를 롤링 업데이트로 교체하는 과정과, 문제가 생겼을 때 되돌리는 절차를 정리했다.

1. 배포 절차 (Backend, AI, Frontend)

1-1. 전체 흐름

단계 누가 내용 소요 시간
1 CI main에 병합되면 CI가 이미지를 빌드해 Artifact Registry에 올린다 (측정 후)
2 CD prepare 배포할 태그를 정하고, 그 이미지가 있는지 확인하고, 현재 템플릿과 현재 태그를 기록한다. 현재 태그와 같으면 이후 단계를 건너뛴다 (측정 후)
3 사람 production 환경 승인 (사람 시간, 제외)
4 CD rolling-update 현재 템플릿을 복사하고 app-image-tag만 바꾼 새 템플릿을 만든다 (측정 후)
5 MIG 새 VM을 만든다(surge). VM이 부팅하면서 Secret Manager에서 env를 읽고 이미지를 받아 컨테이너를 실행한다 (측정 후)
6 로드밸런서 새 VM이 헬스체크를 통과하면 트래픽 대상에 넣는다 (측정 후)
7 MIG 기존 VM의 연결을 드레이닝하고(종료 대기 60초), VM을 삭제한다. AI(2대)는 한 대씩 반복한다 (측정 후)
8 CD wait-until로 전체 교체와 안정 상태를 확인한다. 실패하면 자동 롤백한다 (측정 후)
9 CD 오래된 템플릿을 최근 5개만 남기고 지우고, Discord로 결과를 알린다 (측정 후)

1-2. 자동 배포

  1. 개발자가 PR을 develop에 병합하고, develop을 main에 병합한다.
  2. main 병합이 CI를 시작한다. CI가 성공하면 CD가 workflow_run으로 자동 시작한다.
  3. prepare가 끝나면 승인 요청이 뜬다. 승인자가 CI 결과, 이번 변경 내용, DB 스키마 변경 여부를 확인하고 승인한다.
  4. 승인 후 rolling-update가 진행된다. Actions 화면에서 각 스텝을 볼 수 있다.
  5. 완료되면 Discord 알림을 확인하고 3-4의 배포 후 확인을 한다.

1-3. 수동 배포 (태그를 지정하거나 재배포할 때)

특정 태그로 배포하거나 같은 태그를 다시 배포해야 할 때 GitHub Actions에서 CD를 수동 실행한다.

# 예: Backend를 1.7.1 태그로 배포
gh workflow run CD.yml --repo <조직>/<Backend 레포> --ref main -f image_tag=1.7.1

# 같은 태그여도 다시 배포 (force)
gh workflow run CD.yml --repo <조직>/<Backend 레포> --ref main -f image_tag=1.7.1 -f force=true
  • image_tag를 비우면 가장 최근 태그를 배포한다.
  • 현재 배포된 태그와 같으면 기본적으로 건너뛴다. 같은 태그를 일부러 다시 배포할 때만 force=true를 쓴다.

1-4. 배포 후 확인

배포가 끝났다는 알림을 받으면 다음을 확인한다. 변수는 서비스에 맞게 바꾼다.

export ZONE=asia-northeast3-a
export MIG=muro-backend-mig     # 서비스에 맞게 변경

# 1) MIG가 안정 상태이고 모든 VM이 새 템플릿을 쓰는지
gcloud compute instance-groups managed describe $MIG --zone=$ZONE \
  --format="value(status.isStable, versions[0].instanceTemplate.basename())"

# 2) VM별 상태, 현재 작업, 템플릿, 헬스
gcloud compute instance-groups managed list-instances $MIG --zone=$ZONE \
  --format="table(instance.basename(), instanceStatus, currentAction, version.instanceTemplate.basename(), instanceHealth[0].detailedHealthState)"

# 3) 템플릿에 기록된 이미지 태그가 의도한 태그인지
gcloud compute instance-templates describe <현재 템플릿> --format=json \
  | jq -r '.properties.metadata.items[] | select(.key=="app-image-tag") | .value'
확인 항목 정상 기준
MIG 안정 상태 isStable이 True, 모든 VM의 currentAction이 NONE
VM 템플릿 모든 VM이 새 템플릿
VM 헬스 모든 VM이 HEALTHY
로드밸런서 백엔드 백엔드 서비스의 모든 인스턴스가 정상 (gcloud compute backend-services get-health <백엔드 서비스> --global)
서비스 응답 외부 도메인으로 /health와 주요 API가 정상 응답
에러율 배포 직후 5xx와 타임아웃이 늘지 않음 (로그와 모니터링 확인)
AI 추가 확인 Cloud Tasks 큐에 실패와 재시도가 쌓이지 않음

2. 배포 절차 (Chat Server, Recreate)

Chat Server는 서비스가 끊기므로 사전에 안내한 점검 시간에만 배포한다. CI 성공으로 자동 실행되지 않고 수동 실행만 가능하다.

  1. 점검 시간을 사용자에게 사전 공지한다. (채팅방이 매일 00시에 갱신되는 구조라 그 직전 시간대를 피하는 것이 좋다.)
  2. 점검 시간에 CD를 수동 실행한다. maintenance_confirmed를 반드시 켠다.
gh workflow run CD.yml --repo <조직>/<Chat 레포> --ref main \
  -f image_tag=<태그> -f maintenance_confirmed=true
  1. 승인 후 진행 과정은 다음과 같다.
    1. 새 템플릿을 만든다.
    2. MIG가 기존 VM을 내린다. VM이 종료될 때 종료 스크립트(docker stop -t 60)가 처리 중인 메시지 저장과 연결 정리를 마치도록 최대 60초를 기다린다.
    3. 같은 자리에 새 VM을 만들고 부팅 스크립트가 컨테이너를 실행한다.
    4. 헬스체크를 통과하면 로드밸런서가 WebSocket 연결을 다시 받는다.
  2. 3-4의 배포 후 확인을 하고, 채팅 연결과 메시지 송수신이 정상인지 직접 확인한 뒤 점검 종료를 공지한다.
  3. 실패하면 5번의 자동 롤백이 이전 템플릿으로 다시 Recreate한다. 이 과정에서 서비스가 추가로 끊기므로 점검 시간 안에 끝나도록 일정을 여유 있게 잡는다.

3. 롤백

3-1. 자동 롤백 (CD 안에서)

rolling-update job은 교체를 시작한 뒤에 실패하면(wait-until이 실패하거나 타임아웃되는 경우) 이전 템플릿(prepare가 기록한 값)으로 다시 롤링 업데이트를 시작한다.

  • 교체를 시작하기 전에 실패한 경우(템플릿 생성 실패 등)에는 롤백하지 않는다. MIG가 아직 바뀌지 않았기 때문이다.
  • 롤백 후에도 같은 wait-until로 이전 버전으로 돌아왔는지 확인한다. 롤백까지 실패하면 job이 실패로 끝나고 알림이 온다. 이때는 5-3의 수동 롤백을 한다.
  • 이전 템플릿은 최근 5개를 보관하므로 자동 롤백이 가능하다. 5개를 넘는 오래된 버전으로는 5-2의 태그 재배포를 쓴다.

3-2. 수동 롤백 (배포는 성공했는데 문제를 나중에 발견한 경우)

문제를 발견한 시점에 따라 두 가지 방법이 있다.

방법 A. CD로 이전 태그 재배포 (기본)

  1. 문제가 없던 이전 버전의 이미지 태그를 확인한다. (GitHub Releases 또는 Artifact Registry의 태그 목록)
  2. CD를 수동 실행하고 image_tag에 이전 태그를 넣는다. (3-3 명령)
  3. 승인하고 3-4의 배포 후 확인을 한다.

Blue-Green처럼 즉시 롤백되지는 않는다. 일반 배포와 같은 Rolling Update 흐름이라 같은 시간이 걸린다. 설계서에서 이 부분을 수용했다.

방법 B. 이전 템플릿으로 바로 교체 (긴급, 승인 단계 생략)

서비스 장애처럼 급한 경우 승인과 prepare를 거치지 않고 gcloud로 이전 템플릿을 바로 적용한다. 장애 대응 권한이 있는 사람이 쓴다.

export ZONE=asia-northeast3-a
export MIG=muro-backend-mig

# 1) 이전 템플릿 확인 (최근 순)
gcloud compute instance-templates list \
  --filter="name~^backend-tpl-" --sort-by="~creationTimestamp" \
  --format="table(name, creationTimestamp)"

# 2) 이전 템플릿으로 롤링 업데이트
gcloud compute instance-groups managed rolling-action start-update $MIG --zone=$ZONE \
  --version=template=<이전 템플릿 이름> \
  --type=proactive --minimal-action=replace --most-disruptive-allowed-action=replace \
  --replacement-method=substitute --max-surge=1 --max-unavailable=0

# 3) 완료 대기
gcloud compute instance-groups managed wait-until $MIG --zone=$ZONE \
  --version-target-reached --timeout=900
  • Chat Server는 -replacement-method=recreate --max-surge=0 --max-unavailable=1로 바꾼다.
  • 긴급 롤백 후에는 GitHub의 상태와 실제 상태가 어긋나므로, 원인을 정리한 뒤 CD를 수동 실행해서 정상 태그로 맞춘다.

3-3. 자동 롤백까지 실패했을 때

  1. list-instances로 VM별 템플릿과 헬스를 확인한다. 일부 VM만 교체된 혼합 상태일 수 있다.
  2. 가장 최근에 정상이던 템플릿(list로 확인)으로 5-2의 방법 B를 실행한다.
  3. 템플릿 자체가 부팅에 실패하는 경우(시크릿, 이미지, 설정 문제)에는 원인이 같은 템플릿을 쓰지 않도록 이전 안정 템플릿을 고른다.
  4. MIG에 정상 VM이 하나도 없는 상태라면 서비스가 중단된 것이므로 장애 대응으로 전환한다.

3-4. 롤백할 수 없거나 주의할 경우

상황 이유와 대응
DB 마이그레이션이 이미 적용됨 Flyway 마이그레이션은 앞으로만 진행된다. 이전 버전 앱이 새 스키마에서 동작해야 롤백이 안전하다. 배포 전에 8번 규칙을 지킨다
Secret Manager 값을 바꾸고 배포함 부팅 스크립트가 시크릿을 읽는 버전에 따라 이전 이미지와 새 시크릿이 맞지 않을 수 있다. 시크릿 버전을 고정해서 읽는지 확인한다
이전 템플릿이 이미 삭제됨 템플릿은 최근 5개만 보관한다. 이 경우 5-2의 방법 A로 이전 태그를 재배포한다
AI 처리 중인 Cloud Tasks 작업 교체 중 끊긴 작업은 재시도된다. 작업이 멱등하게 구현돼 있어야 롤백 때도 안전하다

4. 인프라 설정 변경이나 시크릿 변경 후 VM 교체

이미지는 그대로인데 Secret Manager 값이나 템플릿 외 설정을 바꿨다면, 새 템플릿 없이 현재 템플릿으로 VM만 교체하면 된다. 부팅 스크립트가 시작할 때 값을 읽기 때문이다.

gcloud compute instance-groups managed rolling-action replace $MIG --zone=$ZONE \
  --max-surge=1 --max-unavailable=0
gcloud compute instance-groups managed wait-until $MIG --zone=$ZONE \
  --version-target-reached --timeout=900

Chat Server는 --max-surge=0 --max-unavailable=1로 하고 점검 시간에 한다.

⚠️ **GitHub.com Fallback** ⚠️