새 버전을 전체 사용자에게 한 번에 배포했습니다. 5분 뒤 에러율이 치솟습니다. 롤백하려는데 "이전 버전이 어디 있죠? 빌드 다시 해야 하나요?"로 또 20분이 흘러갑니다. 그사이 전체 사용자가 장애를 겪습니다. 옆 팀은 같은 위험한 변경을 5% 사용자에게만 먼저 흘렸습니다. 지표가 나빠지자 그 5%만 영향받은 채 1분 만에 되돌렸습니다. 릴리스 전략은 '무엇을 배포하나'가 아니라 '어떻게 내보내고 어떻게 되돌리나'입니다. 같은 코드도 전략에 따라 장애 반경과 복구 속도가 완전히 달라집니다.
- 1블루그린·카나리·롤링 배포의 구조와 롤백 방식을 구분할 수 있다
- 2기능 플래그가 "배포와 노출을 분리"하는 원리를 설명할 수 있다
- 3점진 배포가 관측성·자동 롤백과 결합돼야 함을 설명할 수 있다
- 4상황(위험도·비용·인프라)에 맞는 릴리스 전략을 고를 수 있다
점진 배포 — 장애 반경을 줄인다
한 번에 전부 vs 조금씩 확대
가장 단순한 배포는 '한 번에 전부 교체'지만, 문제가 있으면 전체 사용자가 즉시 영향받습니다. 점진 배포는 이 위험을 줄입니다.
확대
핵심: 점진 배포는 장애 반경(blast radius) 을 줄이고 롤백을 빠르게 합니다. 단, 카나리·롤링은 SLO·에러버짓·포스트모템의 관측성이 있어야 "이상 감지 → 롤백"이 작동합니다.
확대
위 그림처럼 블루그린은 즉각 전환·롤백이 강점이지만 환경 비용이 2배고, 카나리는 소수 영향으로 데이터 기반 확대가 가능하지만 관측성 인프라가 필수이며, 롤링은 추가 환경 없이 점진 교체하지만 두 버전이 잠시 공존합니다.
기능 플래그 — 배포와 노출의 분리
코드는 배포하되, 노출은 스위치로 제어
브랜치 전략에서 잠깐 본 기능 플래그를 제대로 봅니다. 핵심은 배포(코드가 prod에 존재) ≠ 릴리스(사용자에게 노출) 를 분리하는 것입니다.
if (featureFlags.isEnabled("new-checkout", user)) {
// 새 결제 흐름
} else {
// 기존 흐름
}
→ 새 결제 코드를 배포(prod에 존재)하되 플래그 OFF로 숨김
→ 준비되면 ON: 내부직원 → 5% → 전체 점진 노출(카나리와 결합)
→ 문제 발생: 재배포 없이 플래그 OFF로 '즉시' 차단(kill switch)
장점:
- 미완성 기능을 main에 머지·배포해도 안전(Trunk-based 가능케 함).
- 릴리스 타이밍을 코드 배포와 분리 → 마케팅 일정에 맞춰 ON.
- 즉시 롤백: 코드 롤백보다 빠른 플래그 OFF.
- 점진 노출·A/B 테스트: 특정 그룹에만 노출해 실험.
주의: 플래그가 쌓이면 코드가 복잡해지므로(if 분기 누적), 완전히 출시된 플래그는 정리(cleanup) 해야 합니다 — 이는 기술 부채와 리팩터링의 부채 항목입니다.
확대
위 그림처럼 기능 플래그는 코드를 prod에 배포하되 플래그 OFF로 숨겨두고, 준비가 됐을 때 내부→5%→전체로 점진 노출하며, 문제 시 재배포 없이 즉시 kill switch로 차단합니다.
롤백 준비 점검 — 직접 확인
릴리스 전략의 절반은 '되돌릴 수 있는가'입니다. 이전 버전이 보존돼 있고 한 번에 롤백 가능한지 미리 확인합니다(장애 순간엔 늦습니다).
# 쿠버네티스: 배포 이력과 즉시 롤백 가능 여부
kubectl rollout history deployment/app # 리비전 목록
kubectl rollout undo deployment/app # 직전 버전으로 롤백
# 이전 이미지 태그가 레지스트리에 남아 있나(롤백 기준점)
# (남아 있어야 빠른 롤백 가능 — latest만 쓰면 못 돌아감)
# 기능 플래그로 끌 수 있는 기능인가(가장 빠른 롤백)
echo "kill switch 가능 기능 목록 확인"
$ kubectl rollout history deployment/app
REVISION CHANGE-CAUSE
3 app:v1.2.0
4 app:v1.2.1 ← 현재. 문제 시 'undo'로 3(v1.2.0)으로 1분 내 복귀
→ 이전 리비전 보존됨 + 이미지 태그 불변 → 롤백 안전
kubectl rollout history deployment/app- rollout history에 이전 리비전이 남아 있으면 → rollout undo로 즉시 롤백 가능(안전). 이력이 없거나 latest 태그만 쓰면 "되돌릴 곳이 없는" 위험 상태
- 이미지 태그가 불변(v1.2.0처럼 고정)인지 확인 — latest를 덮어쓰면 같은 태그가 다른 코드라 롤백이 무의미. 시맨틱 버저닝의 불변 태그가 전제
- 카나리/롤링이면 "무슨 지표가 X 넘으면 롤백"을 사전 정의했는지 확인 — 기준 없으면 점진 배포의 의미가 없다. 가능하면 자동 롤백 연결
- 기능 플래그로 끌 수 있는 변경이면 그게 가장 빠른 롤백(재배포 불필요) — 위험 기능은 플래그 뒤에 두는 것을 릴리스 계획에 포함
확대
위 그림처럼 카나리 배포는 5% → 25% → 50% → 100% 순으로 트래픽을 확대하며, 에러율·응답시간 등 지표가 임계를 넘으면 그 비율만 영향받은 채 즉시 v1으로 롤백합니다.
상황: 새 버전을 전체에 한 번에 배포했는데 장애가 났습니다. 롤백하려니 "이전 이미지를 latest로 덮어써서" 되돌릴 대상이 없고, 재빌드에 20분이 걸려 그동안 전체 사용자가 장애를 겪습니다.
원인: 두 가지 실패가 겹쳤습니다. (1) 일괄 배포로 장애 반경이 100%, (2) 가변 태그(latest) 사용으로 이전 버전이 보존되지 않아 롤백 불가. 릴리스 전략과 버저닝이 모두 부재했습니다.
진단 — 사전 점검(배포 전):
□ 이전 버전 이미지/산출물이 불변 태그로 보존돼 있는가?
□ 한 번에 전체가 아니라 점진(카나리/롤링)으로 내보낼 수 있는가?
□ 롤백 명령이 1줄로 준비돼 있는가? (rollout undo 등)
□ 무슨 지표가 나빠지면 롤백할지 기준이 있는가?
해결: (1) 불변 태그(v1.2.0)로 모든 버전을 레지스트리에 보존(시맨틱 버저닝). (2) 위험 변경은 카나리로 5%부터, 관측성으로 지표를 보며 확대. (3) 롤백을 1줄 명령 또는 기능 플래그 OFF로 준비. "어떻게 배포하나"만큼 "어떻게 되돌리나"를 항상 먼저 설계합니다 — 롤백 계획 없는 배포는 도박입니다.
심화 — 롤백을 막는 것은 코드가 아니라 데이터
심화: 두 버전이 공존하는 시간 — 점진 배포의 숨은 계약
카나리·롤링·rollout undo를 갖추면 안전해 보이지만, 이 전략들은 모두 구버전과 신버전이 동시에 살아 있는 시간을 만듭니다. 그 시간을 견디는 호환성이 없으면, 위험을 줄이려던 전략이 오히려 사고를 만듭니다.
- N-1 호환성: 롤링·카나리 동안 v1과 v2는 같은 DB·캐시·세션 저장소를 공유합니다. 세션 직렬화 포맷을 바꾸면 v2가 저장한 세션을 v1이 못 읽어, '배포 중에만 나타났다 사라지는' 간헐 오류가 됩니다. 요청마다 다른 버전에 떨어질 수 있다는 전제로 모든 변경을 설계해야 합니다.
- rollout undo가 못 되돌리는 것: 코드 리비전은 1분 만에 돌아가도, 이미 실행된 DB 마이그레이션·큐에 쌓인 신형 메시지·사용자 기기에 배포된 모바일 앱은 돌아오지 않습니다. 롤백 계획은 코드만이 아니라 데이터·클라이언트까지 포함해야 완성입니다.
- 파괴적 변경은 여러 릴리스로 쪼갭니다(expand/contract): 컬럼 rename·삭제처럼 구버전을 깨뜨리는 변경은 한 릴리스에 하지 않습니다 — 먼저 추가하고(expand), 양쪽을 함께 지원하다가, 전 인스턴스가 신버전이 된 다음 릴리스에서 제거(contract)합니다.
- kill switch의 맹점: 플래그 OFF로 돌아갈 구 경로를 몇 주간 아무도 타지 않았다면, 그 경로는 이미 다른 변경과 어긋나 있을 수 있습니다 — 끄는 순간이 두 번째 장애가 되기도 합니다. 플래그 시스템 자체가 죽었을 때 각 플래그가 어느 쪽으로 동작할지(기본값)까지가 릴리스 설계의 일부입니다.
점진 배포의 성숙도는 배포 도구가 아니라, 변경을 '공존 가능한 조각'으로 쪼개는 팀의 습관에서 드러납니다.
상황: 신버전을 카나리 5%로 내보내기 시작한 지 몇 분 만에, 에러율이 5%가 아니라 거의 전 트래픽에서 급등합니다. 카나리를 즉시 0%로 되돌렸는데도 에러가 계속됩니다 — 롤백을 했는데 장애가 끝나지 않는 상황입니다.
원인: 신버전 배포에 포함된 DB 마이그레이션이 컬럼명을 rename했습니다. 코드는 5%만 신버전이지만 DB는 100% 공유라 마이그레이션은 전역 적용 — 구버전 95%가 존재하지 않는 옛 컬럼을 조회하며 즉사한 것입니다. 카나리 롤백은 코드만 되돌릴 뿐 스키마는 그대로라 에러가 멈추지 않습니다. 파괴적 마이그레이션 앞에서 카나리 5%는 사실상 100% 배포였습니다.
진단:
kubectl logs deploy/app --since=10m | grep -c 'column user_name does not exist'
# 에러가 신버전이 아니라 '구버전' pod에서 대량 발생 — 코드가 아니라 공유 자원 의심
psql -c '\d users' # user_name → username rename 확인
# 마이그레이션 적용 시각 == 에러 시작 시각 → 확정
해결: 1순위는 코드 롤백이 아니라 스키마 역마이그레이션(컬럼명 복구)으로 구버전을 살리는 것입니다. 재작업은 expand/contract로 진행합니다: 새 컬럼 추가 → 신버전이 양쪽에 쓰기 → 백필 → 전 인스턴스 전환 확인 후 다음 릴리스에서 옛 컬럼 제거. 원칙은 하나입니다 — 마이그레이션은 항상 직전 버전 코드와 호환되게. 배포 전 체크리스트에 "이 릴리스에 구버전을 깨뜨리는 스키마 변경이 있는가"를 넣고, 있다면 점진 배포가 아니라 릴리스 분할부터 합니다.
인프라/SRE로서 릴리스 전략은 당신의 핵심 설계 영역입니다 — 카나리/블루그린/롤링을 배포 도구(Argo Rollouts·Spinnaker·K8s)로 구현하고, SLO·에러버짓·포스트모템의 지표와 연결해 "에러율이 임계 초과 시 자동 롤백"을 건립니다. 모든 배포에 '되돌리는 길'(불변 태그·rollout undo·기능 플래그 kill switch)을 미리 깔아두는 것이 SRE의 제1원칙입니다. PM은 기능 플래그로 '배포 ≠ 노출'을 활용해 릴리스 타이밍을 마케팅·실험 일정에 맞추고, 카나리로 위험한 변경을 데이터 기반으로 점진 출시합니다. 좋은 릴리스 전략은 장애를 없애는 게 아니라, 장애의 반경과 시간을 작게 만드는 것입니다.
다음 모듈에서는 이 모든 배포의 전제가 되는 '버전을 어떻게 매기고 호환성을 관리하는가' — 시맨틱 버저닝을 다룹니다.
실전 랩으로 손에 익히기: 릴리스 전략 실습 — 블루그린·카나리·롤링과 기능 플래그를 상황에 맞게 고르고 지표 게이트를 설계합니다.