Rancher Fleet과 Argo Rollouts를 조합해 500개 Kubernetes 클러스터에 카나리 배포하기 — Blast Radius를 파티션 단위로 제한하는 Progressive Delivery
솔직히 말하면, 저도 처음 수십 개 클러스터를 동시에 관리해야 할 때 꽤 막막했습니다. 단일 클러스터에서 카나리 배포 하나 굴리는 건 어렵지 않은데, 클러스터가 수백 개로 늘어나는 순간 얘기가 달라집니다. "이 변경이 전체 프로덕션에 터지면 어떡하지?"라는 두려움이 배포를 망설이게 만들고, 결국 월 2회 배포도 버거워지고 핫픽스 때마다 모두가 숨죽이게 되더라고요.
이 글에서는 Rancher Fleet의 ClusterGroup으로 클러스터 간 Blast Radius를 제어하고, Argo Rollouts로 클러스터 내 트래픽을 점진적으로 이동하는 이중 방어선 패턴을 구체적인 YAML 예시와 함께 살펴봅니다. 두 기술을 조합하면 "3개 클러스터에서 검증 → 15개 staging → 50개 prod-wave-1 → 나머지 300개" 식의 릴리즈 웨이브를 Git 커밋 하나로 자동화할 수 있습니다. 이 글을 읽고 나면 파티션 단위 배포 게이팅을 선언하는 방법, 클러스터 내 자동 롤백을 메트릭 기반으로 구성하는 방법, 그리고 두 기술을 조합할 때 실제로 걸리는 함정들을 파악할 수 있습니다.
사전 조건: 이 글은 Kubernetes를 운영 중인 DevOps 또는 플랫폼 엔지니어를 대상으로 합니다. CRD, Ingress controller,
kubectl기본 사용, Prometheus 기초 개념이 전제됩니다. Kubernetes를 처음 접하는 분이라면 공식 문서에서 기본 개념을 먼저 살펴보시는 걸 권장합니다.
핵심 개념
Fleet이란 무엇이고, ClusterGroup은 왜 필요한가
Rancher Fleet은 SUSE가 만든 GitOps 엔진으로, 수백~수천 개의 Kubernetes 클러스터를 단일 Git 레포지토리에서 선언적으로 관리할 수 있게 해줍니다. "GitOps는 알겠는데, 클러스터가 500개면 ArgoCD Application도 500개 만들어야 하나?" 하는 의문에 대한 Fleet의 답이 바로 ClusterGroup과 GitRepo의 조합입니다.
ClusterGroup은 레이블 기반으로 클러스터를 논리 그룹으로 묶는 CRD입니다. env: canary 레이블이 붙은 클러스터 3개를 canary-clusters 그룹으로 묶어두면, 이후 배포 정책을 클러스터 이름이 아닌 그룹 이름으로 선언할 수 있습니다.
# ClusterGroup 예시
apiVersion: fleet.cattle.io/v1alpha1
kind: ClusterGroup
metadata:
name: canary-clusters
namespace: fleet-default
spec:
selector:
matchLabels:
env: canary클러스터에 레이블을 붙이는 방법은 여러 가지가 있습니다. Rancher UI의 클러스터 설정 화면에서 직접 레이블을 추가하거나, kubectl로 Cluster CRD에 직접 패치할 수 있습니다.
# Rancher Fleet의 Cluster CRD에 레이블 추가
kubectl label cluster my-cluster-01 env=canary wave-number=1 -n fleet-defaultFleet의 GitRepo 리소스에는 rolloutStrategy라는 필드가 있습니다. 여기서 파티션 단위로 배포 순서와 허용 실패 범위를 선언할 수 있고, 앞 파티션이 정상 완료되기 전에는 다음 파티션 배포가 차단됩니다. 클러스터 레벨의 배포 게이트를 Git으로 선언하는 셈입니다.
Blast Radius — 배포 실패나 버그가 발생했을 때 실제로 영향을 받는 클러스터 수, 혹은 사용자 비율을 가리키는 표현입니다. 이 수치를 의도적으로 최소화하는 전략이 Progressive Delivery의 핵심입니다.
Argo Rollouts는 단일 클러스터 내에서 무엇을 하는가
Argo Rollouts는 클러스터 안에 설치되는 컨트롤러로, Kubernetes 기본 Deployment 대신 Rollout CRD를 사용합니다. 카나리 전략을 선언하면 트래픽을 5% → 20% → 50% → 100% 식으로 단계별로 이동시키고, 각 단계 사이에 Prometheus나 Datadog 메트릭을 조회하는 AnalysisRun을 삽입할 수 있습니다.
# Rollout 카나리 스텝 개요
steps:
- setWeight: 5 # 트래픽 5%를 새 버전으로
- analysis: ... # 성공률·레이턴시 분석
- pause: {duration: 5m}
- setWeight: 20
- analysis: ...
- setWeight: 100AnalysisRun이 실패하면 Argo Rollouts는 트래픽을 자동으로 이전 버전으로 되돌립니다. 수동 개입 없이 롤백이 이루어지는 거죠. 이 자동화가 클러스터 내 Blast Radius를 제한하는 두 번째 방어선 역할을 합니다.
두 기술의 역할 분리: Fleet은 "어느 클러스터까지 배포할 것인가"를 제어하고, Argo Rollouts는 "그 클러스터 안에서 트래픽을 얼마나 이동시킬 것인가"를 제어합니다. 서로 다른 레이어를 담당하기 때문에 충돌 없이 조합됩니다.
두 기술이 만나는 지점: Git 커밋 하나에서 수백 개 클러스터까지
이 구조가 어떻게 맞물리는지 전체 흐름을 한 번에 보면 이해가 빠릅니다.
Git 커밋 푸시
└─ Fleet GitRepo 감지
├─ 파티션 1: canary-clusters (3개 클러스터)
│ └─ Argo Rollouts: 5% 트래픽 → AnalysisRun → 자동 프로모션
│ [파티션 1 Ready 확인 후 다음 파티션 진행]
├─ 파티션 2: staging (15개 클러스터)
│ └─ Argo Rollouts: 20% → 50% → 100%
│ [파티션 2 Ready 확인 후 다음 파티션 진행]
├─ 파티션 3: prod-wave-1 (50개 클러스터, us-east)
└─ 파티션 4: prod-wave-2 (나머지 300개 클러스터)maxUnavailablePartitions: 1로 설정해두면, 어느 파티션이라도 NotReady 상태가 되는 즉시 전체 롤아웃이 일시 중단됩니다. 카나리 3개 클러스터에서 문제가 터지면 staging 이후로는 배포가 나가지 않는 구조입니다.
여기서 한 가지 짚고 넘어가야 할 중요한 지점이 있습니다. Fleet이 파티션 완료를 판단하는 기준은 번들 내 리소스의 Ready 상태입니다. 표준 Deployment라면 Available 컨디션으로 명확히 판단되지만, Argo Rollouts의 Rollout CRD는 얘기가 다릅니다. Rollout은 카나리 스텝 진행 중에도 안정 버전 파드가 살아 있으면 Available: True를 보고할 수 있습니다. 즉, 카나리 분석이 아직 끝나지 않았는데 Fleet이 파티션을 Ready로 인식하고 다음 파티션 배포를 시작할 수 있다는 거죠.
저도 이 부분에서 처음 이 패턴을 적용할 때 꽤 고생했습니다. 이 문제에 대한 실용적인 대응 방법은 아래 예시 2에서 다루겠습니다.
실전 적용
예시 1: Fleet fleet.yaml — 파티션 롤아웃 전략 선언
Fleet 레포지토리 루트에 있는 fleet.yaml에서 파티션을 정의합니다. ClusterGroup 이름 혹은 클러스터 레이블 셀렉터 둘 다 사용 가능합니다.
# fleet.yaml
rolloutStrategy:
maxUnavailablePartitions: 1
partitions:
- name: canary
clusterGroup: canary-clusters # ClusterGroup CRD 참조
maxUnavailable: 1 # 최대 1개 클러스터 동시 업데이트 허용
- name: staging
clusterGroupSelector:
matchLabels:
env: staging # 레이블로 동적 그룹 선택
maxUnavailable: "30%"
- name: prod-wave-1
clusterSelector:
matchLabels:
region: us-east
wave-number: "1" # wave-number 전용 레이블로 중복 방지
env: production
maxUnavailable: "10%"
- name: prod-wave-2
clusterSelector:
matchLabels:
wave-number: "2" # prod-wave-1과 명확히 구분
env: production
maxUnavailable: "5%"| 필드 | 의미 |
|---|---|
maxUnavailablePartitions |
동시에 NotReady 허용되는 파티션 수 (1이면 1개 파티션만 배포 진행) |
clusterGroup |
사전 정의된 ClusterGroup CRD 이름 참조 |
clusterGroupSelector |
레이블로 ClusterGroup을 동적으로 선택 |
maxUnavailable |
해당 파티션 내에서 동시 업데이트 허용 클러스터 수/비율 |
prod-wave-1과 prod-wave-2에 wave-number 레이블을 별도로 쓴 게 보이시나요? env: production만으로 파티션을 나누면 같은 클러스터가 두 파티션에 모두 포함되는 문제가 생깁니다. 저희 팀은 이걸 wave-number 전용 레이블로 명확히 구분해서 관리합니다.
예시 2: 클러스터 내 Argo Rollouts 카나리 — 메트릭 분석 포함
각 클러스터에 배포되는 애플리케이션은 Deployment 대신 Rollout으로 선언합니다. 이미지 태그는 아래처럼 고정값으로 쓰는 게 아니라 실제 GitOps 운영에서는 Helm values나 Kustomize 오버레이로 주입하는 방식을 권장합니다.
# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 10
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:v2.0.0 # 실제 GitOps 환경에서는 Helm values로 주입 권장
strategy:
canary:
canaryService: my-app-canary # 카나리 트래픽용 Service
stableService: my-app-stable # 안정 버전 트래픽용 Service
trafficRouting:
nginx:
stableIngress: my-app-ingress
annotationPrefix: nginx.ingress.kubernetes.io
steps:
- setWeight: 5
- analysis:
templates:
- templateName: success-rate-check
args:
- name: service-name
value: my-app-canary
- pause:
duration: 5m
- setWeight: 20
- analysis:
templates:
- templateName: latency-check
- setWeight: 100
- pause: {} # 카나리 완료 후 수동 promote 대기 — Fleet 파티션 게이팅과 연계마지막 pause: {} 스텝을 주목해주세요. 앞서 언급한 Fleet의 Rollout Ready 판단 문제를 완화하는 방법 중 하나입니다. 모든 카나리 스텝이 끝난 뒤 수동 promote를 기다리는 상태로 두면, Fleet이 파티션 완료를 판단하는 시점을 명시적으로 제어할 수 있습니다. kubectl argo rollouts promote my-app 명령으로 다음 파티션 진행 전에 최종 확인을 거치고 싶은 팀에게 특히 유용합니다.
GitHub Actions와 연동해 특정 사람이 승인해야 promote가 실행되도록 워크플로를 설계하는 팀도 많습니다. 다만 승인 프로세스를 추가하면 배포가 사람 의존적이 되어서 저는 개인적으로 AnalysisTemplate을 충분히 촘촘하게 짜서 자동 프로모션에 의존하는 편을 더 선호합니다. 사람이 병목이 되면 결국 밤에 전화 오는 날이 생기거든요.
# analysistemplate.yaml — Prometheus 기반 성공률 분석
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate-check
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99 # 99% 이상 성공률 유지
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",status!~"5.."}[2m])) /
sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))| 구성 요소 | 역할 |
|---|---|
canaryService / stableService |
NGINX Ingress가 트래픽을 분리할 두 Service |
setWeight |
카나리 Service로 보낼 트래픽 비율 (%) |
analysis |
AnalysisTemplate을 참조해 메트릭 검사 수행 |
pause |
다음 스텝 진행 전 대기 시간 또는 수동 승인 대기 |
failureLimit |
분석 실패를 몇 번까지 허용할지 |
AnalysisRun — Argo Rollouts가 카나리 단계 사이에 실행하는 메트릭 수집·평가 작업입니다. Prometheus, Datadog, CloudWatch 등 다양한 프로바이더와 연동할 수 있으며, 평가 결과에 따라 자동 프로모션 또는 롤백이 결정됩니다.
예시 3: Argo CD ApplicationSet Progressive Sync — Fleet 없이 ArgoCD만 쓰는 경우
이 섹션은 "언제 Fleet 대신 ArgoCD만 써도 되는가"라는 판단 기준과 함께 보셔야 합니다. 아래 경우라면 Fleet 없이 ApplicationSet만으로 충분한 경우가 많습니다.
- 이미 Argo CD를 전사 GitOps 엔진으로 사용 중이고, Fleet을 추가로 도입할 운영 부담이 부담스러운 경우
- 클러스터 수가 100개 이하라 Fleet의 대규모 확장성이 크게 필요하지 않은 경우
- ArgoCD ApplicationSet의
RollingSync기능만으로 웨이브 배포 요건이 충족되는 경우
반대로 클러스터 수가 500개를 넘거나, Fleet의 번들 단위 관리 체계가 이미 있다면 아래 예시보다 예시 1+2 조합이 더 적합합니다.
# applicationset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-fleet
spec:
generators:
- clusters:
selector:
matchExpressions:
- key: env
operator: In
values: [canary, staging, production]
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: env
operator: In
values: [canary]
maxUpdate: 1 # canary 클러스터 1개씩 순차 동기화
- matchExpressions:
- key: env
operator: In
values: [staging]
maxUpdate: "30%" # staging 클러스터의 30%씩 배치 동기화
- matchExpressions:
- key: env
operator: In
values: [production]
maxUpdate: "10%" # production 클러스터의 10%씩 웨이브 배포
template:
metadata:
name: "my-app-{{name}}"
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app
path: k8s/
targetRevision: HEAD
destination:
server: "{{server}}"
namespace: my-appArgo CD ApplicationSet의 RollingSync는 안정화 단계에 접어들고 있습니다. 현재 상태는 Argo CD 공식 문서에서 직접 확인하시는 걸 권장합니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Blast Radius 이중 제어 | Fleet이 클러스터 간 확산을 막고, Argo Rollouts가 클러스터 내 트래픽을 제어 — 두 레이어가 독립적으로 동작 |
| 완전 선언적 | 모든 배포 정책이 Git에 존재하므로 드리프트가 없고, 히스토리 추적·롤백이 자연스럽게 지원됨 |
| 자동 롤백 | AnalysisRun 실패 시 수동 개입 없이 트래픽 자동 복귀 |
| 점진적 신뢰 누적 | 소규모 카나리 클러스터 검증 후 확산하므로 릴리즈에 대한 확신을 단계별로 쌓을 수 있음 |
| 가시성 | Argo Rollouts Dashboard + Fleet UI로 수백 클러스터 롤아웃 상태를 한눈에 파악 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 운영 복잡도 증가 | Fleet, Argo CD, Argo Rollouts 세 컴포넌트의 버전 호환성 관리 부담 | 저희 팀은 Confluence 페이지 하나에 호환성 매트릭스를 직접 유지합니다. 업그레이드 시 이 표를 먼저 체크하는 게 습관이 됐어요 |
| Rollout CRD Ready 판단 문제 | Fleet이 Rollout의 카나리 완료 상태를 표준 Deployment와 다르게 인식할 수 있어 파티션 게이팅이 예상대로 동작하지 않을 수 있음 |
카나리 스텝 마지막에 pause: {} 추가, 또는 Fleet custom health check 구성 |
| Argo Rollouts는 클러스터 로컬 | 전역 멀티 클러스터 상태를 Argo Rollouts 단독으로 파악 불가 | Fleet 또는 Argo CD 대시보드에서 상위 뷰를 별도로 구성 |
| 파티션 완료 판단 임계값 설계 | maxUnavailable 임계값이 너무 엄격하면 일부 클러스터 장애 시 전체 롤아웃이 장시간 중단됨 |
클러스터 특성별로 임계값을 다르게 설정 (canary는 엄격하게, prod-wave-2는 느슨하게) |
| 모니터링 스택 사전 구축 필수 | AnalysisRun이 의존하는 Prometheus 등이 모든 클러스터에 배포되어 있어야 함 | Fleet으로 모니터링 스택 자체도 GitOps로 관리하면 일관성 유지 가능 |
| Fleet 번들 컨트롤러 성능 | 수천 클러스터 규모에서 번들 재조정 폭주가 발생할 수 있음 | SUSE가 공개한 실험 결과를 참고해 배치 크기와 파티션 설계를 환경에 맞게 조정 |
실무에서 가장 흔한 실수
-
canary와 production 클러스터 레이블을 중복 설계해 동일 클러스터가 두 파티션에 포함되는 경우 — Fleet 파티션은 클러스터 선택이 겹치면 예상치 못한 동작을 유발합니다.
wave-number: 1,wave-number: 2같은 전용 레이블을 별도로 설계하는 것이 훨씬 안전합니다. 저도 처음엔 이걸 몰라서 파티션이 섞이는 문제로 꽤 시간을 낭비했습니다. -
AnalysisTemplate 없이
setWeight만 선언하는 경우 — 트래픽은 이동했지만 검증 로직이 없으면 카나리의 의미가 사라집니다. 최소한 성공률 하나라도 분석 조건으로 걸어두는 것을 권장합니다. "일단 배포부터"라는 압박이 있더라도 이 부분만큼은 생략하지 않는 게 좋습니다. -
maxUnavailablePartitions를 0으로 설정해서 단일 클러스터 장애 시 전체 배포가 영구 중단되는 경우 — 이 값이 0이면 모든 파티션이 동시에 완전 정상이어야만 다음 파티션으로 진행됩니다. 클러스터 인프라 자체의 불안정성을 감안해 최소 1~2개 클러스터의 실패는 허용하도록 설계하는 편이 현실적입니다.
마치며
이 패턴을 도입한 팀들이 실제로 얻은 것을 이야기하면, 가장 큰 변화는 배포 주기가 짧아지고 핫픽스 때의 긴장감이 눈에 띄게 줄었다는 겁니다. 카나리 3개 클러스터에서 먼저 검증이 된다는 확신이 생기면, 배포 자체가 두려운 이벤트가 아니라 일상적인 작업이 됩니다. 300개 클러스터에 한 번에 올리던 팀이 이 구조를 도입하고 나서 월 2회에서 주 3회 배포로 전환한 사례도 있습니다.
처음부터 300개 클러스터 전체를 이 패턴으로 전환할 필요는 없습니다. 지금 바로 시작해볼 수 있는 3단계를 제안합니다.
-
기존 클러스터 3~5개에
env: canary레이블을 붙이고fleet.yaml에partitions블록을 추가해보기 — 파티션 설정은 기존 배포에 영향을 주지 않으면서 선언만 해볼 수 있어 부담이 적습니다. ClusterGroup CRD를 만들고 레이블 셀렉터가 의도한 클러스터를 잡는지 먼저 확인해보시면 좋습니다. -
단일 클러스터에 Argo Rollouts 컨트롤러를 Helm으로 설치한 뒤, 가장 트래픽이 적은 서비스 하나를
RolloutCRD로 변환해보기 —helm install argo-rollouts argo/argo-rollouts -n argo-rollouts --create-namespace로 설치 후,setWeight: 5 → pause → setWeight: 100형태의 기본 스텝을 먼저 확인해보시면 됩니다. -
AnalysisTemplate 하나를 Prometheus 성공률 기반으로 작성해 Rollout에 연결하기 — 처음엔
successCondition: result[0] >= 0.95정도로 느슨하게 설정해두는 걸 권장합니다. 메트릭 쿼리를 직접 작성해보면 AnalysisRun이 어떤 기준으로 롤백을 결정하는지 체감할 수 있고, 이 임계값을 점차 높여가다 보면 어느 순간 모니터링 스택이 부족하다는 걸 느끼게 됩니다. 그게 바로 다음 단계로 넘어갈 타이밍입니다.
참고 자료
- Fleet Rollout Strategy 공식 문서 | Rancher Fleet
- Fleet fleet.yaml 레퍼런스 | Rancher Fleet
- Fleet Custom Resources Spec (ClusterGroup CRD) | Rancher Fleet
- Canary Releases with Rancher Continuous Delivery | SUSE Blog
- Scaling Kubernetes GitOps with Fleet — Experiment Results | SUSE Blog
- Argo Rollouts 공식 문서 — Canary Strategy
- Argo Rollouts Best Practices
- Argo CD Progressive Syncs 공식 문서
- ArgoCD Sync Waves 공식 문서
- Kamada + Argo Rollouts: From Canary To Global | ArgoCon Europe 2026
- Building a Fleet with ArgoCD and GKE | Google Cloud Blog
- ACK One + Argo Rollouts 카나리 릴리즈 | Alibaba Cloud
- Canary delivery with Argo Rollouts and Amazon VPC Lattice | AWS Blog
- Progressive Delivery Pipeline with GitHub Actions and Argo Rollouts
- Multi-Cluster GitOps: Fleet Provisioning and Bootstrapping | AWS Blog