-
이코에코(Eco²) Deployment Strategy: Canary Deployments이코에코(Eco²) 2025. 12. 30. 22:10

2024-12-30 | 리팩토링을 앞두고 안전한 배포 전략 수립
TL;DR
- 문제: 대규모 Clean Architecture 리팩토링을 안전하게 배포해야 함
- 선택지: Rolling Update, Blue-Green, Canary
- 결정: Canary 배포 (헤더 기반)
- 이유: 기존 Istio 인프라 활용, 비용 효율성, 점진적 검증 가능
1. 배경
1.1 현재 상황
도메인 서비스들의 Clean Architecture 리팩토링을 앞두고 있다. Auth 서비스를 시작으로 모든 도메인 서비스에 다음 변경이 예정되어 있다:
- 디렉토리 구조 전면 개편 (
models/→domain/entities/,services/→application/commands/등) - CQRS 패턴 도입 (Command/Query 분리)
- Repository 인터페이스 추가 (DIP 적용)
- 의존성 주입 개선
이는 단순한 버그 픽스가 아니라 코드베이스의 근본적인 변경이다. 프로덕션에 직접 배포하기엔 위험이 크다.
1.2 요구사항
점진적 검증 전체 트래픽에 영향 없이 새 버전 테스트 🔴 필수 빠른 롤백 문제 발생 시 즉시 복구 (< 1분) 🔴 필수 비용 효율성 추가 인프라 비용 최소화 🟡 중요 개발자 친화성 쉬운 테스트 환경 🟢 권장 자동화 가능성 CI/CD 파이프라인 통합 🟢 권장 1.3 현재 인프라 구성
┌─────────────────────────────────────────────────────────────────┐ │ AWS EKS Cluster │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ auth node │ │character node│ │ scan node │ ... │ │ │ (2 CPU,4GB) │ │ (2 CPU,4GB) │ │ (2 CPU,4GB) │ │ │ │ │ │ │ │ │ │ │ │ - auth-api │ │-character-api│ │ - scan-api │ │ │ │ - ext-authz │ │-character- │ │ - scan-worker│ │ │ │ - auth-relay │ │ worker │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Istio Service Mesh │ │ │ │ - Gateway (외부 트래픽 진입점) │ │ │ │ - VirtualService (라우팅 규칙) │ │ │ │ - DestinationRule (트래픽 정책) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ ArgoCD (GitOps) │ │ │ │ - ApplicationSet으로 도메인별 자동 배포 │ │ │ │ - Kustomize 오버레이 (dev/prod) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘핵심 제약사항:
- 각 노드는 2 CPU, ~4GB 메모리로 제한
- 도메인별 전용 노드로 Taint/Toleration 설정
- 추가 노드 프로비저닝 비용 부담
2. 배포 전략 비교
2.1 Rolling Update (롤링 업데이트)
Kubernetes의 기본 배포 전략이다. Pod를 하나씩 순차적으로 교체한다.
┌─────────────────────────────────────────────────────────────────┐ │ Rolling Update │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 시간 → │ │ │ │ T0: ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │v1 │ │v1 │ │v1 │ │v1 │ ← 모두 v1 │ │ └───┘ └───┘ └───┘ └───┘ │ │ │ │ │ T1: ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │v2 │ │v1 │ │v1 │ │v1 │ ← 첫 번째 교체 (v1+v2 혼재) │ │ └─┬─┘ └───┘ └───┘ └───┘ │ │ │ │ │ T2: ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │v2 │ │v2 │ │v1 │ │v1 │ ← 두 번째 교체 │ │ └───┘ └─┬─┘ └───┘ └───┘ │ │ │ │ │ ... ▼ │ │ │ │ Tn: ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │v2 │ │v2 │ │v2 │ │v2 │ ← 모두 v2 │ │ └───┘ └───┘ └───┘ └───┘ │ │ │ └─────────────────────────────────────────────────────────────────┘동작 방식
# Kubernetes Deployment 기본 설정 spec: strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 목표 replica 대비 초과 허용 Pod 수 maxUnavailable: 0 # 최소 가용 Pod 보장장점
장점 설명 Zero Config Kubernetes 기본 전략, 별도 설정 불필요 리소스 효율 추가 리소스 최소화 ( maxSurge: 1이면 1개만 추가)자동화 kubectl rollout명령으로 상태 확인/롤백단점
버전 혼재 🔴 배포 중 v1/v2가 동시에 서비스 → API 호환성 문제 영향 범위 🔴 첫 Pod 교체 시점부터 실제 사용자 영향 검증 불가 🟡 특정 버전만 선택적 테스트 불가능 느린 롤백 🟡 롤백도 롤링 방식 (전체 복구에 시간 소요) 리팩토링에 부적합한 이유
Clean Architecture 리팩토링은 내부 구조가 완전히 바뀌지만 API는 동일해야 한다. 만약 리팩토링 과정에서 의도치 않은 동작 변경이 발생하면:
- Rolling Update 시작 → 첫 v2 Pod 생성
- v2 Pod가 트래픽 수신 시작 (이미 일부 사용자 영향)
- 에러 발견 → 롤백 시작
- 롤백도 롤링 방식으로 진행 → 복구에 수 분 소요
결론: 리팩토링 같은 대규모 변경에는 부적합
2.2 Blue-Green Deployment (블루-그린 배포)
두 개의 동일한 환경을 유지하고, 라우터 전환으로 즉시 배포한다.
┌─────────────────────────────────────────────────────────────────┐ │ Blue-Green Deployment │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────┐ │ │ │ Load Balancer │ │ │ └─────────────────┬───────────────────┘ │ │ │ │ │ ┌──────────────┴──────────────┐ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ Blue (Active) │ │ Green (Standby) │ │ │ │ │ │ │ │ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ │ │ │v1 │ │v1 │ │v1 │ │ │ │v2 │ │v2 │ │v2 │ │ │ │ │ └───┘ └───┘ └───┘ │ │ └───┘ └───┘ └───┘ │ │ │ │ │ │ │ │ │ │ ← 100% 트래픽 │ │ ← 0% (대기) │ │ │ └─────────────────────┘ └─────────────────────┘ │ │ │ │ ═══════════════════════════════════════════════════════════ │ │ 전환 후 (Switch) │ │ ═══════════════════════════════════════════════════════════ │ │ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ Blue (Standby) │ │ Green (Active) │ │ │ │ │ │ │ │ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ │ │ │v1 │ │v1 │ │v1 │ │ │ │v2 │ │v2 │ │v2 │ │ │ │ │ └───┘ └───┘ └───┘ │ │ └───┘ └───┘ └───┘ │ │ │ │ │ │ │ │ │ │ ← 0% (롤백 대기) │ │ ← 100% 트래픽 │ │ │ └─────────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘동작 방식
# Service를 selector 변경으로 전환 # 전환 전 spec: selector: app: my-app version: blue # v1 # 전환 후 spec: selector: app: my-app version: green # v2또는 Istio VirtualService weight 조정:
http: - route: - destination: host: my-app subset: blue weight: 0 # 전환 후 - destination: host: my-app subset: green weight: 100 # 전환 후장점
즉시 전환 라우터 변경 한 번으로 완료 (< 1초) 즉시 롤백 문제 시 다시 Blue로 전환 (< 1초) 완전한 격리 테스트 환경이 프로덕션과 동일 일관성 항상 단일 버전만 서비스 단점
2배 리소스 🔴 Blue + Green 모두 프로비저닝 필요 유휴 비용 🔴 Standby 환경이 항상 대기 상태 DB 스키마 🟡 양쪽 환경이 동일 DB 사용 시 스키마 변경 복잡 상태 동기화 🟡 세션, 캐시 등 상태 관리 필요 비용 분석
현재 인프라 기준 Blue-Green 적용 시:
현재 비용 (Stable만): - auth 노드: $X/월 - character 노드: $X/월 - scan 노드: $X/월 - ... (총 N개 노드) = $NX/월 Blue-Green 적용 시: - Blue 환경: $NX/월 - Green 환경: $NX/월 = $2NX/월 (100% 증가)우리 환경에서는 비용이 2배가 되므로 현실적으로 불가능.
2.3 Canary Deployment (카나리 배포)
소수의 카나리 Pod로 새 버전을 먼저 검증하고, 문제 없으면 점진적으로 확대한다.
이름의 유래: 광산에서 유독 가스 감지를 위해 카나리아 새를 사용한 것에서 유래
┌─────────────────────────────────────────────────────────────────┐ │ Canary Deployment │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────┐ │ │ │ Istio VirtualService │ │ │ │ (Header-based / Weight-based) │ │ │ └─────────────────┬───────────────────┘ │ │ │ │ │ ┌────────────────┴────────────────┐ │ │ │ │ │ │ ▼ ▼ │ │ ┌───────────────────────┐ ┌───────────────────────┐ │ │ │ Stable (v1) │ │ Canary (v2) │ │ │ │ │ │ │ │ │ │ ┌───┐ ┌───┐ ┌───┐ │ │ ┌───┐ │ │ │ │ │v1 │ │v1 │ │v1 │ │ │ │v2 │ │ │ │ │ └───┘ └───┘ └───┘ │ │ └───┘ │ │ │ │ │ │ │ │ │ │ 일반 트래픽 (95%) │ │ 테스트 트래픽 (5%) │ │ │ │ 또는 │ │ 또는 │ │ │ │ X-Canary 헤더 없음 │ │ X-Canary: true │ │ │ └───────────────────────┘ └───────────────────────┘ │ │ │ │ ═══════════════════════════════════════════════════════════ │ │ 점진적 확대 단계 │ │ ═══════════════════════════════════════════════════════════ │ │ │ │ Phase 1: 헤더 기반 (개발자만) 0% weight, 헤더로 접근 │ │ ↓ │ │ Phase 2: 5% 트래픽 일반 사용자 5% 노출 │ │ ↓ │ │ Phase 3: 20% 트래픽 에러율/레이턴시 모니터링 │ │ ↓ │ │ Phase 4: 50% 트래픽 대규모 검증 │ │ ↓ │ │ Phase 5: 100% 트래픽 Canary → Stable 전환 │ │ │ └─────────────────────────────────────────────────────────────────┘라우팅 방식
방식 1: 헤더 기반 (Header-based)
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService spec: http: # X-Canary: true 헤더가 있으면 Canary로 - match: - headers: x-canary: exact: 'true' route: - destination: subset: canary # 그 외는 Stable로 - route: - destination: subset: stable방식 2: 가중치 기반 (Weight-based)
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService spec: http: - route: - destination: subset: stable weight: 95 - destination: subset: canary weight: 5방식 3: 하이브리드 (우리가 선택한 방식)
http: # 1순위: 헤더가 있으면 무조건 Canary - match: - headers: x-canary: exact: 'true' route: - destination: subset: canary # 2순위: 나머지는 Stable (나중에 weight 추가 가능) - route: - destination: subset: stable장점
최소 리소스 Canary Pod 1-2개만 추가 (+10~15% 비용) 점진적 검증 단계별 트래픽 확대로 위험 최소화 선택적 테스트 헤더 기반으로 개발자만 테스트 가능 빠른 롤백 Canary Pod 제거 또는 weight 0으로 즉시 복구 실제 트래픽 프로덕션 환경에서 실제 사용자 패턴으로 검증 단점
설정 복잡도 🟡 VirtualService, DestinationRule 이해 필요 모니터링 필요 🟡 버전별 메트릭 분리 및 비교 필요 디버깅 복잡 🟢 두 버전 동시 운영 시 로그 추적 복잡 비용 분석
현재 비용 (Stable만): - N개 노드, 각 노드에 Pod 2-3개 = 기준 비용 $B/월 Canary 적용 시: - 기존 Stable Pod 유지 - 서비스당 Canary Pod 1개 추가 - 추가 리소스: CPU 0.2코어, 메모리 256MB (노드당) = $B × 1.1~1.15/월 (10~15% 증가)3. 최종 선택: Canary (헤더 기반)
3.1 결정 매트릭스
점진적 검증 30% 1 2 5 Canary만 단계별 검증 가능 빠른 롤백 25% 2 5 5 BG/Canary 모두 즉시 가능 비용 효율성 25% 5 1 4 BG는 2배 비용 설정 용이성 10% 5 3 2 Rolling이 가장 간단 기존 인프라 활용 10% 3 2 5 Istio 이미 구축됨 총점 100% 2.8 2.4 4.5 3.2 Istio 활용 이점
우리 클러스터에는 이미 Istio Service Mesh가 구축되어 있다:
# 이미 존재하는 리소스들 - Gateway: istio-system/eco2-gateway - VirtualService: 각 도메인별 라우팅 규칙 - DestinationRule: 일부 서비스에 존재 (character, my, ext-authz)Canary 배포를 위해 추가로 필요한 것:
# 추가/수정이 필요한 리소스 1. DestinationRule: stable/canary subset 정의 (신규 또는 수정) 2. VirtualService: X-Canary 헤더 매칭 규칙 추가 (수정) 3. Deployment: Canary 버전 (version: v2 라벨) 추가 (신규)핵심: 새로운 인프라 구성 요소 없이 기존 Istio로 구현 가능
3.3 구현 결정사항
라우팅 전략: 헤더 기반 우선
처음에는 가중치 기반이 아닌 헤더 기반으로 시작:
# Phase 1: 개발자 테스트 (헤더 기반) http: - match: - headers: x-canary: exact: 'true' route: - destination: subset: canary - route: - destination: subset: stable이유:
- 리팩토링 초기에는 개발자(본인, Opus)만 테스트
- 안정성 확인 후 가중치 기반으로 전환
- 가중치 전환은 YAML 수정만으로 가능
버전 라벨링 전략
# Stable Deployment metadata: labels: app: auth-api version: v1 # ← subset 매칭에 사용 # Canary Deployment metadata: labels: app: auth-api version: v2 # ← subset 매칭에 사용 release: canary이미지 태그 전략
Stable: mng990/eco2:{service}-dev-latest Canary: mng990/eco2:{service}-dev-canaryCI/CD에서
-canary태그로 빌드하면 자동으로 Canary Deployment에 적용.4. 구현 상세
4.1 적용 범위
API 서비스 (9개)
auth-api auth ✅ ✅ (신규) ✅ character-api character ✅ ✅ (수정) ✅ chat-api chat ✅ ✅ (신규) ✅ image-api image ✅ ✅ (신규) ✅ location-api location ✅ ✅ (신규) ✅ my-api my ✅ ✅ (수정) ✅ scan-api scan ✅ ✅ (신규) ✅ sse-gateway sse-consumer ✅ ✅ (신규) ✅ ext-authz auth - (gRPC) ✅ (수정) ✅ Worker 서비스 (4개)
HTTP 라우팅이 불필요하므로 Canary Deployment만 추가:
auth-relay auth ✅ character-worker character ✅ scan-worker scan ✅ my-worker my ✅ 4.2 파일 구조
workloads/ ├── domains/{service}/base/ │ ├── deployment.yaml # Stable (version: v1) │ ├── deployment-canary.yaml # Canary (version: v2) ← 신규 │ ├── destination-rule.yaml # stable/canary subset ← 신규/수정 │ ├── service.yaml │ ├── configmap.yaml │ └── kustomization.yaml # canary 리소스 포함 ← 수정 │ └── routing/{service}/base/ └── virtual-service.yaml # X-Canary 헤더 라우팅 ← 수정4.3 배포 워크플로우
┌─────────────────────────────────────────────────────────────────┐ │ Canary 배포 워크플로우 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. 코드 변경 │ │ └─→ feature/xxx 브랜치에서 작업 │ │ │ │ 2. Canary 이미지 빌드 │ │ └─→ docker build -t mng990/eco2:{service}-dev-canary │ │ └─→ docker push mng990/eco2:{service}-dev-canary │ │ │ │ 3. ArgoCD 자동 배포 │ │ └─→ Canary Deployment가 새 이미지로 업데이트 │ │ └─→ Canary Pod 생성 (version: v2) │ │ │ │ 4. 개발자 테스트 (헤더 기반) │ │ └─→ curl -H "X-Canary: true" https://api.growbin.app/... │ │ └─→ 로그/메트릭 확인 │ │ │ │ 5. 문제 발견 시 │ │ └─→ 코드 수정 후 2번부터 반복 │ │ └─→ 또는 Canary Pod 스케일 0으로 비활성화 │ │ │ │ 6. 테스트 통과 │ │ └─→ 옵션 A: 가중치 기반으로 전환 (5% → 20% → 50% → 100%) │ │ └─→ 옵션 B: Stable 이미지를 Canary로 교체 │ │ │ │ 7. 완전 전환 │ │ └─→ Canary 이미지를 latest 태그로 재태깅 │ │ └─→ Stable Deployment 업데이트 │ │ └─→ Canary Pod 스케일 다운 │ │ │ └─────────────────────────────────────────────────────────────────┘5. 사용 가이드
5.1 Canary 버전 테스트
# cURL curl -H "X-Canary: true" https://api.growbin.app/api/v1/auth/me # HTTPie http https://api.growbin.app/api/v1/auth/me X-Canary:true # 브라우저: ModHeader 확장 프로그램 사용5.2 로그 확인
# Canary Pod 로그 kubectl logs -n auth -l version=v2 --tail=100 -f # Stable Pod 로그 kubectl logs -n auth -l version=v1 --tail=100 -f5.3 메트릭 구분
Jaeger/Grafana에서 서비스명으로 구분:
- Stable:
auth-api - Canary:
auth-api-canary
5.4 롤백
# 즉시 롤백: Canary Pod 제거 kubectl scale deployment auth-api-canary --replicas=0 -n auth # 또는 VirtualService에서 canary route 제거6. 결론
선택 요약
ubuntu@k8s-master:~$ echo '=== Check Canary Deployments ===' echo 'auth namespace:' kubectl get deploy -n auth | grep -E 'canary|NAME' echo '' echo 'character namespace:' kubectl get deploy -n character | grep -E 'canary|NAME' echo '' echo 'scan namespace:' kubectl get deploy -n scan | grep -E 'canary|NAME' === Check Canary Deployments === auth namespace: NAME READY UP-TO-DATE AVAILABLE AGE auth-api-canary 1/1 1 1 19m auth-relay-canary 0/1 1 0 19m ext-authz-canary 0/1 1 0 19m character namespace: NAME READY UP-TO-DATE AVAILABLE AGE character-api-canary 1/1 1 1 19m character-worker-canary 1/1 1 1 20m scan namespace: NAME READY UP-TO-DATE AVAILABLE AGE scan-api-canary 1/1 1 1 19m scan-worker-canary 1/1 1 1 19m배포 전략 Canary Deployment 라우팅 방식 헤더 기반 (X-Canary: true) 구현 기술 Istio VirtualService + DestinationRule 적용 범위 API 서비스 9개 + Worker 4개 비용 증가 ~10-15% (Pod 1개/서비스 추가) 기대 효과
- 안전한 리팩토링: 실제 클러스터 배포 환경에서 검증 후 전환
- 빠른 피드백: 문제 발견 시 즉시 롤백 가능
- 개발자 경험 향상: 헤더 하나로 새 버전 테스트
- 비용 효율성: Blue-Green 대비 85% 비용 절감
관련 커밋
4efee1e2- feat(canary): add header-based canary deployment for auth serviced3a67f61- feat(canary): add header-based canary deployment for all workloads
참고 자료
'이코에코(Eco²)' 카테고리의 다른 글
이코에코(Eco²) MQ 도입 전, 코드 품질 개선: Chat API 리팩토링 (0) 2025.12.21 이코에코(Eco²) MQ 도입 전, 코드 품질 개선: My API 리팩토링 (0) 2025.12.20 이코에코(Eco²) MQ 도입 전, 코드 품질 개선: Character API 리팩토링 (0) 2025.12.20 이코에코(Eco²) 백엔드/인프라 코드 품질 분석기 도입 (1) 2025.12.20 [Dec.20.2025] 이코에코(Eco²) 백엔드/인프라 디자인 패턴 (0) 2025.12.20