ArgoCD ApplicationSet rollingSync + Argo Rollouts로 멀티 클러스터 카나리 배포 구현하기
수십 개 클러스터에 안전하게 점진적 롤아웃을 적용하는 2중 게이팅 전략
대상 독자: ArgoCD를 어느 정도 써본 분들. Kubernetes 기본 개념(Deployment, Service, Ingress)은 안다고 가정합니다.
솔직히 말하면, 처음 멀티 클러스터 배포를 맡았을 때 가장 두려웠던 건 "한 번에 터지면 어떡하지?"였습니다. 클러스터가 5개든 50개든, 새 버전을 동시에 밀어넣는 순간 뭔가 잘못되면 전부 영향을 받는다는 공포감이 있었거든요. 그 고민의 끝에서 만난 조합이 ArgoCD ApplicationSet의 rollingSync와 Argo Rollouts의 카나리 스텝입니다.
이 두 도구는 서로 다른 레이어에서 "점진적으로"라는 원칙을 구현합니다. ApplicationSet이 "어느 클러스터부터 배포할지"를 제어한다면, Argo Rollouts는 "그 클러스터 안에서 트래픽을 얼마나 흘릴지"를 제어합니다. 이 2중 게이팅 구조가 핵심입니다. 이 글을 다 읽으면, Matrix Generator로 수십 개 클러스터의 Application을 자동 생성하고 rollingSync로 배포 순서를 제어하며, 각 클러스터 내에서 Argo Rollouts가 Prometheus 메트릭 기반으로 카나리를 자동 평가하는 전체 아키텍처를 직접 구성할 수 있습니다.
핵심 개념
두 도구가 각각 무엇을 맡는가
먼저 역할 분리를 명확히 해두는 게 중요합니다. 저도 처음엔 이 경계가 흐릿해서 설정을 여기저기 중복으로 넣곤 했거든요.
| 레이어 | 담당 도구 | 역할 |
|---|---|---|
| 클러스터 간 (Cluster-level) | ApplicationSet rollingSync |
클러스터 A → B → C 순으로 단계적 롤아웃 |
| 클러스터 내 (Pod-level) | Argo Rollouts Rollout |
각 클러스터 안에서 카나리 트래픽 가중치 단계 제어 |
ArgoCD ApplicationSet은 단일 템플릿 하나로 수십~수백 개의 ArgoCD Application을 자동 생성하는 확장 리소스입니다. 클러스터 목록, Git 디렉터리, 행렬 조합 등 다양한 Generator를 통해 멀티 클러스터·멀티 테넌트 환경을 선언형으로 자동화할 수 있습니다.
Argo Rollouts는 기존 Deployment 리소스를 Rollout 리소스로 대체해 카나리(Canary), 블루-그린(Blue-Green) 같은 고급 배포 전략을 제공하는 Kubernetes CRD 기반 컨트롤러입니다. 트래픽 가중치 단계(step) 및 메트릭 기반 자동 프로모션/롤백을 지원합니다.
Progressive Sync란? ApplicationSet의
rollingSync전략을 사용하면, 생성된 Application 전체를 한꺼번에 동기화하지 않고 레이블 매칭 그룹 단위로 단계별로 동기화합니다. 이전 단계의 모든 Application이Healthy상태가 되어야 다음 단계로 넘어갑니다.
ApplicationSet rollingSync의 구조
rollingSync의 핵심은 matchExpressions로 Application에 붙은 레이블을 기준으로 단계를 나누는 것입니다.
# ApplicationSet rollingSync 핵심 구조
spec:
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: env
operator: In
values: ["canary"] # 1단계: 카나리 클러스터
maxUpdate: 1
- matchExpressions:
- key: env
operator: In
values: ["staging"] # 2단계: 스테이징 클러스터들
maxUpdate: 25%
- matchExpressions:
- key: env
operator: In
values: ["production"] # 3단계: 프로덕션 전체operator: In은 Application의 레이블 값이 values 목록에 포함될 때 해당 단계로 분류하겠다는 의미입니다. 직관적이지 않아서 처음 보면 헷갈릴 수 있는데, "레이블 env가 canary 값에 해당하면 이 단계로"라고 읽으면 됩니다.
maxUpdate는 해당 단계에서 동시에 업데이트할 최대 Application 수(또는 비율)를 제한합니다. 스테이징 클러스터가 20개라면 25%로 설정하면 한 번에 5개씩만 업데이트됩니다.
Argo Rollouts의 카나리 스텝과 메트릭 분석
클러스터 내부에서는 Argo Rollouts가 트래픽을 단계적으로 이동시킵니다. AnalysisTemplate을 끼워 넣으면 Prometheus 같은 메트릭 공급자에 쿼리를 보내 성공률, 오류율, 레이턴시를 평가하고, 임계값 초과 시 자동으로 롤백합니다.
# Rollout 카나리 스텝 예시
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-service
spec:
strategy:
canary:
trafficRouting:
istio:
virtualService:
name: my-service-vsvc
steps:
- setWeight: 10 # 트래픽 10% → 카나리
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-service # AnalysisTemplate의 {{args.service-name}}에 주입
- pause: {duration: 10m}
- setWeight: 30
- pause: {duration: 10m}
- setWeight: 100참고: 위 예시는 Istio가 이미 클러스터에 구성된 환경을 가정합니다. NGINX Ingress나 Kubernetes Gateway API를 사용하는 경우
trafficRouting설정 방식이 달라집니다. 자세한 내용은 Argo Rollouts 트래픽 관리 공식 문서를 참고해보시면 좋습니다.
실전 적용
세 예시는 서로 연결된 레이어입니다. 예시 1이 ApplicationSet으로 멀티 클러스터 Application을 자동 생성하는 기반이고, 예시 2가 그 위에 얹히는 클러스터 내 품질 평가 레이어이며, 예시 3은 클러스터 수가 많아질 때 메트릭을 중앙 집중화하는 확장 구성입니다.
전체 파일 구조는 이렇습니다.
my-gitops-repo/
├── applicationsets/
│ └── my-service-appset.yaml # 예시 1: ApplicationSet (rollingSync 포함)
└── k8s/
├── base/
│ └── analysis-template.yaml # 예시 2: AnalysisTemplate 정의
└── overlays/
├── canary/
│ └── rollout.yaml # 예시 2: Rollout (AnalysisTemplate 참조)
└── production/
└── rollout.yaml예시 1: Matrix Generator로 클러스터×환경 조합 자동 생성
ApplicationSet Matrix Generator는 클러스터 목록과 환경 목록을 곱해서 모든 조합의 Application을 자동으로 만들어냅니다. 수동으로 Application YAML을 하나씩 작성하던 시절을 생각하면 정말 편해진 거죠. 여기서 핵심은 template.labels.env 값이 나중에 rollingSync 단계 매칭의 기준이 된다는 점입니다.
# applicationsets/my-service-appset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-service-appset
namespace: argocd
spec:
generators:
- matrix:
generators:
- clusters:
selector:
matchLabels:
region: ap-northeast-2 # 특정 리전 클러스터만 대상으로 필터링
- list:
elements:
- env: canary
- env: production
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: env
operator: In
values: ["canary"]
maxUpdate: 1 # 카나리 클러스터 1개씩만
- matchExpressions:
- key: env
operator: In
values: ["production"] # 이후 프로덕션 전체 진행
template:
metadata:
name: "{{name}}-{{env}}"
labels:
env: "{{env}}" # rollingSync matchExpressions와 매칭되는 핵심 레이블
cluster: "{{name}}"
spec:
project: default
source:
repoURL: https://github.com/my-org/my-service
targetRevision: HEAD
path: "k8s/overlays/{{env}}"
destination:
server: "{{server}}"
namespace: my-service
syncPolicy:
syncOptions:
- CreateNamespace=true| 필드 | 설명 |
|---|---|
matrix.generators |
클러스터 목록과 환경 목록을 곱해 모든 조합을 생성 |
clusters.selector |
특정 레이블을 가진 클러스터만 대상으로 필터링 |
rollingSync.steps |
canary 환경을 먼저 배포하고, 이후 production 순으로 진행 |
template.labels.env |
Generator에서 주입된 값으로 rollingSync matchExpressions와 매칭됨 |
template.labels.env가 빠지면 어떤 단계에도 속하지 않는 Application이 생겨 rollingSync 자체가 동작하지 않습니다. 이 레이블 연결이 전체 구조의 접착제 역할을 합니다.
예시 2: AnalysisTemplate으로 자동 품질 평가
카나리 트래픽을 흘린 뒤 Prometheus에서 성공률을 쿼리해 기준 미달이면 자동 롤백하는 구성입니다. 이 부분이 빠지면 사실 반쪽짜리 카나리 배포입니다. AnalysisTemplate과 Rollout은 args로 연결되는데, 이 연결이 누락되면 분석이 아예 실행되지 않습니다.
먼저 AnalysisTemplate을 정의합니다.
# k8s/base/analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name # Rollout 쪽에서 값을 주입받을 인수 선언
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95 # 성공률 95% 이상
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring.svc.cluster.local:9090
query: |
sum(rate(http_requests_total{
job="{{args.service-name}}",
status!~"5.."
}[2m]))
/
sum(rate(http_requests_total{
job="{{args.service-name}}"
}[2m]))그리고 Rollout에서 이 템플릿을 참조할 때 args로 서비스 이름을 명시적으로 전달합니다.
# k8s/overlays/canary/rollout.yaml (분석 스텝 부분)
steps:
- setWeight: 10
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-service # AnalysisTemplate의 {{args.service-name}}에 주입
- pause: {duration: 10m}
- setWeight: 30
- pause: {duration: 10m}
- setWeight: 100저도 처음엔 왜 분석이 실행되지 않는지 몰라 한참 헤맸는데, args를 빠뜨리면 Prometheus 쿼리에서 {{args.service-name}}이 빈 문자열로 치환됩니다. 오류도 조용히 나거나 아예 안 나는 경우가 있어서, 증상이 눈에 잘 안 띕니다. Rollout과 AnalysisTemplate 양쪽에 args 정의가 모두 있어야 한다는 점을 꼭 확인해두시면 좋습니다.
예시 3: Federated Prometheus로 멀티 클러스터 분석 중앙화
클러스터가 많아지면 각 클러스터마다 메트릭을 따로 보는 게 번거롭습니다. 이 섹션은 Prometheus federation 아키텍처에 익숙한 분들을 위한 확장 구성입니다.
ClusterAnalysisTemplate이란? 일반
AnalysisTemplate이 특정 네임스페이스에 종속된 분석 리소스라면,ClusterAnalysisTemplate은 클러스터 전체에서 공유되는 분석 리소스입니다. 관리 클러스터에 하나만 정의해두고 여러 Rollout에서 참조할 수 있어, 멀티 클러스터 환경의 중앙 집중 평가에 적합합니다.
관리 클러스터에 Federated Prometheus를 두고 모든 워크로드 클러스터의 메트릭을 중앙 집중적으로 수집하면, ClusterAnalysisTemplate 하나로 일괄 평가가 가능합니다.
# 관리 클러스터 Prometheus ConfigMap의 scrape_configs 항목
# Helm으로 설치 중이라면 values.yaml의 server.extraScrapeConfigs에 추가
scrape_configs:
- job_name: 'federate'
scrape_interval: 15s
honor_labels: true # 원본 클러스터 레이블 보존
metrics_path: '/federate'
params:
match[]:
- '{job="my-service"}'
- '{__name__=~"http_requests_total|http_request_duration_seconds.*"}'
static_configs:
- targets:
- 'prometheus.cluster-a.internal:9090'
- 'prometheus.cluster-b.internal:9090'
- 'prometheus.cluster-c.internal:9090'honor_labels: true는 워크로드 클러스터의 원본 레이블(job, instance 등)을 그대로 보존해, 연합된 메트릭에서 어느 클러스터 출처인지 구분할 수 있게 합니다.
이 구성을 갖추면 카나리 배포 중 어느 클러스터에서든 오류가 터졌을 때 관리 클러스터의 단일 Prometheus 쿼리로 감지하고 해당 Application의 롤아웃을 중단할 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Blast Radius 최소화 | 클러스터 단위 + Pod 단위 2중 게이팅으로 장애 전파 범위를 최소화 |
| 자동화된 롤백 | 메트릭 임계값 초과 시 자동 롤백, Argo Rollouts 1.8 기준 2초 이내 복구 |
| GitOps 단일 진실 원천 | Git에서 모든 배포 상태를 선언형으로 관리하며 감사 추적이 용이 |
| 독립 장애 격리 | 한 클러스터 배포 실패가 다른 클러스터에 영향을 주지 않음 |
Blast Radius: 장애나 배포 실패가 영향을 미치는 범위를 뜻합니다. 카나리 배포의 핵심 목표 중 하나가 이 범위를 최소화하는 것입니다.
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| AutoSync 강제 비활성화 | rollingSync 사용 시 모든 Application의 자동 동기화가 강제로 꺼짐 |
CI/CD 파이프라인에서 argocd appset sync 명시적 트리거로 대체 |
| 레이스 컨디션 버그 | 원격 클러스터 간 스텝 순서가 보장되지 않는 케이스 보고 (Issue #22852) | 최신 ArgoCD 버전 패치 여부 확인 후 적용 |
| 롤아웃 정체(Stall) | Application이 Healthy에 도달 못하면 전체 롤아웃이 멈춤 |
타임아웃 설정 및 수동 개입 절차 사전 수립 |
| 대규모 클러스터 부하 | 수백 개 Application의 Watch 스트림 동시 처리가 병목 가능 | ArgoCD 컨트롤러 샤딩 및 리소스 제한 튜닝 |
| 클러스터별 독립 설치 | Argo Rollouts는 각 클러스터에 별도 설치 필요, 중앙 관리 불가 | ApplicationSet으로 Rollouts 자체도 배포 자동화 가능 |
| 트래픽 관리 도구 의존 | 정밀한 가중치 제어를 위해 Istio, NGINX, Gateway API 중 하나 필수 | 이미 도입된 인프라에 맞는 통합 방식 선택 |
실무에서 가장 흔한 실수
-
rollingSync를 켰는데 AutoSync가 예상대로 작동하길 기대하는 경우 —rollingSync전략을 사용하면 해당 ApplicationSet이 생성한 모든 Application에 AutoSync가 강제로 꺼집니다. 저도 처음엔 왜 배포가 안 되는지 몰라 한참 헤맸는데, 이게 의도된 동작입니다. CI 파이프라인에서argocd appset sync명령으로 명시적으로 트리거해야 합니다. -
AnalysisTemplate에
args를 넘기지 않는 경우 — Rollout에서analysis.args를 생략하면 AnalysisTemplate의 Prometheus 쿼리에서{{args.service-name}}이 빈 문자열이 됩니다. 분석이 조용히 실패하거나 잘못된 메트릭을 평가하게 되어 카나리 자동 롤백이 작동하지 않습니다. -
Argo Rollouts를 설치하지 않은 클러스터에
Rollout리소스를 배포하는 경우 — CRD가 없으면Rollout리소스 자체가 무시되거나 오류가 납니다. ApplicationSet으로 Argo Rollouts 자체를 먼저 설치하는 Application을 선행 단계로 배치해두면 이 문제를 예방할 수 있습니다. -
트래픽 관리 도구 없이 카나리 가중치를 기대하는 경우 — Argo Rollouts가
setWeight: 10을 선언해도, Istio나 NGINX Ingress 같은 트래픽 관리 도구가 없으면 실제 트래픽 분산은 일어나지 않습니다. 기본 쿠버네티스 서비스 레벨에서는 파드 수 비율로만 대략적으로 조절될 뿐입니다.
마치며
ArgoCD ApplicationSet의 rollingSync와 Argo Rollouts의 카나리 스텝을 조합하면, 클러스터 레벨과 파드 레벨 두 겹의 점진성으로 멀티 클러스터 배포의 위험을 실질적으로 낮출 수 있습니다.
처음 이 구조를 구축할 때는 도구가 4개(ArgoCD + ApplicationSet + Argo Rollouts + 서비스 메시)나 얽혀 있어서 어디서부터 시작해야 할지 막막하게 느껴질 수 있습니다. 복잡도를 단계적으로 쌓아가는 접근이 가장 현실적입니다.
지금 바로 시작해볼 수 있는 3단계:
-
단일 클러스터에 Argo Rollouts를 먼저 설치하고 카나리 스텝을 검증해볼 수 있습니다. 아래 명령어로 설치한 뒤, 기존
Deployment를Rollout으로 변환하는 방법은 공식 마이그레이션 가이드에서 확인할 수 있습니다.kubectl argo rollouts get rollout my-service --watch로 단계가 진행되는 모습을 직접 확인해보시면 구조가 눈에 들어옵니다.bashkubectl create namespace argo-rollouts kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml -
ApplicationSet Matrix Generator로 소규모 환경(예: dev + staging 2개 클러스터)에
rollingSync를 적용해보시면 좋습니다.maxUpdate: 1로 시작해 한 번에 한 클러스터씩 배포되는 흐름을 관찰할 수 있습니다. 이 단계에서 AutoSync 비활성화 동작을 직접 경험해두면 나중에 당황하지 않습니다. -
Prometheus
AnalysisTemplate을 추가해 메트릭 기반 자동 평가까지 연결해보시면 좋습니다. 처음엔successCondition을 넉넉하게(예:>= 0.80) 잡아두고 실제 메트릭 흐름을 보면서 임계값을 조금씩 높여가는 방식이 현실적입니다. 단순 메트릭 임계값으로 시작해 안정화된 이후에 SLO 기반 자동 프로모션으로 발전시키는 경로가 가장 무난합니다.
참고 자료
- Argo Rollouts 공식 문서 | argo-rollouts.readthedocs.io
- ArgoCD ApplicationSet Progressive Syncs 공식 문서 | argo-cd.readthedocs.io
- Canary Deployment Strategy | Argo Rollouts 공식
- Analysis & Progressive Delivery | Argo Rollouts 공식
- Skyscanner/applicationset-progressive-sync | GitHub
- How to automate multi-cluster deployments using Argo CD | Red Hat Developer
- Canary delivery with Argo Rollouts and Amazon VPC Lattice for EKS | AWS Blog
- Argo Rollouts Canary Monitoring with Last9 | last9.io
- RollingSync race condition issue | argoproj/argo-cd #22852