Skip to Content
Blog정상 Canary는 왜 안전한가: step과 AnalysisRun

정상 Canary는 왜 안전한가: step과 AnalysisRun

#Argo Rollouts#GitOps#Istio#메트릭

새 버전의 파드가 Ready라고 해서 서비스도 정상이라고 볼 수 있을까요?

Deployment의 롤링 업데이트는 파드 준비 상태를 확인하며 버전을 교체하지만, 오류율을 보고 배포를 멈추거나 새 버전에 트래픽을 5%만 보내는 기능은 제공하지 않습니다.

우리 환경에서는 Argo Rollouts와 Istio로 트래픽을 나누고, AnalysisRun으로 오류율과 지연 시간을 확인합니다.

파드 수와 트래픽 비율을 따로 다룬다

Rollout도 Deployment처럼 ReplicaSet으로 파드를 관리합니다.

배포 순서는 step으로 지정하고, stable과 canary에 보낼 트래픽은 Istio로 조절합니다.

Istio를 통해 설정 반영요청95%5%Rollout 컨트롤러VirtualService 가중치 ·DestinationRule subset설정서비스로 보내는 요청같은 서비스 주소로 접근Istio · Envoy반영된 라우팅 설정에 따라요청 분배기존 ReplicaSet의파드stable subset · 기존버전새 ReplicaSet의파드canary subset · 새 버전
Rollout 컨트롤러가 VirtualService의 가중치와 DestinationRule의 subset을 설정하면 Envoy가 각 ReplicaSet의 파드로 요청을 전달합니다. 점선은 설정 변경을, 실선은 요청 흐름을 나타냅니다. 95%와 5%는 각 버전으로 보내는 트래픽 비율입니다.

setWeight: 5를 지정하면 전체 트래픽의 5%를 새 버전으로 보냅니다.

파드 수와는 별개라 canary가 2대여도 트래픽은 5%만 받을 수 있습니다.

5%를 보내고, 10분을 본다

공통 차트의 기본 steps는 다음과 같습니다.

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

새 버전에 5%를 보낸 뒤 10분 동안 관찰하고, 문제가 없으면 100%로 늘립니다.

이때 background AnalysisRun이 배포 중 메트릭을 계속 확인합니다.

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

setWeight: 100 하나만 선언한 서비스는 5%로 관찰하는 단계와 10분 대기를 생략합니다.

아래 예시는 이 기본 steps에 전체 replica 20대, minPodsPerReplicaSet: 2를 적용했습니다.

파드 20대가 준비돼야 트래픽을 100%로 늘린다

처음 5%에 필요한 파드는 계산상 20 × 5% = 1대입니다.

하지만 최소 파드 수가 2이므로 canary는 2대로 시작합니다.

10분 뒤 마지막 setWeight: 100 단계로 넘어가면 컨트롤러는 새 ReplicaSet의 목표를 20대로 늘립니다.

그림의 ③~⑤처럼 step index가 2로 바뀌어도, 20대가 모두 준비되기 전까지는 트래픽을 5%만 보냅니다.

Ready 이후 minReadySeconds 동안 안정 상태를 유지해야 가용한 파드로 인정합니다.

컨트롤러가 적용하는 조건은 다음과 같습니다.

새 ReplicaSet이 현재 step에서 요구하는 만큼 준비되지 않았다면 이전 weight를 사용한다.

변경한 상태는 다음 reconcile에서 확인한다

컨트롤러는 현재 상태를 읽고 필요한 변경을 적용하는 reconcile을 반복합니다.

현재 상태 확인Rollout · ReplicaSettraffic weight결정현재 step이 요구하는 비율ReplicaSet 목표계산stable · canary 파드 수다음 step 판단진행 가능하면 인덱스를옮긴다상태 기록효과는 다음 바퀴에나타난다
컨트롤러는 weight와 ReplicaSet 목표를 계산한 뒤 step 진행 여부를 기록합니다. 다음 reconcile에서는 바뀐 index로 계산합니다(rollout/canary.go).

한 번의 reconcile에서 traffic weight와 ReplicaSet 목표를 계산한 뒤 step 진행 여부를 기록합니다.

바뀐 step index는 다음 reconcile에서 읽고, 파드의 가용성을 확인해 새 weight를 적용할지 결정합니다.

마지막 step과 승격

모든 step을 통과하고 새 ReplicaSet 전체가 준비되면 새 버전을 stable로 승격(promotion)합니다.

setWeight: 5index 0pause: 10mindex 1 · AnalysisRun측정남은 step 없음모든 step 통과최종 weight 100%승격setWeight: 100없어도 된다
모든 step을 통과하면 컨트롤러는 최종 weight를 100으로 계산합니다. 마지막 setWeight: 100을 명시하지 않아도 전체 파드가 준비되면 승격합니다.

step이 더 없을 때 컨트롤러는 최종 weight를 100으로 계산합니다.

따라서 마지막 setWeight: 100을 명시하지 않아도 정상 배포는 100%까지 진행됩니다.

AnalysisRun은 무엇을 보고 멈추는가

측정할 메트릭과 판정 기준을 AnalysisTemplate에 지정하면, 배포 중 생성되는 AnalysisRun이 이 설정에 따라 측정합니다.

우리 환경에서는 background analysis를 사용해 특정 step에 한정하지 않고 배포 중 계속 측정합니다.

오류율이 0.001 이상이거나 지연 시간이 0.3s 이상이면 해당 측정을 실패로 판정합니다.

alt[result.Failed <= failureLimit 2][else] Failed 가 3 이 되는 순간아래 2·3이 interval 20s 로 반복된다count 를 주지 않으면 Rollout 이 끝날 때까지4. failureCondition: result[0] >= 0.001참이면 result.Failed 를 1 올린다7. status.abort = trueGetCurrentSetWeight 가 Abort 를 먼저 보고 0 을 반환한다→ 가중치 0, stable RS 가 전량을 받는다1. 생성 — background, 리비전마다 하나2. errorRateQuery3. result 배열5. 20초 뒤 다시 측정6. phase = Failedrollout 컨트롤러AnalysisRunPrometheus
20초마다 측정하고, 실패가 누적 세 번이면 Rollout을 abort합니다. 중간에 성공한 측정이 있어도 실패 횟수는 줄지 않습니다.

interval: 20s마다 측정하고, 실패가 failureLimit: 2를 초과해 누적 세 번이 되면 AnalysisRun이 Failed가 됩니다.

중간에 성공한 측정이 있어도 실패 횟수는 줄지 않습니다.

컨트롤러는 Rollout을 abort하고, canary weight를 0으로 내려 stable로 트래픽을 되돌립니다.

새 버전만 측정하는 것은 아니다

현재 Prometheus 쿼리는 stable과 canary를 구분하지 않고 서비스 전체 메트릭을 봅니다.

95%5%stable트래픽 95%canary트래픽 5%서비스 전체 메트릭두 버전 요청을 합쳐 계산AnalysisRun오류율 · 지연 시간 판정
두 버전의 요청을 함께 집계하므로 AnalysisRun은 stable 트래픽까지 포함한 오류율로 판정합니다.

canary에서 오류가 늘어도 트래픽 비중이 5%라면 서비스 전체 오류율에는 작게 반영될 수 있습니다.

새 버전의 오류율을 따로 확인하려면 쿼리에서도 두 버전의 요청을 구분해야 합니다.

그런데 롤백이 step을 건너뛰면, 컨트롤러가 찾는 “이전 weight”는 무엇이 될까요?