Skip to Content
BlogCanary로 안전하게 배포하는 방법

Canary로 안전하게 배포하는 방법

#Argo Rollouts#GitOps#Istio#트러블슈팅

1부 — 원치 않았던 강제 롤백에서는 서비스 전체 오류율이 복구 배포까지 중단시켰습니다.

2부 — 왜 rollbackWindow는 안전하지 않았을까?에서는 분석을 건너뛰었더니, 준비된 파드 2대에 트래픽 100%가 몰렸습니다.

배포를 중단할 기준과 트래픽을 늘릴 조건을 각각 넣었지만, 복구할 때도 의도대로 동작하는지는 별도로 봐야 했습니다.

그럼 무엇을 바꿔야 할까요?

당시 v1.8.2 구성에서는 마지막 setWeight: 100을 제거하는 것부터 시작할 수 있습니다.

여기에 초기 파드가 받을 부하, Analysis가 측정하는 대상, 실제 롤백 동작까지 함께 확인하려고 합니다.

마지막 setWeight: 100을 제거한다

2부에서 컨트롤러는 step 끝에서 거꾸로 찾은 weight 100을, 아직 준비 중인 ReplicaSet에도 적용했습니다.

앞에 낮은 weight가 남도록 마지막 100을 지우면, 이때 선택하는 값도 낮아집니다.

공통 차트의 steps는 다음과 같이 바꿀 수 있습니다.

steps: - setWeight: 5 - pause: duration: 10m - - setWeight: 100

이렇게 바꾸면 배포가 5%에서 끝나지 않을까 싶지만, 컨트롤러는 남은 step이 없으면 최종 weight를 100으로 계산합니다. (v1.8.2의 최종 weight 계산 )

따라서 정상 배포에서는 전체 파드가 준비되면 트래픽을 100%로 늘리고 승격합니다.

반면 롤백 대상 파드가 아직 준비 중일 때는 step 끝에서 거꾸로 찾은 5를 적용합니다.

초기 파드에는 5%를 보내고, 전체 ReplicaSet이 준비되면 100%로 전환합니다.

결론 : 마지막 100을 지워도 정상 배포는 끝까지 진행되며, 이 롤백 경로에서 준비 중인 파드에 적용되는 weight는 5로 낮아집니다.

준비 중에는 장애 버전으로 트래픽이 남는다

사고 당시처럼 복구 버전이 2대만 준비됐다면 이 구간의 분배는 다음과 같습니다.

복구 버전 canary 5% 장애 버전 stable 95%

초기 파드에 전체 요청이 몰리는 상황은 피하지만, 나머지 파드가 준비될 때까지 요청의 95%는 장애 버전으로 갑니다.

승격하려면 기존 설정에서도 전체 ReplicaSet이 준비돼야 했습니다.

이 조건은 그대로이고, 파드가 준비되는 동안 트래픽을 보내는 비율이 달라집니다.

복구 중 오류가 즉시 사라지는 설정은 아니므로, 전체 파드를 확보하는 데 걸리는 시간도 함께 봐야 합니다.

남겨둔 weight를 초기 파드가 감당할 수 있을까

100을 지워도 남은 weight대로 보낸 요청을 초기 canary가 감당하지 못하면 과부하가 발생할 수 있습니다.

전체 replica 수를 N, 초기 가용 파드 수를 m, canary로 보낼 트래픽 비율을 w라고 하겠습니다(5%라면 w는 0.05입니다).

요청이 균등하게 분산된다면, 초기 canary 한 대가 받는 부하는 전체 N대에 나눠 보낼 때의 약 w × N / m배입니다.

이 값이 1이 되는 지점을 전체 replica 수로 정리하면 다음과 같습니다.

평소와 같은 파드당 부하가 되는 전체 replica 수 ≈ 초기 가용 파드 수 / 남는 트래픽 비율

minPodsPerReplicaSet: 2이고 실제로 2대가 준비된 상황의 계산입니다.

