Argo Rollouts — 승격 이전의 단계, 그리고 롤백이 실패하는 방식
Deployment를 Rollout으로 바꾸면 “카나리로 나간다"가 생깁니다. 그런데 실제로 생기는 것은 스텝 인덱스 하나와, 그 인덱스를 읽는 서로 다른 함수 두 개입니다. 하나는 ReplicaSet을 몇 대로 띄울지 정하고 다른 하나는 트래픽을 몇 퍼센트 보낼지 정합니다. 둘이 같은 값에 도달하지만 도달하는 시각이 다릅니다.
이 챕터는 그 시차가 어디서 오고, 왜 롤백에서만 벌어지고, 우리 함대에서 실제로 무엇을 부쉈는지를 다룹니다.
1부 — 승격 이전 (/rollouts/01-canary-step-analysisrun/)
- Rollout이 Deployment 자리에 더 붙이는 것은 세 평면입니다. 파드 층은 같고 트래픽 층과 판정 층이 새로 생깁니다
✓ - 이 차트에서 Rollout 하나는 값이 다섯 번 겹쳐 만들어집니다. 맵은 깊게 합쳐지지만
canary.steps는 리스트라 통째로 교체됩니다✓ - step은 여덟 종류이고 사내 기본값은
setWeight 5 → pause 10m → setWeight 100세 단입니다✓ - 정상 배포를 안전하게 만드는 것은
atDesiredReplicaCount게이트입니다. 다만 이것은 “가중치를 올리기 전에 파드를 준비한다"는 양의 보장이 아니라 “파드가 미달이면 이전 가중치를 쓴다"는 음의 보장입니다✓ - AnalysisRun 판정은 누적
result.Failed > failureLimit입니다 — 연속이 아닙니다✓
2부 — 롤백이 스스로를 취소한다 (/rollouts/02-rollback-window-weight/)
- 오류율 쿼리에 리비전을 가르는 라벨이 없습니다. 인시던트 중 롤백하면 새 AnalysisRun이 아직 높은 오류율을 읽고 롤백 자체를 abort합니다
✓ - 그래서
rollbackWindow를 넣었습니다(2025-06-27). 스텝 인덱스를 끝으로 던지고 실행 중인 AnalysisRun까지 취소합니다 —promote --full과 같은 코드, 같은 줄입니다✓ - 가중치를 정하는 if/else 체인은
:187(stable로 동적 복귀) →:194(완전 승격) →:199(abort) →:217(canary 0대) →:229(PromoteFull) →:243(그 밖 전부, 역탐색)의 여섯 갈래입니다. 갈리는 곳은 그중 하나 —promote --full은:229에 자기 분기가 있어 현재 가중치를 동결하는데,rollbackWindow는 그 분기가 없어:243→:245역탐색으로 떨어져 방금 건너뛴 마지막setWeight: 100을 집습니다.:199(abort)가:243보다 앞이므로, 이 사고는 abort되지 않은rollbackWindow경로에서만 성립합니다✓ - 그 시점 canary RS는 하한인 2대입니다. 가용량 게이트는 stable만 검사하고, 가중치 100%면 stable 요구치가 0이라 무조건 통과합니다
✓ - 2026-08-21 prod 실측: UH 503 28건(원 출처 미확인)
?, 요청 결손 3,058건(−80.1%, 기대치 대비 산출값)≈, endpoint 0 구간 30초✓
3부 — 그래서 무엇을 할 것인가 (/rollouts/03-what-to-do/)
- 처방은 마지막
setWeight: 100한 줄 삭제입니다. 그 스텝은 100% 도달에 아무 역할이 없고 파드 수도 변하지 않습니다✓ - 대가는 하나 — 램프 구간 내내 95%가 되돌리려던 버전으로 갑니다. 롤백 완료 시각은 변하지 않습니다
✓ - 적용이 두 갈래로 갈립니다. Helm이 리스트를 교체하므로 base 수정과 오버라이드 386블록 수정은 별개 작업입니다
✓ dynamicStableScale: true는 악화입니다. 게다가 우리 차트는 그 필드를 렌더하지 않습니다✓- 업스트림은 아직 안 고쳤습니다. PR #4852는 2026-07-15에 열려 리뷰 승인까지 갔지만, 2026-08-27 조회 시점 여전히 머지 전입니다. CI는 unit 2,583건이 통과했으나 별도 e2e 리포트에 2건 실패가 남아 있어 ‘전부 통과’로는 단정할 수 없습니다
✓(GitHub 조회, 2026-08-27) minPodsPerReplicaSet이 왜 2였나 — 같은 값 하나가 방향에 따라 정반대 증상으로 1년 3개월간 재발한 이야기✓- 처방이 성립하는 근거 자체가 깨지기 쉽습니다.
:245가:255·:261보다 먼저 평가된다는 분기 순서에 의존하므로, 컨트롤러 버전을 올릴 때마다 이 순서를 고정하는 회귀 테스트가 필요합니다Σ
왜 세 편인가. 1부 없이 2부를 읽으면 “역탐색이 100을 집는다"가 기괴한 버그로만 보입니다. 그 코드는 원래 안전장치입니다 — 주석이 그렇게 적혀 있고(“Use the previous weight since the new RS is not ready for a new weight”), 정상 배포에서는 실제로 그렇게 동작합니다. 인덱스를 끝으로 던지는 다른 기능이 그 “이전 가중치"의 의미를 바꿔버린 것이 사건의 전부입니다. 그러니 먼저 정상 경로에서 그 장치가 무엇을 지키는지를 봐야 합니다.
2부와 3부를 나눈 이유는 다릅니다. 2부는 “왜 깨지나"이고 3부는 “그래서 뭘 하나"인데, 읽는 목적이 달라서 한 문서에 두면 어느 쪽으로 읽어야 할지 모호해집니다. 처방만 필요한 사람은 3부만 봐도 됩니다.
근거의 출처
- 컨트롤러 코드:
argo-rolloutsv1.8.2 소스와 master(2026-08-25)를 직접 대조했습니다. 인용한파일:줄은 v1.8.2 기준이고 master에서 줄 번호가 밀린 곳은 본문에 함께 적었습니다. - 차트: 사내
yo-charts실물 스냅샷(2026-08-18)입니다.platform/charts/base의rollouts.yaml·analysistemplate.yaml·values.yaml과 서비스별 values, 그리고 git 히스토리. - 실측: prod k8s Event, Envoy 액세스 로그, Datadog 메트릭. 2026-08-21 사고 타임라인.
- 업스트림 이슈: GitHub에서 상태를 직접 확인했습니다(2026-08-27 시점).
근거 등급은 문장 끝에 표기합니다 — ✓ 확인됨 · ≈ 추정 · ? 미확인 · Σ 종합 판단. ✓는 코드·차트 대조로 재현 가능한 사실에만 씁니다. GitHub 이슈·PR처럼 시점에 따라 상태가 바뀌는 외부 근거는 ✓(GitHub 조회, 날짜)로 조회 시점을 함께 적습니다.
문서
| 문서 | 한 줄 요약 |
|---|---|
| 01 승격 이전 — step과 AnalysisRun | Rollout이 더 붙이는 세 평면, 값이 겹치는 다섯 층, 리컨실 한 바퀴의 호출 순서, step 여덟 종류와 승격의 정의, AnalysisRun의 측정 루프와 abort |
| 02 롤백이 스스로를 취소한다 | 오류율 쿼리에 리비전 필터가 없어 롤백이 자기를 abort하는 경로, rollbackWindow가 정확히 하는 일, promote --full과 갈리는 한 줄, 가용량 게이트가 canary를 보지 않는다는 사실, 2026-08-21 실측 |
| 03 그래서 무엇을 할 것인가 | 마지막 setWeight: 100 제거와 그 대가, 안전 한계식, 적용이 두 갈래로 갈리는 이유, dynamicStableScale 기각, 업스트림 이슈 패밀리, minPodsPerReplicaSet 사가, 정리와 남는 위험 |
인접 챕터: Istio — VirtualService·DestinationRule·subset의 의미는 그쪽이 정본입니다. 이 챕터는 그 위에서 Rollout 컨트롤러가 무엇을 어떤 순서로 고쳐 쓰는지만 봅니다.