1편의 문제 2와 6: 마이그레이션은 성공했는데 ECS 배포가 실패하면, DB는 새 스키마인데 코드는 구버전인 상태가 되고, 자동 복구 수단이 없었다. 새 이미지가 시작 직후 크래시하거나 헬스체크가 실패하면 흔히 일어난다.
미리 저장, 나중에 복구
아이디어는 단순하다.
- 배포 전에 현재 실행 중인 Task Definition ARN을 저장
- 배포가 실패하면 저장한 ARN으로 되돌림
ECS는 Task Definition을 버전으로 관리한다. 배포할 때마다 새 버전이 등록되고, 이전 버전은 삭제되지 않고 남는다. ARN만 알면 언제든 되돌릴 수 있다.
Configure AWS credentials → [신규] Save current Task Definition ← 현재 ARN 저장 → Build → Register Task Def → Migration → [수정] Deploy ECS ← continue-on-error: true → [신규] Rollback on failure ← deploy 실패 시에만현재 ARN 저장
- name: Save current Task Definition (for rollback) id: save-task-def run: | PREV=$(aws ecs describe-services \ --cluster $CLUSTER --services "$SERVICE" \ --query 'services[0].taskDefinition' \ --output text 2>/dev/null || echo "NONE") echo "previous=$PREV" >> "$GITHUB_OUTPUT"describe-services가 현재 서비스의 Task Definition ARN을 준다. 이걸 $GITHUB_OUTPUT에 previous로 저장하면 이후 스텝에서 ${{ steps.save-task-def.outputs.previous }}로 꺼낼 수 있다.
2>/dev/null || echo "NONE"은 최초 배포(서비스 미존재)나 권한 문제로 조회가 실패하는 경우를 처리한다. 둘 다 NONE을 저장해 롤백을 건너뛰게 한다. 이 처리가 없으면 조회 실패가 전체 배포를 중단시킨다.
Deploy에 continue-on-error
- name: Deploy Amazon ECS task definition id: deploy continue-on-error: true uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: service: $SERVICE cluster: $CLUSTER wait-for-service-stability: truecontinue-on-error가 없으면 이 스텝이 실패하는 순간 job이 끝나버려 롤백 스텝이 실행될 기회가 없다. 붙이면 실패해도 다음 스텝으로 넘어간다.
여기서 함정이 하나 있다. continue-on-error가 붙은 스텝은 결과를 두 가지로 기록한다.
| 속성 | 값 | 의미 |
|---|---|---|
steps.deploy.outcome | failure | 스텝의 실제 결과 |
steps.deploy.conclusion | success | job에 보고되는 결과 (마스킹됨) |
continue-on-error는 job을 계속 진행시키려고 conclusion을 success로 바꾼다. 하지만 outcome은 실제 결과를 유지한다. 그래서 롤백 조건은 반드시 outcome을 봐야 한다. conclusion을 보면 항상 success라 롤백이 절대 안 돈다. 처음에 이걸 모르고 conclusion으로 짰다가, 일부러 실패시켜도 롤백이 안 뛰어서 한참 헤맸다.
실패 시에만 롤백
- name: Rollback on deployment failure if: steps.deploy.outcome == 'failure' run: | PREV="${{ steps.save-task-def.outputs.previous }}" if [ "$PREV" == "NONE" ] || [ -z "$PREV" ]; then echo "No previous task definition. Skipping rollback." exit 1 fi aws ecs update-service \ --cluster $CLUSTER --service "$SERVICE" \ --task-definition "$PREV" exit 1배포가 성공하면 if 조건에서 걸러져 아예 실행되지 않는다. 실패 시 update-service로 구 Task Definition을 다시 물린다. ECS가 롤링으로 구 컨테이너를 띄우기 시작한다. 워크플로우는 롤백 완료를 기다리지 않는다. 트리거만 하고 ECS에 맡긴다.
마지막 exit 1이 중요하다. 롤백을 트리거했다고 배포가 성공한 건 아니다. 워크플로우는 여전히 실패로 기록돼야 개발자가 알림을 받고, PR 자동 머지가 막힌다.
롤백이 못 푸는 것 — 그리고 ADD-only와의 관계
이 롤백은 코드만 되돌린다. DB 마이그레이션은 적용된 상태 그대로다. 그런데도 안전한 이유는 이 프로젝트의 마이그레이션 정책 때문이다.
| 마이그레이션 | 구 코드 + 새 스키마 | 결과 |
|---|---|---|
| ADD COLUMN | 새 컬럼을 모름 | 무시하고 동작 ✅ |
| DROP COLUMN | 없는 컬럼 참조 | 즉시 크래시 ❌ |
| DROP TABLE | 없는 테이블 쿼리 | 즉시 크래시 ❌ |
ADD-only 정책이 없으면 코드 롤백은 오히려 DB 크래시를 부른다. 자동 롤백과 ADD-only 마이그레이션은 분리된 규칙이 아니라 서로를 전제하는 쌍이다. 둘 중 하나만 있으면 위험하다.
남은 문제: 매 FE 배포마다 생기는 CloudFront 캐시 전파 지연. 마지막 편에서 파일 특성에 맞는 캐시 전략으로 푼다.