마지막으로 남는 weight평소와 같은 파드당 부하가 되는 전체 규모
1%약 200대
5%약 40대
10%약 20대
50%약 4대

이 값은 부하를 비교하기 위한 근사치입니다.

파드의 최대 처리량이나 기동 직후의 성능까지 보장하지는 않으므로, 실제 부하와 준비 상태를 함께 확인해야 합니다.

최대 100대까지 늘어나는 서비스라면 5%를 초기 2대에 보내도, 한 대가 평소의 약 2.5배를 받습니다.

이런 서비스는 남는 weight를 더 낮추거나, 트래픽을 보내기 전에 더 많은 파드가 실제로 가용한 상태인지 확인해야 합니다.

공통 차트만 바꾸면 끝날까

서비스가 steps를 선언하면 공통 차트의 리스트를 통째로 대체합니다.

따라서 자체 steps를 가진 서비스는 base와 별도로 수정해야 합니다.

기존 steps의 weight확인할 변경
[1, 5, 100], [5, 100]마지막 100 제거 후 남은 5의 용량 확인
[10, 50, 100]100을 지워도 50이 남으므로 앞 단계까지 조정
[100]이 하나도 역탐색 대상이므로 변경 필요
steps: []역탐색할 setWeight가 없어 이번 경로에는 직접 노출되지 않음

그래서 확인할 대상은 공통 차트의 기본값뿐 아니라, 서비스별로 최종 렌더된 manifest입니다.

CI에서도 마지막 100이 남았는지 검사할 수 있습니다.

다만 100이 없다는 검사만으로는 부족하고, 남은 비율이 서비스의 최대 규모와 초기 용량에 맞는지도 봐야 합니다.

파드 수를 늘리거나 다른 옵션을 쓰면 어떨까

같은 문제를 다른 설정으로 피할 수 있을지도 살펴봤습니다.

설정이번 경로에서의 한계
dynamicStableScalestable을 트래픽에 맞춰 줄이는 기능. 이번 역탐색 문제를 해결하는 보호 조건은 아니며, abort 시 stable을 다시 늘릴 수 있음
setCanaryScale정상 배포에서 미리 확장하는 데 쓰지만, step을 건너뛴 뒤에도 보호한다고 볼 수 없음
minPodsPerReplicaSet 인상목표 파드 수의 하한을 높임. 실제 가용 파드 수를 보장하지 않으며 평소 Canary 비용도 증가
step analysiswindow 안의 빠른 롤백에서는 분석 단계도 건너뜀

예를 들어 최소 파드를 전체 replica와 같은 20대로 잡으면, 처음부터 20대를 목표로 만들 수 있습니다.

하지만 minPodsPerReplicaSet은 목표 수의 하한이고, 그 파드들이 모두 Available해졌다는 뜻은 아닙니다.

2대만 준비된 순간에 마지막 weight 100을 선택하는 경로는 여전히 남습니다.

따라서 최소 파드 수만 높여서 이번 문제를 해결했다고 볼 수는 없습니다.

평소 트래픽 5%로 검증할 때도 20대를 실행해야 하는 비용까지 고려하면, 가용 파드 수와 weight를 함께 확인하는 편이 중요합니다.

최소 파드 수 2에도 배경이 있었다

2025-05에는 작은 weight에서도 단일 파드에 요청이 몰리지 않도록 base chart에 minPodsPerReplicaSet: 2를 추가했습니다.

그해 6월에는 rollbackWindow.revisions: 3을 추가했고, 8월에는 현재와 유사하게 traffic routing을 구성했습니다.

2025년 하반기부터 소규모 워크로드에는 최소 파드 수의 예외가 늘었습니다.

2026-07에 비프로덕션 replica를 줄인 뒤, 8월에는 0·1대 워크로드의 목표와 최소 파드 수가 충돌하는 문제가 드러났습니다.

