Zone-Aware Argo Rollouts
지원 버전: Istio 1.18+, Argo Rollouts 1.6+ 마지막 업데이트: 2026년 2월 19일 난이도: ⭐⭐⭐⭐⭐ (고급)
이 문서는 AWS 가용 영역(Availability Zone)별로 독립적인 Argo Rollouts Canary 배포를 설정하면서, Istio의 locality-aware 라우팅을 활용하여 자동 failover를 구현하는 방법을 설명합니다.
목차
문제 정의
실제 사용 사례: Spot Instance 환경에서의 PDB 관리
배경: AWS Spot Instance를 사용하는 환경에서는 특정 가용 영역(Zone)의 모든 노드가 갑자기 중단될 수 있습니다.
문제 시나리오:
왜 Zone별 Rollout이 필요한가?
- Rollout당 독립적인 PDB 관리
- 각 Zone의 Rollout이 자체 PDB를 관리
- Zone C가 완전히 사라져도 Zone A, B의 PDB는 영향 없음
- Zone 단위 복구
- Zone C가 복구되면 해당 Rollout만 재시작
- 다른 Zone의 배포 상태에 영향 없음
- Spot Instance 중단 대응
- 특정 Zone의 Spot Instance가 모두 중단되어도
- 다른 Zone의 서비스는 계속 운영
- Istio locality failover로 자동 트래픽 전환
PDB 설정 예시 (Zone별):
# Zone A - PDB (Rollout별 독립)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-a-pdb
namespace: default
spec:
minAvailable: 1 # Zone A에서 최소 1개
selector:
matchLabels:
app: test
zone: a
---
# Zone B - PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-b-pdb
namespace: default
spec:
minAvailable: 1 # Zone B에서 최소 1개
selector:
matchLabels:
app: test
zone: b
---
# Zone C - PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-c-pdb
namespace: default
spec:
minAvailable: 1 # Zone C에서 최소 1개
selector:
matchLabels:
app: test
zone: c장점:
- Zone C 전체 중단 시에도 Zone A, B의 PDB는 정상 동작
- 각 Zone이 독립적으로 복구 가능
- Canary 배포도 Zone별로 독립적으로 진행
요구사항
- Zone별 독립 배포: 3개의 가용 영역(a, b, c)에 각각 독립적인 Canary 배포
- Zone 격리: 각 zone의 트래픽은 기본적으로 해당 zone 내에서만 처리
- Failover 전용: 장애 발생 시에만 다른 zone으로 트래픽 전환 (a→b, b→c, c→a)
- 통합 호출: 클라이언트는 단일 서비스 이름으로 호출
- Spot Instance 대응: Zone 단위 중단에도 서비스 연속성 보장
일반적인 문제
문제: 여러 Argo Rollouts가 같은 VirtualService를 참조하면 충돌 발생
# ❌ 잘못된 접근: 모든 Rollouts가 같은 route 수정 시도
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-a
spec:
strategy:
canary:
trafficRouting:
istio:
virtualService:
name: test # 모든 zone의 Rollout이 같은 VirtualService 참조
routes:
- primary # 같은 route를 동시에 수정 시도 → 충돌!해결책: Zone별 별도 route를 사용한 분리
중요: Argo Rollouts는 지정된 route 이름의 전체 destinations 배열을 관리합니다. 따라서 여러 Rollout이 같은 route 이름을 참조하면, 각 Rollout이 서로의 설정을 덮어쓰게 됩니다. subset을 다르게 설정해도 충돌이 발생합니다.
아키텍처 개요
전체 구조
핵심 컴포넌트
- 단일 VirtualService: 모든 zone의 트래픽 라우팅 규칙 정의
- Zone별 Rollout: 각 zone에서 독립적인 Canary 배포 관리
- Subset 기반 분리: 각 Rollout은 고유한 subset 쌍 관리 (stable-a/canary-a 등)
- Locality-aware DestinationRule: 자동 zone-local 라우팅 및 failover
핵심 설계 결정
1. 단일 VirtualService + Zone별 Route 분리
왜 이 방식이 필요한가?
Argo Rollouts는 지정된 route 이름의 전체 destinations 배열을 덮어쓰는 방식으로 동작합니다. 따라서 각 Zone의 Rollout이 독립적인 route 이름을 관리해야 충돌이 발생하지 않습니다:
# VirtualService: 단일 VirtualService에 Zone별 route 정의
http:
- name: zone-a-route # Rollout A가 이 route의 stable-a/canary-a 관리
match:
- sourceLabels:
topology.istio.io/zone: us-east-1a
route:
- destination: {host: test, subset: stable-a}
weight: 90
- destination: {host: test, subset: canary-a}
weight: 10
- name: zone-b-route # Rollout B가 이 route의 stable-b/canary-b 관리
match:
- sourceLabels:
topology.istio.io/zone: us-east-1b
route:
- destination: {host: test, subset: stable-b}
weight: 90
- destination: {host: test, subset: canary-b}
weight: 10핵심 원리:
- 각 Rollout은 서로 다른 route 이름을 참조 (
zone-a-route,zone-b-route,zone-c-route) - 각 route는 sourceLabels match를 통해 해당 Zone의 트래픽만 처리
- Locality-aware 라우팅이 자동으로 zone-local 엔드포인트를 우선 선택
2. Locality-aware 라우팅
기본 동작:
- Zone A의 클라이언트 → Zone A의 Pod (100%)
- Zone B의 클라이언트 → Zone B의 Pod (100%)
- Zone C의 클라이언트 → Zone C의 Pod (100%)
Failover 시:
- Zone A 장애 → Zone B로 자동 전환
- Zone B 장애 → Zone C로 자동 전환
- Zone C 장애 → Zone A로 자동 전환
3. 통합 서비스 호출
클라이언트는 단일 DNS 이름 사용:
# 이렇게 호출
curl http://test.default.svc.cluster.local:8080
# Istio가 자동으로 zone-local 엔드포인트로 라우팅구현 가이드
1. 공통 Service 생성
중요: selector에 zone 레이블을 포함하지 마세요 (모든 zone의 Pod 선택)
apiVersion: v1
kind: Service
metadata:
name: test
namespace: default
spec:
selector:
app: test # zone 레이블 없음 - 모든 zone의 Pod 선택
ports:
- name: http
port: 8080
targetPort: 80802. Zone별 Rollout Service
각 Rollout이 관리하는 stable/canary Service:
# Zone A - Stable Service
apiVersion: v1
kind: Service
metadata:
name: test-stable-a
namespace: default
spec:
selector:
app: test
zone: a # Zone A의 stable Pod만 선택
ports:
- name: http
port: 8080
targetPort: 8080
---
# Zone A - Canary Service
apiVersion: v1
kind: Service
metadata:
name: test-canary-a
namespace: default
spec:
selector:
app: test
zone: a # Zone A의 canary Pod만 선택
ports:
- name: http
port: 8080
targetPort: 8080
---
# Zone B - Stable Service
apiVersion: v1
kind: Service
metadata:
name: test-stable-b
namespace: default
spec:
selector:
app: test
zone: b
ports:
- name: http
port: 8080
targetPort: 8080
---
# Zone B - Canary Service
apiVersion: v1
kind: Service
metadata:
name: test-canary-b
namespace: default
spec:
selector:
app: test
zone: b
ports:
- name: http
port: 8080
targetPort: 8080
---
# Zone C - Stable Service
apiVersion: v1
kind: Service
metadata:
name: test-stable-c
namespace: default
spec:
selector:
app: test
zone: c
ports:
- name: http
port: 8080
targetPort: 8080
---
# Zone C - Canary Service
apiVersion: v1
kind: Service
metadata:
name: test-canary-c
namespace: default
spec:
selector:
app: test
zone: c
ports:
- name: http
port: 8080
targetPort: 80803. Zone별 Route가 있는 단일 VirtualService
모든 zone의 트래픽을 처리하는 단일 VirtualService (Zone별 route 분리):
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: test
namespace: default
spec:
hosts:
- test
- test.default.svc.cluster.local
http:
# Zone A route (Rollout A가 관리)
- name: zone-a-route
match:
- sourceLabels:
topology.kubernetes.io/zone: us-east-1a
route:
- destination:
host: test
subset: stable-a
weight: 90
- destination:
host: test
subset: canary-a
weight: 10
# Zone B route (Rollout B가 관리)
- name: zone-b-route
match:
- sourceLabels:
topology.kubernetes.io/zone: us-east-1b
route:
- destination:
host: test
subset: stable-b
weight: 90
- destination:
host: test
subset: canary-b
weight: 10
# Zone C route (Rollout C가 관리)
- name: zone-c-route
match:
- sourceLabels:
topology.kubernetes.io/zone: us-east-1c
route:
- destination:
host: test
subset: stable-c
weight: 90
- destination:
host: test
subset: canary-c
weight: 10중요 변경사항:
- ❌ 이전: 모든 Zone이 같은
primaryroute 공유 → 충돌 발생 - ✅ 수정: 각 Zone이 독립적인 route 이름 사용 (
zone-a-route,zone-b-route,zone-c-route) - ✅ 추가:
sourceLabels.topology.kubernetes.io/zonematch로 Zone별 트래픽 분리
동작 방식:
- Zone A의 파드에서 발생한 요청 →
zone-a-route매칭 - Rollout A는
zone-a-route의 weight만 수정 (다른 Zone 영향 없음) - Locality-aware 라우팅이 자동으로 zone-local 엔드포인트 우선 선택
4. DestinationRule with Locality Settings
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: test
namespace: default
spec:
host: test
trafficPolicy:
loadBalancer:
localityLbSetting:
enabled: true
# 각 zone은 기본적으로 로컬 트래픽만 처리
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 100 # Zone A → Zone A (100%)
- from: us-east-1/us-east-1b/*
to:
"us-east-1/us-east-1b/*": 100 # Zone B → Zone B (100%)
- from: us-east-1/us-east-1c/*
to:
"us-east-1/us-east-1c/*": 100 # Zone C → Zone C (100%)
# Failover 설정: a→b, b→c, c→a
failover:
- from: us-east-1/us-east-1a
to: us-east-1/us-east-1b # Zone A 장애 시 Zone B로
- from: us-east-1/us-east-1b
to: us-east-1/us-east-1c # Zone B 장애 시 Zone C로
- from: us-east-1/us-east-1c
to: us-east-1/us-east-1a # Zone C 장애 시 Zone A로
# 빠른 장애 감지를 위한 Outlier Detection
outlierDetection:
consecutiveErrors: 3 # 3번 연속 실패 시
interval: 10s # 10초마다 확인
baseEjectionTime: 30s # 30초간 제외
maxEjectionPercent: 100 # 최대 100% 제외 가능
# 각 zone별 stable/canary subset 정의
subsets:
# Zone A subsets
- name: stable-a
labels:
app: test
zone: a
- name: canary-a
labels:
app: test
zone: a
# Zone B subsets
- name: stable-b
labels:
app: test
zone: b
- name: canary-b
labels:
app: test
zone: b
# Zone C subsets
- name: stable-c
labels:
app: test
zone: c
- name: canary-c
labels:
app: test
zone: c5. Zone별 Rollout 설정
Zone A Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-a
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: test
zone: a
template:
metadata:
labels:
app: test
zone: a
spec:
# Zone A에만 Pod 배포
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
env:
- name: ZONE
value: "a"
strategy:
canary:
# Zone A 전용 Service
canaryService: test-canary-a
stableService: test-stable-a
trafficRouting:
istio:
virtualService:
name: test # 공통 VirtualService
routes:
- zone-a-route # Zone A 전용 route
destinationRule:
name: test # 공통 DestinationRule
canarySubsetName: canary-a # Zone A 전용 subset
stableSubsetName: stable-a # Zone A 전용 subset
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 80
- pause: {duration: 5m}Zone B Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-b
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: test
zone: b
template:
metadata:
labels:
app: test
zone: b
spec:
# Zone B에만 Pod 배포
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1b
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
env:
- name: ZONE
value: "b"
strategy:
canary:
# Zone B 전용 Service
canaryService: test-canary-b
stableService: test-stable-b
trafficRouting:
istio:
virtualService:
name: test # 공통 VirtualService
routes:
- zone-b-route # Zone B 전용 route
destinationRule:
name: test # 공통 DestinationRule
canarySubsetName: canary-b # Zone B 전용 subset
stableSubsetName: stable-b # Zone B 전용 subset
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 80
- pause: {duration: 5m}Zone C Rollout
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-c
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: test
zone: c
template:
metadata:
labels:
app: test
zone: c
spec:
# Zone C에만 Pod 배포
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1c
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
env:
- name: ZONE
value: "c"
strategy:
canary:
# Zone C 전용 Service
canaryService: test-canary-c
stableService: test-stable-c
trafficRouting:
istio:
virtualService:
name: test # 공통 VirtualService
routes:
- zone-c-route # Zone C 전용 route
destinationRule:
name: test # 공통 DestinationRule
canarySubsetName: canary-c # Zone C 전용 subset
stableSubsetName: stable-c # Zone C 전용 subset
steps:
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 20
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 80
- pause: {duration: 5m}트래픽 흐름
정상 상태 (Zone-local 트래픽)
Failover 시나리오
Canary 배포 중 트래픽 흐름
문제 해결
1. VirtualService 충돌 오류
증상:
Error: VirtualService update conflict원인: 여러 Rollout이 같은 route를 동시에 수정 시도
해결:
# ✅ 각 Rollout이 고유한 subset 관리하도록 설정
spec:
strategy:
canary:
trafficRouting:
istio:
destinationRule:
canarySubsetName: canary-a # Zone별로 다른 subset
stableSubsetName: stable-a2. Cross-zone 트래픽 발생
증상: Failover가 아닌데도 다른 zone으로 트래픽 전송
원인: distribute 설정이 잘못됨
해결:
# ✅ 올바른 distribute 설정
distribute:
- from: us-east-1/us-east-1a/*
to:
"us-east-1/us-east-1a/*": 100 # 100% local만3. Failover가 작동하지 않음
증상: Zone 장애 시에도 다른 zone으로 failover되지 않음
원인: Outlier detection이 비활성화되어 있거나 설정이 너무 느림
해결:
# ✅ 빠른 장애 감지
outlierDetection:
consecutiveErrors: 3 # 3번만 실패해도 감지
interval: 10s # 10초마다 확인
baseEjectionTime: 30s # 30초간 제외4. Rollout이 멈춤
증상: Canary 배포가 진행되지 않음
확인:
# Rollout 상태 확인
kubectl argo rollouts get rollout test-a -n default
# VirtualService 가중치 확인
kubectl get virtualservice test -n default -o yaml | grep weight
# DestinationRule subset 확인
kubectl get destinationrule test -n default -o yaml5. 디버깅 명령어
# 1. Pod가 올바른 zone에 배포되었는지 확인
kubectl get pods -l app=test -o wide
kubectl get nodes --show-labels | grep topology.kubernetes.io/zone
# 2. Locality 라우팅 설정 확인
istioctl proxy-config endpoint <pod-name> --cluster "outbound|8080||test.default.svc.cluster.local"
# 3. VirtualService 동기화 확인
istioctl proxy-config route <pod-name> --name 8080
# 4. Outlier detection 상태 확인
kubectl exec <pod-name> -c istio-proxy -- curl localhost:15000/clusters | grep outlier
# 5. Argo Rollouts 로그 확인
kubectl logs -n argo-rollouts deployment/argo-rollouts모범 사례
1. Rollout 동기화
문제: 여러 zone의 Rollout을 동시에 배포하면 복잡도 증가
권장:
# Zone별 순차 배포
kubectl argo rollouts promote test-a -n default
# 5분 대기 및 모니터링
kubectl argo rollouts promote test-b -n default
# 5분 대기 및 모니터링
kubectl argo rollouts promote test-c -n default2. Canary 분석
각 zone별로 독립적인 분석 수행:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate-zone-a
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(
istio_requests_total{
destination_service="test.default.svc.cluster.local",
destination_workload_namespace="default",
response_code=~"2..",
destination_pod_label_zone="a"
}[5m]
)) /
sum(rate(
istio_requests_total{
destination_service="test.default.svc.cluster.local",
destination_workload_namespace="default",
destination_pod_label_zone="a"
}[5m]
))3. 점진적 Rollout 단계
steps:
- setWeight: 5 # 매우 작은 트래픽부터 시작
- pause: {duration: 5m}
- analysis:
templates:
- templateName: success-rate-zone-a
- setWeight: 10
- pause: {duration: 5m}
- setWeight: 25
- pause: {duration: 10m}
- setWeight: 50
- pause: {duration: 10m}
- setWeight: 75
- pause: {duration: 10m}4. 자동 롤백
spec:
strategy:
canary:
analysis:
templates:
- templateName: success-rate-zone-a
startingStep: 2 # 두 번째 단계부터 분석 시작
trafficRouting:
istio:
virtualService:
name: test
destinationRule:
name: test
canarySubsetName: canary-a
stableSubsetName: stable-a5. 모니터링 및 알림
Prometheus Alerts:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: zone-aware-rollout-alerts
spec:
groups:
- name: rollout
rules:
# Zone A Canary 실패율 높음
- alert: HighErrorRateZoneA
expr: |
sum(rate(istio_requests_total{
destination_service="test.default.svc.cluster.local",
response_code=~"5..",
destination_pod_label_zone="a"
}[5m])) /
sum(rate(istio_requests_total{
destination_service="test.default.svc.cluster.local",
destination_pod_label_zone="a"
}[5m])) > 0.05
for: 2m
annotations:
summary: "Zone A Canary has high error rate"
# Cross-zone 트래픽 발생 (예상치 못한)
- alert: UnexpectedCrossZoneTraffic
expr: |
sum(rate(istio_requests_total{
destination_service="test.default.svc.cluster.local",
source_workload_zone="a",
destination_pod_label_zone!="a"
}[5m])) > 0
for: 5m
annotations:
summary: "Unexpected cross-zone traffic from Zone A"6. 배포 체크리스트
- [ ] 모든 zone의 Node가 준비됨
- [ ] VirtualService가 모든 subset 포함
- [ ] DestinationRule의 locality 설정 확인
- [ ] Outlier detection 활성화
- [ ] 각 Rollout이 고유한 subset 관리
- [ ] Zone별 Service 정의됨
- [ ] Prometheus 메트릭 수집 확인
- [ ] 알림 규칙 설정됨
성능 고려사항
리소스 요구사항
Control Plane:
- Istiod: CPU 500m, Memory 2GB (추가 VirtualService/DestinationRule로 인한 부하)
Data Plane:
- Envoy Sidecar: CPU 100-500m, Memory 50-150MB (zone 정보 및 locality 라우팅 오버헤드)
Argo Rollouts Controller:
- CPU 100m, Memory 128MB (3개 Rollout 관리)
네트워크 오버헤드
- Zone-local 트래픽: 추가 latency 1-2ms (Envoy overhead)
- Cross-zone 트래픽 (failover 시): 추가 latency 5-10ms (zone 간 네트워크)
참고 자료
관련 문서
외부 링크
다음 단계
- Lab: Zone-aware Rollout 실습
- Multi-cluster로 확장하여 region 간 failover 구현
- Progressive Delivery로 자동화된 분석 및 롤백