정상 Canary는 왜 안전한가: step과 AnalysisRun
Deployment로 롤링 업데이트를 하면 새 버전의 파드를 늘리면서 기존 버전의 파드를 줄여 갑니다.
이 과정에서 새 파드가 준비됐는지는 확인하지만, 새 버전에 트래픽을 몇 퍼센트 보낼지 정하거나 서비스의 오류율과 지연 시간을 기준으로 배포를 계속할지 판단하는 기능은 Deployment 자체에 없습니다.
Argo Rollouts는 Deployment 대신 Rollout 리소스로 배포를 관리하면서 이런 제어를 함께 제공합니다.
ReplicaSet을 통해 파드를 늘리고 줄이는 방식은 비슷하지만, Canary 배포에서는 step으로 배포 단계를 나누고 AnalysisRun으로 메트릭을 확인해 배포를 중단할 수 있습니다.
이 글의 환경에서는 traffic routing을 사용하기 때문에 setWeight: 5는 새 버전의 파드 수를 5%로 만드는 것이 아니라 실제 서비스 트래픽의 5%를 canary로 전달한다는 의미입니다.
예를 들어 새 버전에 트래픽을 5%만 보내고, 10분 동안 오류율과 지연 시간을 확인한 뒤 다음 단계로 진행할 수 있습니다.
이렇게 배포를 진행해 새 버전을 stable로 확정하는 것이 **승격(promotion)**입니다.
그렇다면 각 단계에서 파드 수와 트래픽 비율은 어떻게 정해지고, AnalysisRun의 판정은 배포에 어떻게 반영될까요?
이번 글에서는 정상적인 Canary 배포가 어떤 순서로 진행되고, 어떤 장치가 이를 안전하게 만드는지 살펴봅니다.
이 흐름을 먼저 이해하면 2부에서 다룰 롤백 문제가 어느 지점에서 발생하는지도 훨씬 쉽게 볼 수 있습니다.
1. Deployment와 Rollout의 차이
파드를 배포하는 층은 Deployment와 크게 다르지 않습니다.
Rollout 역시 ReplicaSet을 만들고 pod-template-hash로 버전을 구분하며, 새 버전의 파드를 늘리고 기존 버전의 파드를 줄입니다.
Rollout이 여기에 추가하는 것은 크게 두 가지입니다.
- 트래픽 제어
- 배포 상태 판정
일반 Kubernetes Service만으로는 stable과 canary에 95% / 5%처럼 가중치를 나눠 요청을 전달할 수 없습니다.
traffic routing을 사용하는 Rollout에서는 Controller가 stable과 canary를 구분하고, 현재 step에 맞춰 두 버전으로 전달할 트래픽 비율을 조절합니다.
따라서 Rollout에는 서로 다른 두 가지 상태가 존재합니다.
- 파드가 준비됐는가
- 그 파드에 트래픽을 얼마나 보낼 것인가
예를 들어 canary 파드 2대가 모두 Ready 상태라고 해도 트래픽은 5%만 받을 수 있습니다.
파드 수와 트래픽 비율을 서로 독립적으로 관리하기 때문입니다.
그리고 새 버전으로 배포를 계속 진행해도 되는지는 AnalysisRun이 판단합니다.
우리 환경에서는 오류율과 지연 시간을 확인하고, 기준을 만족하면 다음 단계로 진행하며 실패하면 현재 배포를 중단합니다.
정리하면 정상적인 Canary 배포는 다음과 같은 흐름으로 진행됩니다.
이 구조에서 가장 중요한 점은 파드가 준비되는 시점과 traffic weight가 변경되는 시점이 서로 다른 제어 단계라는 것입니다.
2. 이 글에서 보는 Rollout 설정
주요 설정
Rollout의 실제 동작은 최종적으로 렌더된 spec에 어떤 값이 들어가느냐로 결정됩니다.
이 글에서 중요하게 볼 설정은 다음과 같습니다.
| 설정 | 역할 |
|---|---|
strategy: canary | Canary 방식으로 배포 |
minReadySeconds | Ready 이후 안정 상태로 인정하기까지 기다리는 시간 |
revisionHistoryLimit | 과거 ReplicaSet을 유지할 개수 |
rollbackWindow.revisions | 빠른 롤백 대상으로 유지할 이전 revision 범위 |
canary.minPodsPerReplicaSet | stable/canary ReplicaSet에 유지할 최소 파드 수 |
canary.steps | 트래픽을 어떤 순서로 변경할지 정의 |
analysis.* | 오류율·지연 시간 등을 기준으로 배포 진행 여부를 판단 |
기본 Canary step은 다음과 같은 형태입니다.
steps:
- setWeight: 5
- pause:
duration: 10m
- setWeight: 100먼저 새 버전에 트래픽을 5% 보내고 일정 시간 관찰한 뒤, 문제가 없으면 100%까지 전환하는 구조입니다.
여기서 setWeight는 파드 수가 아니라 트래픽 비율입니다.
실제 canary ReplicaSet에 몇 개의 파드가 필요한지는 별도로 계산됩니다.
예를 들어 전체 replica 수가 작더라도 minPodsPerReplicaSet: 2가 설정되어 있다면 canary에는 최소 2대의 파드가 유지될 수 있습니다.
우리 환경에서는 배포가 진행되는 동안 AnalysisRun이 오류율과 지연 시간도 함께 확인합니다.
기본적인 흐름은 단순히
5% → 10분 대기 → 100%
가 아니라,
파드 준비 → 5% 트래픽 전환 → 메트릭 확인 → 다음 단계 진행
에 가깝습니다.
서비스별 설정이 기본값을 덮어쓰는 방식
한 가지 주의할 점은 canary.steps가 리스트라는 점입니다.
Helm values를 여러 파일에서 합칠 때 map 값은 병합할 수 있지만 리스트는 뒤에서 선언한 값으로 통째로 교체됩니다.
따라서 서비스가 자체적으로 canary.steps를 정의하면 공통 차트의 step에 추가되는 것이 아니라 전체 step이 서비스 설정으로 교체됩니다.
예를 들어 공통값이
steps:
- setWeight: 5
- pause:
duration: 10m
- setWeight: 100이어도 서비스에서
steps:
- setWeight: 100을 선언하면 실제 Rollout에는 setWeight: 100만 남습니다.
따라서 같은 Rollout 차트를 사용하더라도 서비스마다 실제 배포 방식은 달라질 수 있습니다.
일부 워크로드는 단계적으로 Canary를 진행하지만, 일부는 처음부터 100%로 전환하거나 Analysis를 비활성화해 일반적인 롤링 배포에 가까운 형태로 사용할 수도 있습니다.
이 차이가 중요한 이유는 뒤에서 다룰 rollbackWindow, minPodsPerReplicaSet, 마지막 setWeight: 100의 영향이 모든 서비스에서 동일하지 않기 때문입니다.
이제부터는 기본적인 Canary 설정을 기준으로 정상 Rollout이 어떤 순서로 진행되는지 따라가 보겠습니다.
3. 정상 Canary의 진행 순서
reconcile과 상태 반영
Rollouts Controller는 Rollout의 현재 상태를 읽고, 필요한 변경 사항을 계산한 뒤 다시 상태를 기록하는 과정을 반복합니다.
한 번의 reconcile에서 배포 전체를 끝내는 것이 아니라,
상태 확인 → 필요한 변경 → 상태 기록 → 다음 reconcile
을 반복하면서 배포를 진행합니다.
Canary 배포에서는 이 과정에서 크게 세 가지를 결정합니다.
- 현재 step에서 요구하는 traffic weight
- stable/canary ReplicaSet의 목표 파드 수
- 현재 상태에서 다음 step으로 진행할 수 있는지
중요한 점은 step이 변경됐다고 모든 상태가 동시에 바뀌는 것은 아니라는 것입니다.
currentStepIndex, ReplicaSet 크기, traffic weight는 Controller가 상태를 반복해서 조정하면서 서로 다른 시점에 반영될 수 있습니다.
정상 배포에서는 이 차이가 문제가 되지 않습니다.
새로운 traffic weight를 적용하기 전에 필요한 파드가 준비됐는지 확인하는 장치가 있기 때문입니다.
traffic weight와 Pod 수
현재 구성에서 배포 흐름을 이해하는 데 필요한 step은 주로 두 가지입니다.
| step | 역할 |
|---|---|
setWeight | canary에 보낼 트래픽 비율을 변경 |
pause | 다음 step으로 진행하기 전에 일정 시간 대기 |
예를 들어 다음 설정은
steps:
- setWeight: 5
- pause:
duration: 10m
- setWeight: 100다음과 같이 읽을 수 있습니다.
5% 트래픽 전환 → 10분 관찰 → 100% 전환
traffic routing을 사용하는 Rollout에서는 traffic weight와 ReplicaSet의 파드 수를 별도로 계산합니다.
따라서 새 버전에 트래픽을 5%만 보내면서도 canary 파드는 2대 이상 떠 있을 수 있습니다.
전체 replica가 20대라고 가정해 보겠습니다.
setWeight: 5라면 단순 계산으로는
20 × 5% = 1 Pod이지만 minPodsPerReplicaSet: 2가 설정되어 있다면 실제 canary ReplicaSet은 최소 2대까지 올라갑니다.
이 상태에서 트래픽의 5%를 canary로 전달하며 일정 시간 상태를 관찰합니다.
문제가 없으면 마지막 setWeight: 100으로 넘어갑니다.
여기서 중요한 변화가 발생합니다.
100%의 트래픽을 받기 위해서는 새 ReplicaSet이 전체 replica 수인 20대까지 준비되어야 합니다.
따라서 새 ReplicaSet의 목표는 20대로 올라갑니다.
하지만 아직 20대가 모두 준비되지 않았다면 트래픽은 즉시 100%로 바뀌지 않습니다.
새 RS 목표: 20
새 RS Available: 2 → 5 → 12 → 20
Traffic:
5% ────────────────────┐
└─ 100%20대가 준비될 때까지 기존 5% weight를 유지하고, 필요한 파드가 모두 준비된 뒤에야 다음 weight를 적용합니다.
이 때문에 정상적인 순차 배포에서는 새 버전의 처리 용량보다 먼저 트래픽이 증가하지 않습니다.
새 weight 적용 전 가용성 확인
예를 들어 step이 다음과 같다고 하겠습니다.
0: setWeight 5
1: pause 10m
2: setWeight 100currentStepIndex: 2가 됐다는 것은 마지막 setWeight: 100 단계에 도달했다는 뜻입니다.
하지만 step index가 2가 됐다고 트래픽까지 즉시 100%가 되는 것은 아닙니다.
Controller는 새 ReplicaSet이 현재 step에서 요구하는 트래픽을 받을 준비가 됐는지 함께 확인합니다.
아직 준비가 끝나지 않았다면 새로운 weight를 적용하지 않고 이전 weight를 계속 사용합니다.
currentStepIndex = 2
desired weight = 100%
새 RS 준비 전
→ 실제 traffic weight = 이전 값인 5%
새 RS 준비 완료
→ 실제 traffic weight = 100%이 안전장치의 핵심은 단순합니다.
새 ReplicaSet이 새로운 weight를 받을 준비가 되지 않았다면 이전 weight를 유지한다.
즉 내부적으로 반드시
파드를 먼저 준비 → 그다음 트래픽 변경
이라는 두 개의 독립된 절차가 있는 것은 아닙니다.
정확히는 새 파드가 준비되지 않았다면 새로운 weight를 적용하지 않는 방식입니다.
정상적인 배포에서는 두 표현이 같은 결과를 만듭니다.
하지만 이 차이는 step index가 평소와 다른 방식으로 이동하는 롤백에서 중요해집니다.
4. 마지막 step과 승격
마지막 setWeight: 100
한 가지 알아둘 점이 있습니다.
Argo Rollouts는 모든 step을 지나 현재 step이 더 이상 없으면 최종 weight를 100으로 계산합니다.
따라서 다음과 같이 마지막 setWeight: 100이 없어도
steps:
- setWeight: 5
- pause:
duration: 10m마지막 step을 모두 통과하면 최종적으로 canary는 100%까지 진행할 수 있습니다.
즉,
- setWeight: 100은 최종적으로 트래픽을 100%로 만들기 위해 반드시 필요한 step은 아닙니다.
정상 배포에서는 이 step이 존재해도 앞에서 설명한 가용성 확인이 동작하기 때문에 문제가 드러나지 않습니다.
하지만 특정 롤백 경로에서는 step index가 일반적인 순서를 거치지 않으면서 이 마지막 weight가 다른 의미를 갖게 됩니다.
이 부분이 2부의 핵심입니다.
promotion
카나리 배포가 모든 단계를 통과하고 새 ReplicaSet 전체가 준비되면 새 버전을 stable로 확정합니다.
이 과정을 promotion이라고 합니다.
기존 stable RS
↓
새 ReplicaSet → stablepromotion이 완료되면 새 ReplicaSet이 기준 버전이 되고 이전 ReplicaSet은 이후 축소됩니다.
여기서 중요한 것은 promotion 자체가 트래픽 안전성을 보장하는 장치는 아니라는 점입니다.
promotion에 도달하기 전에 이미 새 ReplicaSet의 준비 상태와 traffic weight를 조정하는 과정이 진행됩니다.
정상 배포에서는 결과적으로
파드 준비 → traffic 100% → promotion
순서가 만들어집니다.
뒤에서 다룰 롤백 문제 역시 promotion 이후가 아니라 promotion에 도달하기 전 traffic weight를 계산하는 구간에서 발생합니다.
5. AnalysisRun이 배포를 멈추는 방법
AnalysisTemplate과 AnalysisRun
AnalysisTemplate은 어떤 메트릭을 어떤 기준으로 판정할지 정의하는 틀이고, AnalysisRun은 실제 배포 중 그 템플릿을 실행하는 인스턴스입니다.
Canary 배포에서는 AnalysisRun을 특정 step에서만 실행할 수도 있고 Rollout 전체에 걸쳐 background로 실행할 수도 있습니다.
우리 구성에서는 background analysis를 사용합니다.
따라서 특정 analysis step에 들어갔을 때만 동작하는 것이 아니라 새 리비전의 Rollout이 시작되면 AnalysisRun이 만들어지고 배포가 진행되는 동안 계속 메트릭을 확인합니다.
다음과 같은 step이 있다면
steps:
- setWeight: 5
- pause:
duration: 10m
- setWeight: 100pause: 10m 동안에도 AnalysisRun은 계속 동작합니다.
즉 이 10분은 단순한 대기 시간이 아니라 실제 트래픽을 일부 흘리면서 오류율과 지연 시간을 관찰하는 구간입니다.
측정 기준
현재 구성에서는 주로 두 지표를 사용합니다.
| 지표 | 기본 기준 |
|---|---|
| 오류율 | 0.001 이상이면 실패 측정 |
| 지연 시간 | 0.3s 이상이면 실패 측정 |
메트릭은 일정 간격으로 반복해서 측정됩니다.
한 번 기준을 넘었다고 바로 Rollout 전체가 실패하는 것은 아니며 설정된 failureLimit을 초과하면 AnalysisRun을 실패로 판정합니다.
예를 들어
interval: 20s
failureLimit: 2라면 20초마다 측정하고 실패 측정이 누적 세 번 발생하면 AnalysisRun이 Failed 상태가 됩니다.
여기서 failureLimit은 연속 실패 횟수가 아니라 누적 실패 횟수입니다.
중간에 정상 측정이 있더라도 실패 횟수는 누적됩니다.
현재 분석 범위
현재 Prometheus 기반 오류율과 latency 쿼리에는 한 가지 중요한 특징이 있습니다.
stable과 canary를 각각 분리해서 측정하지 않고 서비스 전체의 메트릭을 기준으로 판단합니다.
예를 들어
stable 95%
canary 5%상태에서 측정되는 값은 canary 5%만의 오류율이 아니라 두 버전의 요청이 합쳐진 서비스 전체 오류율입니다.
stable 95% ─┐
├─ 서비스 전체 오류율 → AnalysisRun
canary 5% ─┘따라서 AnalysisRun이 존재한다고 해서 자동으로 새 버전만을 검증한다고 볼 수는 없습니다.
Canary에서만 오류율이 크게 증가해도 전체 트래픽에서 차지하는 비중이 작으면 서비스 전체 수치에서는 상대적으로 작게 보일 수 있습니다.
AnalysisRun이 실제로 무엇을 검증하는지는 결국 어떤 메트릭과 어떤 라벨을 기준으로 쿼리하느냐에 따라 결정됩니다.
이 특성은 2부에서 롤백과 AnalysisRun의 관계를 살펴볼 때 다시 중요해집니다.
실패와 abort
AnalysisRun이 실패하면 Rollout은 현재 배포를 더 진행하지 않고 abort 상태로 전환합니다.
정상 진행 중에는
stable 95%
canary 5%처럼 트래픽이 나뉘어 있을 수 있지만, abort가 발생하면 canary로 보내던 트래픽을 중단하고 stable 쪽으로 되돌립니다.
AnalysisRun Failed
↓
Rollout Abort
↓
canary traffic → 0%
stable로 복귀기존 stable ReplicaSet이 유지되고 있는 일반적인 Canary 구성이라면 새 버전의 이상을 감지했을 때 기존 버전으로 빠르게 트래픽을 되돌릴 수 있습니다.
정리하면 AnalysisRun의 역할은 다음과 같습니다.
메트릭 반복 측정 → 실패 여부 판정 → 실패 시 Rollout 중단 및 stable 복귀
앞에서 살펴본 step이 배포를 어떻게 진행할 것인지를 정한다면, AnalysisRun은 그 배포를 계속 진행해도 되는지를 판단하는 안전장치입니다.
6. 다음 편으로 넘기는 것
정상적인 Canary 배포에서는 크게 세 가지 장치가 함께 작동합니다.
-
step을 순서대로 진행한다
- 이전 weight는 현재보다 낮은 값이 된다.
-
새 ReplicaSet이 준비되기 전에는 새로운 weight를 적용하지 않는다
- 처리 가능한 용량보다 트래픽이 먼저 증가하지 않는다.
-
AnalysisRun이 배포 상태를 감시한다
- 메트릭이 기준을 벗어나면 현재 배포를 중단하고 stable로 복귀한다.
정상 경로에서는 이 세 가지가 서로 맞물리면서 안전하게 동작합니다.
문제는 롤백입니다.
우리가 사용한 rollbackWindow는 빠른 롤백을 위해 정상적인 step 진행 과정의 일부를 건너뜁니다.
그 과정에서 정상 배포에서는 안전장치였던 조건들이 예상과 다른 방식으로 작동하기 시작합니다.
특히 두 가지가 중요합니다.
- 새 AnalysisRun이 장애 당시의 높은 오류율을 다시 읽어 롤백 자체를 abort할 수 있다.
- step index가 끝으로 이동하면서 Controller가 유지하려는 “이전 weight”의 의미가 달라질 수 있다.
2026-08-21의 사고는 이 조건들이 겹치면서 발생했습니다.
다음 편에서는 rollbackWindow가 정상적인 Canary 흐름을 어떻게 건너뛰고, 왜 그 과정에서 트래픽 가중치가 예상하지 못한 방향으로 움직였는지 코드와 실제 사고 흐름을 따라가 봅니다.