위로 막힌다 — 승격이 수렴하지 않는다아래로 샌다 — 스케일이 멈추지 않는다CheckMinPodsPerReplicaSet두 조건절이 각각 한 방향desired 1KEDA min=max=1canary 2 + stable1하한 2 · 합계 3 vsdesired 1DegradedProgressDeadlineExceededdesired 0컨슈머 정지하한 1 > 목표 0count == 0 분기를 못 탄다파드 진동생성과 삭제 반복
전체 목표가 1대일 때는 최소 파드 수 2가 승격을 막을 수 있고, 목표가 0대일 때는 scale-to-zero와 충돌할 수 있습니다.

전체 목표가 1인데 canary 하한이 2이면 승격 조건에 도달하지 못할 수 있고, 0까지 줄이는 Consumer에서는 최소 파드 조건이 scale-to-zero와 충돌할 수 있습니다.

0대부터 100대 이상까지 규모가 다른 서비스에 기본값 하나를 적용하다 보니 예외가 늘었고, 이 값이 각 워크로드에 맞는지 CI에서 검증하지도 못했습니다.

이번 사고에서도 초기 파드 수는 2대로 정해졌는데, 전체 20대가 준비되기 전에 트래픽을 100% 보냈습니다.

그래서 최소 파드 수를 바꿀 때도 남는 weight와 서비스의 최대 replica 규모를 함께 봐야 합니다.

Analysis가 어느 버전의 오류를 보는지도 맞춘다

마지막 weight를 낮추는 변경은 2부에서 겪은 트래픽 집중 문제를 다룹니다.

1부에서 복구를 중단시켰던 서비스 전체 오류율 문제도 따로 남아 있습니다.

새 버전의 상태로 배포를 판단하려면, 메트릭에서 해당 리비전의 요청을 식별하고 Analysis 쿼리가 그 요청만 집계하는지 확인해야 합니다.

stable의 오류를 canary의 실패로 판정하거나, canary의 오류가 작은 트래픽 비중에 묻히는 일을 줄이기 위해서입니다.

당시처럼 두 버전을 구분하기 어려운 구성이라면, 서비스 전체 오류율을 사용한다는 제약을 런북에도 명시해야 합니다.

rollbackWindow로 분석을 생략하는 롤백에서는 무엇으로 복구 버전의 상태를 확인할지도 정해둬야 합니다.

단계를 건너뛴 만큼, 복구 대상과 가용 파드 수를 확인하는 운영 절차가 필요합니다.

장애 중에는 어떤 명령을 쓸까

수단역할과 주의점
retry실패한 Rollout을 다시 진행. 같은 Analysis 조건에서 서비스 오류율이 높으면 다시 abort될 수 있음
promote현재 pause를 진행. abort 자체를 해제하는 수단은 아님
promote --fullstep·Analysis를 건너뛰는 수동 승격. 2부에서 본 구성에서는 파드가 준비될 때까지 현재 weight를 유지
GitOps rollback / undo이전 pod template으로 복귀. rollbackWindow 조건에 따라 빠른 롤백 경로 진입

수동 승격을 고려할 때는 현재 배포 대상이 복구할 버전인지, 그 버전이 실제로 정상인지 먼저 확인해야 합니다.

이를 확인했고 남은 step과 분석을 생략하려는 경우에 promote --full을 사용할 수 있습니다.

분석을 생략하므로, 높은 오류율의 원인을 확인하지 않은 채 반복 실행하는 복구 명령으로 쓰면 안 됩니다.

retry는 실패한 배포를 다시 시도하므로, 같은 서비스 전체 오류율에 다시 중단될 수 있습니다.

또한 수동 조치 뒤에는 Git의 선언 상태도 맞춰야 합니다.

런북에도 이 후속 작업까지 포함해야 합니다.

정상 배포가 성공해도 롤백은 따로 검증한다

이 문제는 정상 배포만으로 드러나지 않습니다.

이전 revision으로 돌아가면서, 변경 전후 weight와 Available replica를 함께 봐야 합니다.

서비스 자체 steps를 확인하는 경우에는 관찰 시간을 줄여 다음 설정으로 비교할 수 있습니다.

