- Published on
DevOps Progressive Delivery: Argo Rollouts 실전 운영
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Progressive Delivery의 핵심 전제
- Argo Rollouts 설치와 초기 설정
- 카나리 배포: 완전한 Rollout 매니페스트
- AnalysisTemplate: 자동 판단의 핵심
- 블루-그린 배포 설정
- 운영 명령어 레퍼런스
- 트러블슈팅: 실제 장애 시나리오
- 카나리 vs 블루-그린 선택 가이드
- AnalysisTemplate 재사용: 크로스 레퍼런스
- 퀴즈
- 참고 자료

Progressive Delivery의 핵심 전제
Progressive Delivery는 "배포와 릴리스를 분리한다"는 아이디어에서 출발한다. 코드를 프로덕션에 배포하되, 사용자에게 노출하는 트래픽 비율을 점진적으로 늘리면서 자동 분석으로 안전성을 검증한다. 문제가 감지되면 전체 롤아웃 전에 자동으로 중단한다.
Argo Rollouts는 Kubernetes 네이티브 Progressive Delivery 컨트롤러다. 표준 Deployment를 대체하는 Rollout CRD를 제공하며, 카나리(Canary)와 블루-그린(Blue-Green) 두 가지 전략을 지원한다. Argo Rollouts 1.7에서는 AnalysisRun에 TTL 전략과 크로스 레퍼런스 기능이 추가되어 대규모 운영이 한결 수월해졌다.
Argo Rollouts 설치와 초기 설정
# Argo Rollouts 컨트롤러 설치 (Helm)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argo-rollouts argo/argo-rollouts \
--namespace argo-rollouts \
--create-namespace \
--set controller.replicas=2 \
--set dashboard.enabled=true \
--set dashboard.service.type=ClusterIP \
--version 2.40.6
# kubectl 플러그인 설치
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
# 설치 확인
kubectl argo rollouts version
# argo-rollouts: v1.7.2
카나리 배포: 완전한 Rollout 매니페스트
기본 Rollout 리소스
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: order-service
namespace: production
labels:
app: order-service
team: commerce
spec:
replicas: 10
revisionHistoryLimit: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '8080'
prometheus.io/path: '/metrics'
spec:
containers:
- name: order-service
image: ghcr.io/my-org/order-service:v2.5.0
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
env:
- name: APP_ENV
value: production
- name: DD_SERVICE
value: order-service
strategy:
canary:
# 카나리와 안정 버전에 각각 다른 서비스 연결
canaryService: order-service-canary
stableService: order-service-stable
# Istio/NGINX 트래픽 라우팅 설정
trafficRouting:
nginx:
stableIngress: order-service-ingress
additionalIngressAnnotations:
canary-by-header: X-Canary
# 단계별 롤아웃
steps:
# 1단계: 5% 트래픽, 2분간 자동 분석
- setWeight: 5
- analysis:
templates:
- templateName: success-rate-and-latency
args:
- name: service-name
value: order-service
- name: canary-hash
valueFrom:
podTemplateHashValue: Latest
- pause:
duration: 2m
# 2단계: 20% 트래픽, 5분간 관찰
- setWeight: 20
- pause:
duration: 5m
# 3단계: 50% 트래픽, 수동 승인 (영업시간에만)
- setWeight: 50
- pause: {} # 수동 승인 필요
# 4단계: 전체 전환
- setWeight: 100
# 자동 롤백 조건
abortScaleDownDelaySeconds: 30
scaleDownDelaySeconds: 30
scaleDownDelayRevisionLimit: 2
카나리/스테이블 서비스 분리
# services.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service-stable
namespace: production
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service-canary
namespace: production
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
---
# NGINX Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-service-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/canary: 'true'
nginx.ingress.kubernetes.io/canary-weight: '0'
spec:
ingressClassName: nginx
rules:
- host: order.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service-stable
port:
number: 80
AnalysisTemplate: 자동 판단의 핵심
AnalysisTemplate은 카나리 배포의 성공/실패를 자동으로 판단하는 규칙이다. Prometheus, Datadog, New Relic 등 다양한 메트릭 소스를 지원한다.
Prometheus 기반 분석
# analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate-and-latency
namespace: production
spec:
args:
- name: service-name
- name: canary-hash
metrics:
# 지표 1: 성공률 (99% 이상이어야 통과)
- name: success-rate
interval: 30s
count: 10 # 총 10회 측정
failureLimit: 2 # 2회까지 실패 허용
successCondition: result[0] >= 0.99
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(
http_requests_total{
service="{{args.service-name}}",
pod_template_hash="{{args.canary-hash}}",
status=~"2.."
}[2m]
)) /
sum(rate(
http_requests_total{
service="{{args.service-name}}",
pod_template_hash="{{args.canary-hash}}"
}[2m]
))
# 지표 2: P95 지연시간 (300ms 이하여야 통과)
- name: latency-p95
interval: 30s
count: 10
failureLimit: 2
successCondition: result[0] <= 300
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
histogram_quantile(0.95,
sum(rate(
http_request_duration_milliseconds_bucket{
service="{{args.service-name}}",
pod_template_hash="{{args.canary-hash}}"
}[2m]
)) by (le)
)
# 지표 3: 에러 로그 비율 (0.5% 이하여야 통과)
- name: error-log-rate
interval: 60s
count: 5
failureLimit: 1
successCondition: result[0] <= 0.005
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(
log_messages_total{
service="{{args.service-name}}",
pod_template_hash="{{args.canary-hash}}",
level="error"
}[3m]
)) /
sum(rate(
log_messages_total{
service="{{args.service-name}}",
pod_template_hash="{{args.canary-hash}}"
}[3m]
))
Datadog 기반 분석
# analysis-datadog.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: datadog-analysis
namespace: production
spec:
args:
- name: service-name
- name: dd-api-key
valueFrom:
secretKeyRef:
name: datadog-credentials
key: api-key
- name: dd-app-key
valueFrom:
secretKeyRef:
name: datadog-credentials
key: app-key
metrics:
- name: error-rate
interval: 60s
count: 5
failureLimit: 1
successCondition: 'default(result, 0) < 0.01'
provider:
datadog:
apiVersion: v2
interval: 5m
query: |
sum:trace.http.request.errors{
service:{{args.service-name}},
version:canary
}.as_rate() /
sum:trace.http.request.hits{
service:{{args.service-name}},
version:canary
}.as_rate()
블루-그린 배포 설정
카나리가 점진적 트래픽 전환이라면, 블루-그린은 전체 트래픽을 한번에 전환하되, 이전 버전을 대기 상태로 유지하여 즉시 롤백을 가능하게 한다.
# blue-green-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: payment-service
namespace: production
spec:
replicas: 6
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
containers:
- name: payment-service
image: ghcr.io/my-org/payment-service:v3.1.0
ports:
- containerPort: 8080
strategy:
blueGreen:
activeService: payment-service-active
previewService: payment-service-preview
# 전환 전 자동 분석 실행
prePromotionAnalysis:
templates:
- templateName: success-rate-and-latency
args:
- name: service-name
value: payment-service
# 전환 후 안정성 분석
postPromotionAnalysis:
templates:
- templateName: success-rate-and-latency
args:
- name: service-name
value: payment-service
# 이전 버전 유지 시간 (롤백 대비)
scaleDownDelaySeconds: 600 # 10분간 이전 버전 유지
# 수동 승인 필요 여부
autoPromotionEnabled: false
운영 명령어 레퍼런스
# === 롤아웃 상태 확인 ===
kubectl argo rollouts get rollout order-service -n production
# 실시간 모니터링 (watch 모드)
kubectl argo rollouts get rollout order-service -n production -w
# === 수동 승인 (pause: {} 단계에서) ===
kubectl argo rollouts promote order-service -n production
# === 전체 승인 (나머지 단계 모두 건너뛰기) ===
kubectl argo rollouts promote order-service -n production --full
# === 롤백 (즉시 이전 버전으로) ===
kubectl argo rollouts abort order-service -n production
# === 재시도 (abort 후 같은 버전 재배포) ===
kubectl argo rollouts retry rollout order-service -n production
# === 특정 리비전으로 롤백 ===
kubectl argo rollouts undo order-service -n production --to-revision=3
# === AnalysisRun 결과 확인 ===
kubectl argo rollouts get rollout order-service -n production
# 또는
kubectl get analysisrun -n production -l rollouts-pod-template-hash=abc1234
# === 대시보드 접속 ===
kubectl argo rollouts dashboard -n argo-rollouts
# http://localhost:3100 에서 접속
트러블슈팅: 실제 장애 시나리오
시나리오 1: AnalysisRun이 Inconclusive 상태에서 멈춤
kubectl get analysisrun -n production
# NAME STATUS AGE
# order-service-abc1234-1 Inconclusive 15m
원인: Prometheus 쿼리가 데이터를 반환하지 않아 NaN이 되었다. 카나리 트래픽이 너무 적어 메트릭이 수집되지 않았다.
# 해결: successCondition에 default() 함수 사용
successCondition: 'default(result[0], 1) >= 0.99'
# NaN인 경우 1(100% 성공)로 간주
# 또는 최소 트래픽을 보장하는 설정
steps:
- setWeight: 5
- pause:
duration: 3m # 메트릭 수집을 위한 최소 대기
- analysis:
templates:
- templateName: success-rate-and-latency
시나리오 2: 롤백 후 ReplicaSet이 정리되지 않음
kubectl get rs -n production | grep order-service
# order-service-abc1234 10 10 10 2d
# order-service-def5678 0 0 0 5d # 오래된 RS
# order-service-ghi9012 0 0 0 12d # 더 오래된 RS
# 해결: revisionHistoryLimit 설정
spec:
revisionHistoryLimit: 3 # 최근 3개 리비전만 보관
# 수동 정리
kubectl argo rollouts rollout order-service -n production --revision 1
시나리오 3: 트래픽 라우팅이 실제로 작동하지 않음
NGINX Ingress Controller를 사용할 때 카나리 트래픽이 설정한 비율로 분배되지 않는 경우.
# Ingress annotation 확인
kubectl get ingress order-service-ingress -n production -o yaml
# NGINX Ingress Controller 로그 확인
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50
# 흔한 원인: Ingress에 canary annotation이 누락
# nginx.ingress.kubernetes.io/canary: "true" 가 있는지 확인
# 검증: 100회 요청하여 실제 분배 비율 확인
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}" https://order.example.com/health
done | sort | uniq -c
시나리오 4: 수동 승인(pause) 단계에서 장기간 방치
# 해결: pause에 타임아웃 추가
steps:
- setWeight: 50
- pause:
duration: 4h # 4시간 후 자동 승인
# 또는 Slack 알림으로 수동 승인 유도
# ArgoCD Notification과 연동
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
notifications.argoproj.io/subscribe.on-rollout-step-completed.slack: platform-alerts
카나리 vs 블루-그린 선택 가이드
| 기준 | 카나리 | 블루-그린 |
|---|---|---|
| 트래픽 제어 정밀도 | 높음 (1% 단위) | 없음 (전체 전환) |
| 롤백 속도 | 중간 (abort -> 리라우팅) | 매우 빠름 (서비스 전환) |
| 리소스 비용 | 낮음 (추가 Pod 최소) | 높음 (2배 Pod 필요) |
| 분석 정확도 | 높음 (실 트래픽 기반) | 제한적 (preview 환경) |
| 적합한 서비스 | 대부분의 웹 서비스 | 결제, 금융 등 무중단 필수 |
| 데이터 스키마 변경 | 복잡 (두 버전 공존) | 상대적으로 단순 |
| 구현 복잡도 | 높음 (트래픽 라우팅 필요) | 중간 |
일반 규칙: 대부분의 서비스에는 카나리를 사용하되, 결제나 인증처럼 한 건의 오류도 비용이 큰 서비스에는 블루-그린을 사용한다.
AnalysisTemplate 재사용: 크로스 레퍼런스
Argo Rollouts 1.7에서 도입된 AnalysisTemplate 크로스 레퍼런스 기능으로, 공통 메트릭을 ClusterAnalysisTemplate에 정의하고 서비스별로 참조할 수 있다.
# ClusterAnalysisTemplate: 조직 공통 기준
apiVersion: argoproj.io/v1alpha1
kind: ClusterAnalysisTemplate
metadata:
name: org-standard-analysis
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 30s
count: 10
failureLimit: 2
successCondition: 'default(result[0], 1) >= 0.99'
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",status=~"2.."}[2m]))
/ sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))
---
# 서비스별 추가 분석 (공통 + 커스텀)
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: order-service-analysis
namespace: production
spec:
args:
- name: service-name
metrics:
- name: order-completion-rate
interval: 60s
count: 5
failureLimit: 1
successCondition: result[0] >= 0.95
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(order_completed_total{service="{{args.service-name}}"}[5m]))
/ sum(rate(order_created_total{service="{{args.service-name}}"}[5m]))
퀴즈
Q1. Argo Rollouts의 Rollout CRD가 표준 Deployment를 대체하는 이유는?
정답: ||표준 Deployment는 RollingUpdate 전략만 지원하며, 트래픽 비율 제어와 메트릭 기반 자동 롤백
기능이 없다. Rollout CRD는 카나리/블루-그린 전략, AnalysisRun을 통한 자동 판단, 트래픽 라우팅
제어를 네이티브로 제공한다.||
Q2. AnalysisRun에서 Inconclusive 상태가 발생하는 가장 흔한 원인은?
정답: ||Prometheus 쿼리가 NaN을 반환하는 경우다. 카나리 트래픽이 너무 적어 메트릭이 수집되지
않거나, 쿼리의 label selector가 카나리 Pod과 일치하지 않을 때 발생한다. successCondition에
default() 함수를 사용하여 NaN 처리를 해야 한다.||
Q3. 카나리 배포에서 setWeight: 5와 analysis 단계의 순서가 중요한 이유는?
정답: ||setWeight가 먼저 실행되어 카나리 Pod에 5% 트래픽이 흐른 후에 analysis가 시작되어야 실제
트래픽 기반 메트릭을 수집할 수 있다. analysis를 먼저 실행하면 트래픽이 없어 메트릭이 비어있는
상태에서 판단하게 된다.||
Q4. 블루-그린 배포에서 scaleDownDelaySeconds를 600으로 설정하는 이유는?
정답: ||전환 후 10분간 이전 버전 Pod을 유지하여 문제 발생 시 즉시 롤백할 수 있게 한다. 이 시간이
지나면 이전 버전 Pod이 종료되므로, 롤백 시 새로 Pod을 생성해야 하여 시간이 더 걸린다. 서비스
중요도에 따라 적절한 값을 설정한다.||
Q5. NGINX Ingress Controller와 Argo Rollouts를 연동할 때 canary-by-header 설정의 용도는?
정답: ||특정 HTTP 헤더(X-Canary)를 포함한 요청을 카나리 Pod으로 라우팅한다. QA 팀이나 내부 테스터가 헤더를 추가하여 카나리 버전을 직접 테스트할 수 있게 해준다. 비율 기반 라우팅(setWeight)과 병행하여 사용한다.||
Q6. ClusterAnalysisTemplate과 AnalysisTemplate의 차이와 사용 전략은?
정답: ||ClusterAnalysisTemplate은 클러스터 전체에서 사용할 수 있는 네임스페이스 독립적인 분석
템플릿이다. 조직 공통 SLO 기준(성공률 99%, P95 지연 300ms 이하 등)을 ClusterAnalysisTemplate으로
정의하고, 서비스별 비즈니스 메트릭은 네임스페이스별 AnalysisTemplate으로 추가하는 것이 권장된다.||
Q7. abort 후 retry와 undo --to-revision의 차이는?
정답: ||retry는 abort된 동일 버전을 다시 롤아웃한다(코드 수정 없이 일시적 문제였을 때). undo
--to-revision은 특정 이전 리비전의 Pod 템플릿으로 되돌린다(코드에 문제가 있어 이전 버전으로 복귀할
때). retry는 같은 이미지를 재배포하고, undo는 이전 이미지로 전환한다.||