KEDA + Argo Rollouts로 AI 에이전트(MCP) 서버 카나리 배포·자동 롤백 구현하기
LLM이 외부 도구를 직접 호출하는 MCP(Model Context Protocol) 서버, 잘못된 버전 하나가 AI 워크플로우 전체를 멈춘다. 기존 롤링 업데이트는 이 리스크를 충분히 제어하지 못한다. 버전 전환 시간 동안 구버전과 신버전 파드가 혼재하며 일부 요청이 결함 있는 버전으로 라우팅되어도 즉각 롤백이 불가능하고, 오류를 감지하는 것은 여전히 사람의 몫이다. MCP 서버는 특히 LLM 에이전트가 사용하는 도구의 스키마(함수 시그니처, 파라미터 타입)를 제공하는데, 버전 간 스키마 불일치가 생기면 LLM의 도구 호출 자체가 실패한다는 점에서 짧은 전환 시간도 치명적이다.
이 글에서는 Kubernetes 이벤트 기반 오토스케일러 KEDA와 점진적 배포 컨트롤러 Argo Rollouts를 결합해 MCP 서버 카나리 배포를 자동화하는 방법을 단계별로 살펴본다. Prometheus 메트릭 기반으로 성공률이 95% 아래로 떨어지는 순간 자동 롤백이 실행되고, 요청이 없는 새벽 시간대에는 scale-to-zero로 운영 비용을 최대 90% 절감하는 자율 운영 시스템을 목표로 한다.
KEDA의 이벤트 기반 오토스케일링과 Argo Rollouts의 점진적 트래픽 전환을 결합하면, MCP 서버 배포의 안정성과 비용 효율을 동시에 달성할 수 있다.
이 글을 읽기 전에: Kubernetes Pod·Service·HPA 개념, Prometheus 메트릭 기초, Istio VirtualService 개념을 알고 있다고 가정한다. 이 글의 대상 독자는 Kubernetes 운영 경험이 있는 백엔드·DevOps 개발자다.
핵심 개념
KEDA: 이벤트 기반 오토스케일링
KEDA(Kubernetes Event-Driven Autoscaling)는 CNCF 졸업 프로젝트로, Kubernetes 기본 HPA(Horizontal Pod Autoscaler)를 확장한다. 핵심 차이는 트리거 소스의 폭이다. CPU·메모리뿐 아니라 Kafka 큐 깊이, Prometheus 쿼리 결과, AWS SQS 메시지 수 등 65개 이상의 외부 이벤트 소스를 스케일링 기준으로 삼을 수 있다.
MCP 서버에 특히 유용한 두 가지 기능:
scale-to-zero: 요청이 없으면 파드를 0개로 줄여 비용을 완전히 절감한다
Argo Rollout 직접 타겟 지원: ScaledObject의 scaleTargetRef에 Rollout CRD를 직접 지정해, 카나리 배포 중인 리소스를 동시에 오토스케일링할 수 있다
yaml
# 주의: KEDA는 내부적으로 HPA를 자동 생성한다# 기존 HPA가 있으면 충돌하므로, 반드시 HPA를 먼저 제거하고 ScaledObject만 사용할 것apiVersion: keda.sh/v1alpha1kind: ScaledObjectspec: scaleTargetRef: apiVersion: argoproj.io/v1alpha1 # Deployment가 아닌 Rollout CRD를 타겟 kind: Rollout name: mcp-server
복수 트리거를 설정할 경우, KEDA는 각 트리거가 계산한 목표 replica 수 중 가장 큰 값을 적용한다. 연결 수 트리거가 파드 5개를, 큐 깊이 트리거가 파드 8개를 요구한다면 8개로 스케일 업한다. 이 동작 원리를 이해하지 못하면 예상보다 많은 파드가 뜨는 현상에 당황하게 된다.
scale-to-zero: KEDA가 외부 이벤트 소스를 폴링해 이벤트가 없을 때 minReplicaCount를 0으로 설정하는 기능. 기본 HPA는 최소 1개를 유지하지만, KEDA는 완전한 종료가 가능하다.
Argo Rollouts: 점진적 배포 컨트롤러
Argo Rollouts는 Kubernetes 기본 Deployment를 대체하는 Progressive Delivery 컨트롤러다. Rollout CRD에 카나리 단계를 선언하면, Istio나 Linkerd 같은 서비스 메시와 연동해 트래픽을 가중치 기반으로 분할한다.
핵심은 AnalysisTemplate이다. 배포 단계 중간에 Prometheus 쿼리를 실행해 성공률을 평가하고, 조건을 충족하지 못하면 stable 버전으로 자동 복구한다. 사람이 승인 버튼을 누르지 않아도 된다.
AnalysisTemplate: Prometheus, Datadog, CloudWatch 등 메트릭 공급자와 연동해 배포 품질을 자동 평가하는 Argo Rollouts의 CRD. 평가 간격, 실패 허용 횟수, 성공 조건을 선언적으로 정의한다.
MCP 서버와 Kubernetes 배포 고려사항
MCP(Model Context Protocol)는 Anthropic이 설계하고 현재 Linux Foundation이 관리하는 LLM 도구 연동 표준이다. 2025년 스펙 업데이트로 도입된 Streamable HTTP 전송 방식 덕분에 MCP 서버를 원격 서비스로 Kubernetes 위에서 수평 확장할 수 있게 됐다.
MCP 서버에 카나리 전략이 특히 중요한 이유는 두 가지다. 첫째, MCP 서버는 LLM이 호출하는 도구의 스키마를 제공하는데, 버전 간 스키마 불일치가 발생하면 LLM의 도구 호출 자체가 실패한다. 짧은 전환 시간이라도 구버전·신버전이 혼재하면 LLM 추론 결과가 달라져 AI 워크플로우 전체가 중단될 수 있다. 둘째, MCP 서버가 세션 상태를 유지하는 경우 카나리 전환 중 연결이 끊길 수 있다.
MCP 2025 스펙은 stateless 설계를 권장하며, 세션 상태가 없어야 KEDA의 scale-to-zero와 카나리 트래픽 전환이 안전하게 동작한다. 상태가 필요하다면 Redis 등 외부 저장소로 분리하는 것이 좋다.
예시 1: Istio DestinationRule + VirtualService — 트래픽 분할 기반 설정
Argo Rollouts가 가중치 기반 트래픽 분할을 하려면 Istio의 DestinationRule에서 stable과 canary 서브셋이 먼저 정의되어 있어야 한다. DestinationRule 없이 VirtualService의 subset을 참조하면 즉시 라우팅 오류가 발생하므로 반드시 먼저 적용한다.
yaml
# destination-rule.yaml# VirtualService의 subset(stable/canary) 참조에 필요한 선행 리소스apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: mcp-server-destrule namespace: ai-servicesspec: host: mcp-server # Kubernetes Service 이름과 일치해야 함 subsets: - name: stable labels: app: mcp-server # Argo Rollouts가 stable 파드에 rollouts-pod-template-hash 레이블을 자동 추가 - name: canary labels: app: mcp-server
yaml
# virtual-service.yaml# 초기 가중치는 Argo Rollouts가 배포 단계마다 자동으로 수정한다# 수동으로 편집하면 Rollout 상태와 불일치가 발생하므로 주의apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: mcp-server-vsvc namespace: ai-servicesspec: hosts: - mcp-server http: - name: primary route: - destination: host: mcp-server subset: stable weight: 100 # 초기값. 배포 시작 후 Argo Rollouts가 자동 조정 - destination: host: mcp-server subset: canary weight: 0
예시 2: Argo Rollout — MCP 서버 카나리 배포 전략
기존 Deployment를 Rollout으로 교체한다. 트래픽을 10% → 30% → 60% → 100%로 단계적으로 전환하며, 30% 단계에서 AnalysisTemplate을 실행해 성공률을 자동 검증한다. analysis.args를 통해 현재 카나리 파드의 해시값을 AnalysisTemplate에 전달하는 것이 핵심이다. 이 값이 없으면 카나리 파드만 필터링한 메트릭 평가가 불가능하다.
Rollout이 현재 카나리의 rollouts-pod-template-hash 레이블 값을 AnalysisRun에 자동 주입
podTemplateHashValue: Latest
Latest를 지정하면 Rollout이 배포 시점의 파드 해시를 자동으로 채워준다
예시 3: AnalysisTemplate — Prometheus 기반 자동 롤백 조건
AnalysisTemplate은 카나리 배포의 "자동 품질 게이트"다. 60초마다 Prometheus를 쿼리해 MCP 요청 성공률이 95% 미만이면 실패로 기록하고, 3번 연속 실패 시 자동 롤백을 트리거한다.
yaml
# analysis-template.yamlapiVersion: argoproj.io/v1alpha1kind: AnalysisTemplatemetadata: name: mcp-success-rate namespace: ai-servicesspec: args: - name: canary-hash # Rollout의 analysis.args에서 주입받는 변수 선언 metrics: - name: success-rate interval: 60s # 60초마다 평가 (Prometheus 스크레이프 주기 15s의 4배 이상 권장) failureLimit: 3 # 3번 연속 실패 시 롤백 (일시적 메트릭 이상에 의한 오탐 방지) # result[0]: Prometheus 쿼리가 단일 스칼라 값을 반환할 때의 접근 방식 # 벡터(시계열 집합) 쿼리는 result 구조가 다르므로 주의 successCondition: result[0] >= 0.95 provider: prometheus: address: http://prometheus.monitoring:9090 query: | # rate(metric[2m]): 지난 2분 동안의 초당 평균 증가율 계산 # sum(): 여러 파드의 값을 합산 # {version="{{args.canary-hash}}"}: 카나리 파드만 필터링 # 이 레이블 없이 전체를 집계하면 카나리 오류가 stable에 희석되어 문제를 감지 못 함 sum(rate(mcp_requests_total{status="success", version="{{args.canary-hash}}"}[2m])) / sum(rate(mcp_requests_total{ version="{{args.canary-hash}}"}[2m])) - name: error-rate interval: 60s failureLimit: 2 successCondition: result[0] <= 0.05 # 오류율 5% 이하 provider: prometheus: address: http://prometheus.monitoring:9090 query: | sum(rate(mcp_requests_total{status="error", version="{{args.canary-hash}}"}[2m])) / sum(rate(mcp_requests_total{ version="{{args.canary-hash}}"}[2m]))
PromQL 읽는 법: rate(metric[2m])은 지난 2분 동안의 초당 변화율(증가 속도)을 계산한다. sum()으로 여러 파드의 값을 합산하고, 성공 요청 수를 전체 요청 수로 나누면 성공률이 된다. {version="..."} 레이블 필터가 없으면 stable과 canary 파드가 섞여 카나리의 오류가 전체 성공률에 희석된다.
예시 4: KEDA ScaledObject — 이벤트 기반 오토스케일링
Rollout이 정의됐으면 KEDA ScaledObject를 붙여 이벤트 기반 스케일링을 추가한다. scaleTargetRef에 Rollout을 직접 지정하는 것이 핵심이다.
yaml
# scaled-object.yamlapiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: mcp-server-scaler namespace: ai-servicesspec: scaleTargetRef: apiVersion: argoproj.io/v1alpha1 kind: Rollout # Deployment가 아닌 Rollout을 타겟 name: mcp-server minReplicaCount: 2 # 카나리 단계에서 최소 replica가 2 이상이어야 가중치 계산이 올바름 maxReplicaCount: 20 cooldownPeriod: 60 # 단위: 초(seconds). 스케일 다운 전 60초 대기 triggers: - type: prometheus metadata: serverAddress: http://prometheus.monitoring:9090 metricName: mcp_active_connections query: sum(mcp_active_connections{namespace="ai-services"}) threshold: "50" # 활성 연결 50개당 파드 1개 - type: prometheus metadata: serverAddress: http://prometheus.monitoring:9090 metricName: mcp_request_queue_depth query: sum(mcp_request_queue_depth{namespace="ai-services"}) threshold: "100" # 큐 깊이 100당 파드 1개
필드
설명
minReplicaCount: 2
0으로 설정하면 카나리 배포 중 replica가 0이 되어 Rollout의 가중치 계산이 깨진다
cooldownPeriod: 60
단위는 초(seconds). MCP 세션이 처리 중일 때 조급한 scale-down 방지
복수 트리거 우선순위
연결 수·큐 깊이 각 트리거가 계산한 replica 수 중 더 높은 값이 우선 적용된다
배포 상태는 다음 명령으로 실시간 확인한다:
# Rollout 단계 및 AnalysisRun 상태 확인kubectl argo rollouts get rollout mcp-server --watch -n ai-services# KEDA ScaledObject 동작 확인kubectl get scaledobject mcp-server-scaler -n ai-services
장단점 분석
장점
항목
내용
무중단 점진적 배포
10% → 30% → 60% → 100% 단계 전환으로 장애 반경을 최소화하고, 문제 발생 시 영향 범위를 카나리 트래픽으로 한정
자동 롤백
Prometheus 메트릭이 임계값 미달 시 사람 개입 없이 즉시 stable 버전으로 복구, MTTR 단축
이벤트 기반 비용 최적화
AI 에이전트 요청이 없는 시간대 scale-to-zero로 MCP 서버 운영 비용 최대 90% 절감 가능
GitOps 완전 통합
Argo CD + Argo Rollouts 조합으로 모든 배포 상태를 Git에서 선언적으로 관리
다중 메트릭 조건
성공률, 오류율, 레이턴시 P99 등 복수 조건을 AND로 결합해 정교한 품질 게이트 구성 가능
단점 및 주의사항
항목
내용
대응 방안
KEDA ↔ Rollout 상태 충돌
롤아웃 진행 중 KEDA가 replica 수를 변경하면 카나리 가중치 비율이 왜곡됨
minReplicaCount를 카나리 최소 replica보다 크게 설정, cooldownPeriod 조정
KEDA와 HPA 충돌
KEDA는 내부적으로 HPA를 자동 생성하므로 기존 HPA가 있으면 충돌
기존 HPA를 모두 제거한 뒤 ScaledObject만 사용
메트릭 감지 지연
Prometheus 스크레이프 주기(15s)와 AnalysisTemplate interval 사이 시차로 오류 감지가 늦어짐
interval을 스크레이프 주기의 4배 이상으로 설정(60s 권장)
MCP 세션 드롭
Streamable HTTP 세션 상태 유지 시 카나리 전환 중 연결이 끊길 수 있음
MCP 서버를 stateless로 설계, 세션 상태는 Redis 등 외부 저장소로 분리
복잡한 디버깅
KEDA, Argo Rollouts, Istio 3개 컴포넌트가 모두 개입해 장애 원인 추적이 어려움
kubectl argo rollouts get rollout mcp-server --watch로 단계별 상태 확인, 각 컴포넌트 로그 분리 수집
실무에서 가장 흔한 실수
AnalysisTemplate에 failureLimit: 1 설정: Prometheus 스크레이프 타이밍 문제로 일시적인 NaN 결과가 나올 수 있다. 최소 failureLimit: 3으로 설정해 오탐 롤백을 방지하라.
KEDA minReplicaCount: 0 + 카나리 배포 동시 사용: 카나리 단계에서 replica가 0으로 스케일 다운되면 Rollout 컨트롤러의 가중치 계산이 깨진다. 배포 중에는 최소 2 이상을 유지해야 한다.
Prometheus 쿼리에 버전 레이블 미포함: stable과 canary 파드를 구분하지 않고 전체 메트릭을 집계하면 카나리의 오류가 stable 성공률에 희석되어 문제를 감지하지 못한다. version 또는 rollouts-pod-template-hash 레이블을 필터로 반드시 추가하라.
마치며
롤링 업데이트로는 MCP 서버 버전 전환 중 발생하는 스키마 불일치와 즉각 롤백 불가 문제를 해결하기 어렵다. KEDA와 Argo Rollouts를 결합한 MCP 서버 배포 파이프라인은 "배포 후 관찰"이 아닌 "배포 중 자동 검증"으로, 성공률 95% 미달 시 즉각 롤백과 비활성 시간대 최대 90% 비용 절감, 이 두 가지 효과를 사람 개입 없이 동시에 실현한다.
지금 바로 시작할 수 있는 3단계:
Argo Rollouts + KEDA 설치 후 Rollout으로 마이그레이션: Helm으로 두 컴포넌트를 설치하고, 기존 MCP 서버 Deployment를 위 예시 2의 YAML로 교체한다. kubectl argo rollouts get rollout mcp-server --watch로 단계 진행을 실시간 확인한다.
AnalysisTemplate에 MCP 전용 메트릭 연결: mcp_requests_total 메트릭이 없다면 서버에 Prometheus 익스포터를 먼저 추가한다. failureLimit: 3, successCondition: result[0] >= 0.95 조건의 AnalysisTemplate을 배포하고, Rollout의 analysis.args에 canary-hash 주입(podTemplateHashValue: Latest)을 연결한다.
KEDA ScaledObject로 scale-to-zero 구성: minReplicaCount: 2, maxReplicaCount: 20, Prometheus 트리거로 sum(mcp_active_connections) 쿼리를 threshold 50으로 설정하고 kubectl get scaledobject로 정상 동작을 확인한다.
다음 글: 이 구성의 한계인 "KEDA ScaledObject와 AnalysisTemplate이 각기 다른 CRD로 분리되어 있어 상태 관리가 복잡하다"는 문제를 Flagger가 단일 CRD로 어떻게 해결하는지, 그리고 Argo CD ApplicationSet으로 멀티 클러스터 MCP 서버 카나리 배포를 자동화하는 방법을 살펴본다.