
배포 자동화는 다들 잘 해놨는데, 정작 "이 배포 잘 된 거 맞아?"를 판단하는 건 사람이 대시보드 보고 있다. 우리도 그랬다. ArgoCD가 알아서 sync까지는 해주는데, P99가 튀거나 에러율이 올라가면 누군가는 새벽에 깨서 롤백을 해야 했다.
올해 초 Argo Rollouts을 본격적으로 도입했고, AnalysisTemplate으로 Prometheus 메트릭을 보고 자동 롤백까지 시키는 데까지 왔다. 이 글은 그동안 정리해둔 셋업 노트다. 처음 도입하는 팀이 보면 30분 안에 동작하는 canary는 만들 수 있게 썼다.
왜 Rollout인가 (Deployment로는 안 되나)
솔직히 Deployment + RollingUpdate로도 canary 비슷한 흉내는 낼 수 있다. 그런데 두 가지가 안 된다. 하나는 트래픽 비중을 정밀하게 조정하는 것 — maxSurge로는 10%, 25%, 50%처럼 단계별로 천천히 올릴 수가 없다. 다른 하나는 메트릭 기반 자동 판단이다. Deployment는 "Pod가 Ready인가"만 본다. P99가 800ms를 찍고 있어도 모른다.
Argo Rollouts는 이 둘을 다 해결한다. CRD가 Rollout이라는 점만 빼면 Deployment랑 거의 비슷하게 생겼다.
가장 단순한 canary 셋업
먼저 동작하는 최소 구성부터 보자. 컨트롤러는 헬름으로 설치한다.
helm repo add argo https://argoproj.github.io/argo-helm
helm install argo-rollouts argo/argo-rollouts -n argo-rollouts --create-namespace
Rollout 매니페스트는 이렇게 생겼다.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: web-api
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 10m }
- setWeight: 60
- pause: { duration: 10m }
selector:
matchLabels:
app: web-api
template:
metadata:
labels:
app: web-api
spec:
containers:
- name: web-api
image: registry.local/web-api:v1.4.2
ports:
- containerPort: 8080
이 상태에서 이미지 태그만 바꿔 kubectl apply하면, 새 버전 Pod가 10% 비중으로 먼저 뜨고 5분 대기 → 30%로 늘고 10분 대기 → 이런 식으로 흘러간다. 트래픽 분할은 기본적으로 Pod 개수 비율로 한다. Replica가 10개니까 10%면 새 버전 1개, 구버전 9개다.
여기서 끝나면 그냥 단계가 있는 RollingUpdate랑 크게 다를 게 없다. 핵심은 다음 단계다.
AnalysisTemplate으로 메트릭 기반 자동 판단
pause 자리에 사람이 보고 판단하는 대신, 메트릭을 보고 자동으로 판단하게 만든다. AnalysisTemplate을 먼저 만든다.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: web-api-success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.99
failureLimit: 1
provider:
prometheus:
address: http://prometheus.monitoring.svc:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
version="canary",
status!~"5.."
}[2m]))
/
sum(rate(http_requests_total{
service="{{args.service-name}}",
version="canary"
}[2m]))
count: 5 + interval: 1m이면 1분 간격으로 5번 측정한다. successCondition이 5번 중 한 번이라도 깨지면 (failureLimit: 1) 분석은 실패로 처리된다. 그 시점에 Rollout이 자동으로 중단되고 stable 버전으로 트래픽이 돌아간다.
Rollout 매니페스트에 이걸 묶는다.
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 2m }
- analysis:
templates:
- templateName: web-api-success-rate
args:
- name: service-name
value: web-api
- setWeight: 30
- analysis:
templates:
- templateName: web-api-success-rate
args:
- name: service-name
value: web-api
- setWeight: 60
이러면 각 단계마다 5분 동안 성공률을 보다가, 99% 밑으로 떨어지면 자동 abort 된다. P99 latency, error budget burn rate도 같은 방식으로 추가하면 된다. 우리 팀은 success-rate + P99 + custom business metric(주문 성공률) 세 개를 묶어서 본다.
실무에서 걸리는 것들
처음에 셋업하고 한 달쯤 운영하면서 걸린 것들을 정리해둔다.
카나리 메트릭 라벨링. Prometheus 쿼리에서 canary와 stable을 구분하려면 Pod에 라벨이 박혀 있어야 한다. Rollouts가 자동으로 rollouts-pod-template-hash 라벨을 박아주긴 하지만, 애플리케이션 메트릭에는 자기들이 보고 있는 라벨이 따로 있는 경우가 많다. 우리는 Rollout의 canaryMetadata.labels로 version=canary를 박고, ServiceMonitor의 relabel 설정에서 이걸 메트릭 라벨로 끌어올렸다. 이거 빼먹으면 canary와 stable 메트릭이 섞여서 분석이 무의미해진다.
트래픽 분할 정밀도. Pod 개수 비율로만 분할하면 replicas: 4에서 setWeight: 10은 의미가 없다 (반올림하면 0이거나 1). 진짜 10% 트래픽을 보내려면 서비스 메시(Istio, Linkerd) 또는 Ingress(NGINX, ALB)와 연동해서 weight를 명시적으로 분할해야 한다. 우리는 Istio VirtualService와 묶어서 쓴다.
strategy:
canary:
canaryService: web-api-canary
stableService: web-api-stable
trafficRouting:
istio:
virtualService:
name: web-api
routes: [primary]
이 설정이 있으면 Rollouts가 알아서 VirtualService의 weight를 갱신해준다. Pod 수와 무관해진다.
PDB와의 상호작용. canary 단계에서 새 버전이 죽으면 stable 버전 Pod도 같이 evict 되는 게 아니냐는 질문을 받은 적이 있는데, PDB는 selector 기준으로 동작하니까 app: web-api로만 묶여 있으면 canary와 stable이 같은 PDB를 공유한다. 보통 이게 맞다. 다만 minAvailable을 너무 빡빡하게 잡으면 카나리 단계에서 stable 쪽 Pod를 줄이지 못해서 weight가 안 올라가는 경우가 있다. 한 번 데어봤다.
abort 후 다음 배포. 분석 실패로 abort 되면 Rollout은 Degraded 상태가 된다. 이 상태에서 새 이미지로 다시 apply 해도 곧바로 다음 시도가 들어가진 않는다. kubectl argo rollouts retry rollout web-api로 명시적으로 retry 시키거나, 아예 새 revision으로 가야 한다. CI/CD 파이프라인에서 이 케이스를 처리해두지 않으면 사람이 직접 retry 누르는 일이 생긴다.
다음 단계로 가려면
여기까지가 우리가 한 달 정도 운영하면서 안정화한 셋업이다. 이다음으로는 두 가지를 더 보고 있다. 하나는 ArgoCon 발표에서 봤던 "비교 분석" — canary와 stable의 메트릭을 절대값이 아니라 차이로 보는 방식이다. P99가 200ms든 500ms든 상관없이 stable 대비 30% 이상 늘면 abort. 트래픽 패턴이 시간대마다 다른 서비스에서는 절대값 임계치보다 이게 더 안정적이다. 다른 하나는 experiment 리소스로 다크 런칭하는 거다. 실 트래픽은 안 받고 섀도우 트래픽만 보내서 비교하는 패턴.
이 글에서는 거기까지는 안 다뤘다. 일단 동작하는 canary부터 만들고, 한두 달 운영하면서 메트릭 임계치를 튜닝하는 게 먼저다. 도입 첫 주에는 임계치가 너무 빡빡해서 정상 배포도 자꾸 abort 되는 일이 생기는데, 그건 정상이다.
'IT > CI CD' 카테고리의 다른 글
| ArgoCD 3.0 마이그레이션, 우리가 부쉈던 것들 (0) | 2026.05.25 |
|---|---|
| Reusable Workflow vs Composite Action, 1년 같이 굴려본 결론 (0) | 2026.05.19 |
| ArgoCD app-of-apps에서 sync-wave 잘못 짜서 새벽에 깬 이야기 (0) | 2026.05.17 |
| Renovate vs Dependabot vs ArgoCD Image Updater — 1년 굴려본 솔직 비교 (0) | 2026.05.15 |
| 왜 우리 팀은 Argo Rollouts를 선택했나 — Flagger와 1년 비교 후기 (0) | 2026.05.11 |