# 변경 전 steps: - setWeight: 5 - pause: duration: 10s - setWeight: 100
# 변경 후 steps: - setWeight: 5 - pause: duration: 10s

window 범위 안에 남아 있는 같은 ReplicaSet을 대상으로, 일부 파드만 준비된 동안의 weight를 관찰합니다.

변경 전후의 목표 replica 수와 초기 파드 상태도 맞춰야 두 결과를 비교할 수 있습니다.

구간확인할 것
변경 전 롤백전체가 준비되기 전에 weight 100이 관측되는가
변경 후 롤백준비 중 weight 5를 유지하는가
변경 후 완료전체 ReplicaSet 준비 후 100%로 승격하는가

여기에 일반적인 새 버전 배포도 확인합니다.

마지막 100을 제거한 상태에서 관찰 시간이 끝나고, 전체 파드가 준비된 뒤 정상적으로 승격해야 합니다.

base를 상속하는 서비스는 설정 전파도 따로 확인합니다.

helm template으로 변경 전후 manifest를 비교하고, 상속 서비스 하나에 적용해 같은 롤백 동작을 관찰한 뒤 범위를 넓힙니다.

작은 stage 환경에서는 사고 당시의 20대 중 2대에 100%라는 피해 규모까지 재현하기 어려울 수 있습니다.

피해 규모를 재현하지 못하더라도 파드가 준비되기 전에 weight가 100%로 바뀌는지는 확인할 수 있습니다.

컨트롤러를 올릴 때도 같은 조건으로 확인한다

다음 그림은 2026-08-27에 확인한 v1.8.2의 보호 조건과 당시 업스트림 수정안입니다.

붙은 가드 넷 — 우리 v1.8.2 에 있는 것은 셋통과통과무관부재재발대기미가용 상태의 가중치canary 2대 · weight100%:217 Available 00대일 때만 막는다:266 가용량 게이트#3878 · stable 만 본다:229 동결#1683 · promote --full전용#4639 조기 리셋master 전용 · v1.8.2없음걸리는 가드 없음2 / 20 인데 100%#3941 재발2026-04 · v1.8.3PR #4852가용 replica 비례 캡 ·미머지
2026-08-27에 확인한 v1.8.2는 canary 0대, stable 용량, promote --full을 각각 검사했습니다. #4639는 당시 master에만 반영돼 있었고, canary 가용량에 맞춰 weight를 제한하는 #4852는 7월 24일 승인 후에도 병합되지 않았습니다.

당시 검토한 수정 방향은 canary의 실제 Available replica 비율에 맞춰 traffic weight를 제한하는 것이었습니다.

수정안의 현재 반영 여부는 사용하는 릴리스에서 다시 확인해야 합니다.

해당 수정이 실제 사용하는 컨트롤러 버전에 반영되면, 마지막 setWeight: 100을 제거한 우회 설정이 여전히 필요한지도 검토할 수 있습니다.

버전이 바뀌면 역탐색이나 step 처리 순서도 달라질 수 있으므로, 업그레이드마다 정상 배포·window 안의 롤백·abort와 promote --full 대응을 회귀 검증에 포함해야 합니다.

여러 클러스터의 컨트롤러 버전이 다르면 각각 확인해야 합니다.

정리하며

이번에 확인한 것은 배포와 복구가 같은 설정을 읽어도, 같은 순서로 진행되지는 않는다는 점입니다.

분석이 복구를 막기도 했고, 분석을 건너뛴 경로에서는 파드보다 트래픽이 먼저 넘어갔습니다.

결론 : Canary를 안전하게 운영하려면 분석할 요청의 범위, 준비된 파드의 용량, 그 파드에 보낼 트래픽을 맞추고 실제 롤백까지 확인해야 합니다.

마지막 setWeight: 100을 지우는 것은 그중 이번에 확인한 경로를 보완하는 변경입니다.

앞으로 설정이나 컨트롤러를 바꿀 때도, 배포가 끝까지 진행되는지만큼 되돌아오는 과정도 함께 확인하려고 합니다.