본문으로 건너뛰기
Argo Rollouts

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-rollouts v1.8.2 소스와 master(2026-08-25)를 직접 대조했습니다. 인용한 파일:줄은 v1.8.2 기준이고 master에서 줄 번호가 밀린 곳은 본문에 함께 적었습니다.
  • 차트: 사내 yo-charts 실물 스냅샷(2026-08-18)입니다. platform/charts/baserollouts.yaml·analysistemplate.yaml·values.yaml과 서비스별 values, 그리고 git 히스토리.
  • 실측: prod k8s Event, Envoy 액세스 로그, Datadog 메트릭. 2026-08-21 사고 타임라인.
  • 업스트림 이슈: GitHub에서 상태를 직접 확인했습니다(2026-08-27 시점).

근거 등급은 문장 끝에 표기합니다 — 확인됨 · 추정 · ? 미확인 · Σ 종합 판단. 는 코드·차트 대조로 재현 가능한 사실에만 씁니다. GitHub 이슈·PR처럼 시점에 따라 상태가 바뀌는 외부 근거는 ✓(GitHub 조회, 날짜)로 조회 시점을 함께 적습니다.

문서

문서한 줄 요약
01 승격 이전 — step과 AnalysisRunRollout이 더 붙이는 세 평면, 값이 겹치는 다섯 층, 리컨실 한 바퀴의 호출 순서, step 여덟 종류와 승격의 정의, AnalysisRun의 측정 루프와 abort
02 롤백이 스스로를 취소한다오류율 쿼리에 리비전 필터가 없어 롤백이 자기를 abort하는 경로, rollbackWindow가 정확히 하는 일, promote --full과 갈리는 한 줄, 가용량 게이트가 canary를 보지 않는다는 사실, 2026-08-21 실측
03 그래서 무엇을 할 것인가마지막 setWeight: 100 제거와 그 대가, 안전 한계식, 적용이 두 갈래로 갈리는 이유, dynamicStableScale 기각, 업스트림 이슈 패밀리, minPodsPerReplicaSet 사가, 정리와 남는 위험

인접 챕터: Istio — VirtualService·DestinationRule·subset의 의미는 그쪽이 정본입니다. 이 챕터는 그 위에서 Rollout 컨트롤러가 무엇을 어떤 순서로 고쳐 쓰는지만 봅니다.

마지막 수정 일자