Flagger 단일 CRD로 카나리 배포 단순화하기: KEDA ScaledObject 분리 문제부터 Argo CD ApplicationSet 멀티클러스터 MCP 서버 자동화까지
이 글의 대상 독자: Kubernetes를 운영 환경에서 사용해본 백엔드 개발자 또는 SRE. Helm 배포 경험이 있고 CRD 개념을 알고 있는 분을 전제로 합니다.
Kubernetes에서 카나리 배포를 구성해본 분이라면 한 번쯤 이런 상황을 겪었을 겁니다. KEDA ScaledObject로 오토스케일링을 붙이고, Argo Rollouts의 AnalysisTemplate으로 메트릭 분석을 추가했는데, 배포가 실패하면 어느 CRD가 문제인지 바로 파악이 안 됩니다. ScaledObject가 카나리 파드를 엉뚱하게 스케일업하고, AnalysisTemplate이 아직 롤백을 트리거하지 않은 그 애매한 순간이 바로 장애 시간입니다.
이 글은 그 문제의 근본 원인—오토스케일러와 카나리 분석 컨트롤러가 각각 다른 CRD로 분리되어 상태를 독립적으로 관리한다는 점—을 Flagger의 단일 Canary CRD가 어떻게 해결하는지 설명합니다. 그리고 한 발 더 나아가, AI 어시스턴트와 Kubernetes 클러스터를 연결하는 MCP(Model Context Protocol) 서버를 Argo CD ApplicationSet으로 여러 클러스터에 카나리 배포하는 실전 구성까지 다룹니다.
핵심은 하나입니다: 상태를 하나의 오브젝트가 소유하게 하면 분산된 디버깅 지옥에서 벗어날 수 있습니다.
핵심 개념
KEDA + AnalysisTemplate 분리 구조의 실제 고통
먼저 오해부터 바로잡겠습니다. KEDA와 Flagger는 대체 관계가 아닙니다. KEDA는 이벤트 기반 오토스케일러고, Flagger는 프로그레시브 딜리버리(점진적 배포) 컨트롤러입니다. 두 도구는 역할이 다릅니다. 그런데 왜 함께 쓸 때 문제가 생길까요?
카나리 배포 중에 다음 오브젝트들이 동시에 살아있다고 상상해보세요:
primary-deployment ← Deployment (메인)
canary-deployment ← Deployment (카나리)
primary-scaledobject ← KEDA ScaledObject (primary용)
canary-scaledobject ← KEDA ScaledObject (canary용)
canary-analysis ← AnalysisTemplate (Argo Rollouts)
canary-analysisrun ← AnalysisRun (실행 중인 분석)이 오브젝트들은 서로의 존재를 모릅니다. KEDA가 큐 깊이를 보고 카나리 파드를 10개로 늘리는 동안, AnalysisRun은 에러율 임계값을 초과해서 롤백을 결정할 수 있습니다. 그 사이 10초 동안 트래픽은 에러를 뱉는 카나리 파드로 계속 흘러들어갑니다. 롤백 후에는 canary-scaledobject가 남아 있어 삭제하지 않으면 다음 배포에서 충돌합니다.
Flagger Canary CRD: 단일 소유권 모델
Flagger의 접근 방식은 단순합니다: 모든 하위 리소스의 소유권을 하나의 Canary CRD에 집중시킵니다.
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: mcp-server
namespace: ai-platform
spec:
# 대상 Deployment — 원본은 건드리지 않음
targetRef:
apiVersion: apps/v1
kind: Deployment
name: mcp-server
# KEDA ScaledObject를 Flagger가 관리하도록 위임
autoscalerRef:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
name: mcp-server
primaryScalerQueries:
# primary: 큐 depth 기반 스케일링
queueLength: "sum(keda_scaler_metrics_value{scaledObject='mcp-server-primary'})"
canaryScalerQueries:
# canary: 더 낮은 임계값으로 보수적 스케일링
queueLength: "sum(keda_scaler_metrics_value{scaledObject='mcp-server-canary'})"
# 서비스 메시 트래픽 제어
service:
port: 8080
targetPort: 8080
gateways:
- public-gateway.istio-system.svc.cluster.local
# 카나리 분석 — AnalysisTemplate 불필요
analysis:
interval: 1m
threshold: 5 # 5번 실패 시 롤백
maxWeight: 50 # 최대 50%까지 카나리로 전송
stepWeight: 10 # 10%씩 증가
metrics:
- name: request-success-rate
thresholdRange:
min: 99
interval: 1m
- name: request-duration
thresholdRange:
max: 500 # 500ms 이내
interval: 1m
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
cmd: "hey -z 1m -q 10 -c 2 http://mcp-server-canary.ai-platform/"핵심 동작 원리: Flagger는 이
Canary오브젝트를 감지하고 자동으로mcp-server-primaryDeployment,mcp-server-canaryDeployment, 두 개의 ClusterIP Service, 그리고 KEDA ScaledObject 복사본을 생성합니다. 원본 Deployment는 건드리지 않습니다.
카나리 분석이 진행되는 동안 KEDA ScaledObject의 스케일링을 일시 중단해야 할 때, Flagger는 다음 annotation을 자동으로 추가합니다:
# Flagger가 자동 관리 — 직접 수정 불필요
metadata:
annotations:
autoscaling.keda.sh/paused-replicas: "0"이 한 줄 annotation이 KEDA의 스케일업을 막고, 카나리 분석이 완료되면 Flagger가 annotation을 제거해 스케일링을 재개합니다. 상태 변화의 주도권이 Flagger에 있기 때문에 두 컨트롤러 간 충돌이 없습니다.
Argo CD ApplicationSet: 멀티클러스터를 선언적으로
ApplicationSet은 Argo CD의 Application 오브젝트를 템플릿화해서 여러 클러스터에 자동 생성하는 도구입니다. 핵심은 Generator 개념입니다.
| Generator | 사용 시나리오 | 선택 기준 |
|---|---|---|
clusters |
Argo CD에 등록된 모든(또는 레이블 필터링된) 클러스터 | 클러스터 개수가 동적으로 변할 때 |
git |
Git 디렉토리 구조 또는 JSON 파일 기반 | 클러스터별 설정값이 파일로 존재할 때 |
list |
정적으로 정의된 클러스터 목록 | 대상이 고정되고 작을 때 |
matrix |
두 Generator의 조합 (클러스터 × 환경) | 복잡한 배포 행렬이 필요할 때 |
MCP 서버처럼 각 팀/리전 클러스터에 동일한 구성을 배포하되, 클러스터가 늘어날 때마다 자동화하려면 clusters Generator가 적합합니다.
실전 적용
예시 1: Flagger + KEDA ScaledObject 통합 카나리 배포
먼저 기존 KEDA ScaledObject가 있다고 가정합니다:
# 기존 ScaledObject — 이 파일은 그대로 유지
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: mcp-server
namespace: ai-platform
spec:
scaleTargetRef:
name: mcp-server
minReplicaCount: 1
maxReplicaCount: 20
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring:9090
metricName: mcp_request_queue_depth
query: sum(mcp_request_queue_depth{service="mcp-server"})
threshold: "10"이 ScaledObject를 삭제하지 않고, Canary CRD에 .spec.autoscalerRef로 연결합니다. Flagger가 primary/canary용 ScaledObject를 자동 복제·관리합니다.
배포 이벤트 흐름은 다음과 같습니다:
1. mcp-server Deployment의 이미지 태그 변경 (예: v1 → v2)
↓
2. Flagger가 변경 감지 → Canary 분석 시작
↓
3. KEDA ScaledObject에 paused-replicas: 0 annotation 추가
(스케일링 일시 중단, 파드 수 고정)
↓
4. 트래픽 10% → 20% → ... → 50% (stepWeight 기준)
각 단계마다 Prometheus 메트릭 분석
↓
5-A. 분석 성공 → promotion: canary가 primary로 승격
KEDA annotation 제거, 스케일링 재개
5-B. 분석 실패 (5회 임계값 초과) → rollback
트래픽 100% primary로 복귀, canary 파드 0으로 축소예시 2: Argo CD ApplicationSet으로 멀티클러스터 MCP 서버 배포
MCP(Model Context Protocol) 서버는 Claude, Cursor IDE 같은 AI 어시스턴트가 Kubernetes 클러스터와 통신하는 브릿지입니다. 여러 팀이 각자의 클러스터에 MCP 서버를 운영한다면, 업그레이드 시 카나리 배포가 필요합니다—잘못된 버전이 AI 어시스턴트의 클러스터 접근 권한을 망가뜨리면 영향이 광범위하기 때문입니다.
1단계: 클러스터에 레이블 추가
# Argo CD에 클러스터 등록 시 레이블 부여
argocd cluster add prod-cluster-kr \
--label env=production \
--label region=kr \
--label team=platform
argocd cluster add prod-cluster-jp \
--label env=production \
--label region=jp \
--label team=platform2단계: ApplicationSet 작성
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: mcp-server-canary
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
team: platform # platform 팀 클러스터만 대상
values:
# 클러스터별 오버라이드 가능한 기본값
minReplicas: "2"
maxReplicas: "20"
template:
metadata:
name: "mcp-server-{{name}}" # 클러스터 이름이 자동 삽입
annotations:
# Progressive sync: 첫 번째 웨이브 클러스터 배포 후 5분 대기
argocd.argoproj.io/sync-wave: "{{metadata.labels.region}}"
spec:
project: ai-platform
source:
repoURL: https://github.com/my-org/mcp-server-helm
targetRevision: HEAD
path: charts/mcp-server
helm:
valueFiles:
- values.yaml
- "values-{{metadata.labels.region}}.yaml" # 리전별 설정
parameters:
- name: autoscaling.minReplicas
value: "{{values.minReplicas}}"
- name: autoscaling.maxReplicas
value: "{{values.maxReplicas}}"
- name: flagger.enabled
value: "true"
destination:
server: "{{server}}" # 클러스터 API 서버 URL 자동 삽입
namespace: ai-platform
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true3단계: Helm Chart 내부의 Flagger Canary 리소스
# charts/mcp-server/templates/canary.yaml
{{- if .Values.flagger.enabled }}
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: mcp-server
namespace: {{ .Release.Namespace }}
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: mcp-server
autoscalerRef:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
name: mcp-server
service:
port: {{ .Values.service.port }}
gateways:
- {{ .Values.flagger.gateway }}
analysis:
interval: 1m
threshold: 3
maxWeight: 30 # MCP 서버는 보수적으로 30%까지만
stepWeight: 5
metrics:
- name: request-success-rate
thresholdRange:
min: 99.5
interval: 1m
- name: mcp-tool-call-latency
templateRef:
name: mcp-latency-template # 커스텀 메트릭 템플릿
namespace: flagger-system
thresholdRange:
max: 2000 # LLM 툴 호출은 2초 이내
interval: 1m
{{- end }}새 클러스터를 Argo CD에 env: production, team: platform 레이블로 추가하는 순간, ApplicationSet이 자동으로 해당 클러스터에 Application을 생성합니다. 수동 설정 없이도 MCP 서버가 배포되고, Flagger 카나리 분석이 자동으로 활성화됩니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 단일 상태 소유권 | Canary CRD 하나가 ScaledObject, Service, VirtualService를 모두 소유 — 분산 디버깅 불필요 |
| 원본 보존 | 기존 Deployment, ScaledObject 변경 없이 Flagger 도입 가능 |
| 자동 롤백 | 메트릭 임계값 위반 시 사람 개입 없이 즉시 롤백 |
| GitOps 친화성 | ApplicationSet + Flagger 조합으로 클러스터 추가 = Git 커밋 한 번 |
| CNCF 졸업 프로젝트 | Flagger는 CNCF Graduated — 장기 지원 및 커뮤니티 안정성 보장 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| Argo CD 동기화 충돌 | Flagger가 Argo CD 외부에서 리소스를 수정해 OutOfSync 경고 발생 |
ignoreDifferences 설정으로 Flagger 관리 필드 제외 |
| KEDA 복제본 충돌 | ScaledObject 복제 시 primary 설정이 canary를 오버라이드할 수 있음 | primaryScalerQueries 명시적 지정 필수 |
| 제한적 UI | Flagger 자체 대시보드 없음 | Grafana Flagger 대시보드 또는 Argo CD UI 플러그인 사용 |
| Flux 최적화 설계 | Flux와 먼저 통합을 고려해 설계됨 — Argo CD와는 추가 설정 필요 | selfHeal: false 또는 ignoreDifferences 조합 |
실무에서 가장 흔한 실수
minReplicaCount: 0으로 scale-to-zero 설정 후 카나리 분석 교착 상태: KEDA가 큐가 없을 때 canary 파드를 0으로 줄이면 Flagger는 "파드 준비됨" 신호를 영원히 기다립니다. 해결책은 카나리 분석 기간 동안minReplicaCount: 1을 보장하는 별도 ScaledObject 오버라이드를autoscalerRef.canaryScalerQueries에 지정하는 것입니다.- ApplicationSet의
ignoreDifferences누락으로 인한 지속적 재동기화: Flagger는 Deployment의spec.replicas를 실시간으로 수정합니다. Argo CD는 이를 Git과 다른 상태로 감지해 계속selfHeal동기화를 시도합니다. ApplicationSet 템플릿에 반드시 아래를 추가하세요: ignoreDifferences: - group: apps kind: Deployment jsonPointers: - /spec/replicas - group: keda.sh kind: ScaledObject jsonPointers: - /metadata/annotations- 카나리 promotion 후 원본 ScaledObject가 두 개로 남는 문제: Flagger가 생성한
mcp-server-primaryScaledObject와 원본mcp-serverScaledObject가 공존하면 동일 Deployment를 두 컨트롤러가 동시에 스케일링합니다. 반드시 Helm values에서flagger.enabled: true일 때 원본 ScaledObject의scaleTargetRef가 Flagger가 관리하는 Deployment 이름을 가리키는지 확인하고, Flagger에.spec.autoscalerRef로 위임 후에는 수동 ScaledObject 설정을 제거하세요.
마치며
Flagger의 단일 Canary CRD는 KEDA ScaledObject와 카나리 분석 컨트롤러 사이의 상태 소유권 분쟁을 해결합니다. 원본 리소스를 변경하지 않고도 도입할 수 있으며, Argo CD ApplicationSet과 조합하면 클러스터 레이블 하나로 MCP 서버 카나리 배포 자동화를 선언적으로 구성할 수 있습니다.
지금 바로 시작하려면:
- 기존 Kubernetes 클러스터에 Flagger Helm Chart를 설치하고(
helm install flagger flagger/flagger), 현재 가장 골치 아픈 Deployment 하나에CanaryCRD를 작성해 적용합니다. - Argo CD에 두 번째 클러스터를 등록하고
env: production레이블을 붙인 뒤, 위 ApplicationSet YAML을 적용해 새 클러스터에 자동으로 Application이 생성되는지 확인합니다. - Grafana에 Flagger 공식 대시보드를 임포트해 카나리 분석 진행 상황을 실시간 모니터링합니다.
현재 이 구성의 한계도 있습니다. Flagger의 Gateway API 지원은 아직 성숙 단계이며, ApplicationSet의 Pull Request Generator를 활용한 PR별 프리뷰 환경 자동화는 아직 복잡한 설정이 필요합니다. 두 기능 모두 2025년 내 안정화가 예상되는 방향입니다.
다음 글: Flagger + Prometheus Operator로 커스텀 메트릭 기반 카나리 분석 자동화하기 — LLM 툴 호출 레이턴시를 롤백 트리거로 설정하는 실전
MetricTemplate작성법
참고 자료
- Flagger KEDA ScaledObject 통합 공식 튜토리얼 | docs.flagger.app
- Flagger 동작 원리 상세 | docs.flagger.app
- Gateway API 프로그레시브 딜리버리 | docs.flagger.app
- Argo CD ApplicationSet Cluster Generator | argo-cd.readthedocs.io
- Argo CD Sync Waves | argo-cd.readthedocs.io
- ApplicationSet 멀티클러스터 배포 가이드 | codefresh.io
- KEDA 개념 문서 | keda.sh
- Flagger 카나리 배포 실전 사례 (Expedia Group) | medium.com
- Kubernetes MCP 서버 AI 기반 클러스터 관리 | developers.redhat.com
- AWS EKS MCP 서버 소개 | docs.aws.amazon.com
- 멀티클러스터 배포 자동화 (Argo CD) | developers.redhat.com
- Argo CD ApplicationSet Generator 심화 | piotrminkowski.com
- Canary 배포: Kubernetes Gateway API + Flagger + Google Cloud Deploy | cloud.google.com