이종관
Back to Posts

Blue/Green 배포 (2): Kubernetes에서의 구현

순수 Kubernetes 리소스, Argo Rollouts, Istio를 활용한 Blue/Green 배포 구현과 DB 마이그레이션 전략

2026년 1월 27일·12 min read·
infra
deployment
blue-green
kubernetes
argo-rollouts
istio
devops

이 글은 Blue/Green 배포 시리즈의 두 번째 글입니다.

  1. 기초와 전략
  2. Kubernetes 구현 (현재 글)
  3. 클라우드 & CI/CD 통합
  4. 운영 및 최적화

1. Kubernetes에서의 Blue/Green 개요

Kubernetes는 선언적 배포 관리를 제공하지만, 기본적으로는 Rolling Update 전략을 사용합니다. Blue/Green 배포를 구현하려면 추가적인 설정이 필요합니다.

구현 방법 비교

방법복잡도자동화롤백추천 상황
Service Selector낮음수동수동학습, 소규모
Argo Rollouts중간자동자동프로덕션 권장
Istio높음자동자동대규모, 복잡한 라우팅
Flagger중간자동자동GitOps 환경

2. 순수 Kubernetes로 구현

가장 기본적인 방법으로, Service의 selector를 변경하여 트래픽을 전환합니다.

2.1 아키텍처

2.2 Blue Deployment

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-blue
  labels:
    app: my-app
    color: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      color: blue
  template:
    metadata:
      labels:
        app: my-app
        color: blue
        version: v1.0.0
    spec:
      containers:
      - name: app
        image: my-app:1.0.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20

2.3 Green Deployment

# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-green
  labels:
    app: my-app
    color: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      color: green
  template:
    metadata:
      labels:
        app: my-app
        color: green
        version: v1.1.0
    spec:
      containers:
      - name: app
        image: my-app:1.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20

2.4 Service (트래픽 라우터)

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
  selector:
    app: my-app
    color: blue  # 이 값을 green으로 변경하여 전환
  ports:
    - name: http
      port: 80
      targetPort: 8080

2.5 전환 스크립트

#!/bin/bash
# switch-traffic.sh

NAMESPACE=${NAMESPACE:-default}
SERVICE_NAME=${SERVICE_NAME:-my-app}
TARGET_COLOR=${1:-green}

echo "Switching traffic to $TARGET_COLOR..."

# 현재 상태 확인
CURRENT_COLOR=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE \
  -o jsonpath='{.spec.selector.color}')
echo "Current: $CURRENT_COLOR -> Target: $TARGET_COLOR"

# Green Deployment가 Ready인지 확인
READY_REPLICAS=$(kubectl get deployment my-app-$TARGET_COLOR -n $NAMESPACE \
  -o jsonpath='{.status.readyReplicas}')
DESIRED_REPLICAS=$(kubectl get deployment my-app-$TARGET_COLOR -n $NAMESPACE \
  -o jsonpath='{.spec.replicas}')

if [ "$READY_REPLICAS" != "$DESIRED_REPLICAS" ]; then
  echo "Error: Target deployment not ready ($READY_REPLICAS/$DESIRED_REPLICAS)"
  exit 1
fi

# 트래픽 전환
kubectl patch svc $SERVICE_NAME -n $NAMESPACE \
  -p "{\"spec\":{\"selector\":{\"color\":\"$TARGET_COLOR\"}}}"

echo "Traffic switched to $TARGET_COLOR successfully!"

# 전환 후 상태 확인
kubectl get svc $SERVICE_NAME -n $NAMESPACE -o wide
kubectl get endpoints $SERVICE_NAME -n $NAMESPACE

2.6 수동 구현의 한계

  • 휴먼 에러: selector 변경 시 오타 가능성
  • 자동화 어려움: CI/CD 파이프라인에서 상태 관리 복잡
  • 롤백 자동화 없음: 문제 발생 시 수동 개입 필요
  • 모니터링 연동 없음: 자동 롤백 트리거 불가

3. Argo Rollouts로 자동화

Argo Rollouts는 Kubernetes용 Progressive Delivery Controller로, Blue/Green과 Canary 배포를 선언적으로 관리합니다.

3.1 설치

# Argo Rollouts 설치
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts \
  -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

# kubectl 플러그인 설치 (선택)
brew install argoproj/tap/kubectl-argo-rollouts  # macOS
# 또는
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

3.2 Rollout 리소스

# rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 4
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: my-app:1.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
  strategy:
    blueGreen:
      # 현재 운영 트래픽을 받는 서비스
      activeService: my-app-active
      # 새 버전 미리보기용 서비스
      previewService: my-app-preview
      # 자동 승인 비활성화 (수동 승인 필요)
      autoPromotionEnabled: false
      # 전환 후 이전 ReplicaSet 유지 시간
      scaleDownDelaySeconds: 30
      # Preview ReplicaSet 최소 준비 시간
      previewReplicaCount: 2
      # 자동 롤백 (Analysis 실패 시)
      autoPromotionSeconds: 0  # 0 = 자동 승인 비활성화

3.3 Services 구성

# services.yaml
---
# Active Service (현재 운영)
apiVersion: v1
kind: Service
metadata:
  name: my-app-active
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
---
# Preview Service (새 버전 테스트용)
apiVersion: v1
kind: Service
metadata:
  name: my-app-preview
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

3.4 배포 워크플로우

# 1. 현재 상태 확인
kubectl argo rollouts get rollout my-app -w

# 2. 새 버전 배포 (이미지 변경)
kubectl argo rollouts set image my-app app=my-app:1.2.0

# 3. Preview 환경 테스트
# my-app-preview 서비스로 내부 테스트 수행

# 4. 트래픽 전환 승인
kubectl argo rollouts promote my-app

# 5. 문제 발생 시 롤백
kubectl argo rollouts abort my-app
# 또는 이전 버전으로
kubectl argo rollouts undo my-app

3.5 대시보드에서 상태 확인

# Argo Rollouts 대시보드 실행
kubectl argo rollouts dashboard

# 브라우저에서 http://localhost:3100 접속

3.6 Analysis를 통한 자동 검증

# analysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
  - name: service-name
  metrics:
  - name: success-rate
    interval: 30s
    count: 5
    successCondition: result[0] >= 0.95
    failureLimit: 3
    provider:
      prometheus:
        address: http://prometheus.monitoring:9090
        query: |
          sum(rate(http_requests_total{
            service="{{args.service-name}}",
            status=~"2.."
          }[5m])) /
          sum(rate(http_requests_total{
            service="{{args.service-name}}"
          }[5m]))
# rollout-with-analysis.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  # ... (이전 설정 동일)
  strategy:
    blueGreen:
      activeService: my-app-active
      previewService: my-app-preview
      autoPromotionEnabled: false
      # 전환 전 자동 분석
      prePromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: my-app-preview
      # 전환 후 자동 분석 (롤백 트리거)
      postPromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: my-app-active

4. Istio Service Mesh 연동

Istio를 사용하면 더 세밀한 트래픽 제어가 가능합니다.

4.1 아키텍처

4.2 DestinationRule

# destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: my-app
spec:
  host: my-app
  subsets:
  - name: blue
    labels:
      version: v1.0.0
  - name: green
    labels:
      version: v1.1.0

4.3 VirtualService (Blue/Green)

# virtual-service.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts:
  - my-app
  - my-app.example.com
  gateways:
  - my-app-gateway
  http:
  # 헤더 기반 라우팅 (테스트용)
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: my-app
        subset: green
        port:
          number: 80
  # 기본 트래픽 (Blue로)
  - route:
    - destination:
        host: my-app
        subset: blue
        port:
          number: 80
      weight: 100
    - destination:
        host: my-app
        subset: green
        port:
          number: 80
      weight: 0

4.4 점진적 전환 (Istio + Blue/Green 하이브리드)

# 단계별 전환 예시
# Step 1: 0% Green
- destination:
    host: my-app
    subset: blue
  weight: 100
- destination:
    host: my-app
    subset: green
  weight: 0

# Step 2: 10% Green (테스트)
- destination:
    host: my-app
    subset: blue
  weight: 90
- destination:
    host: my-app
    subset: green
  weight: 10

# Step 3: 100% Green (전환 완료)
- destination:
    host: my-app
    subset: blue
  weight: 0
- destination:
    host: my-app
    subset: green
  weight: 100

4.5 트래픽 미러링 (Shadow Testing)

# 실제 트래픽을 Green에 복제하여 테스트 (응답은 무시)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-app
spec:
  hosts:
  - my-app
  http:
  - route:
    - destination:
        host: my-app
        subset: blue
      weight: 100
    mirror:
      host: my-app
      subset: green
    mirrorPercentage:
      value: 100.0

5. DB 스키마 마이그레이션 전략

Blue/Green 배포에서 가장 어려운 부분은 DB 스키마 변경입니다. 두 버전이 동시에 같은 DB를 사용하기 때문입니다.

5.1 Expand & Contract 패턴

5.2 단계별 마이그레이션 예시

Phase 1: Expand (확장)

-- 새 컬럼 추가 (nullable)
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);

-- 기본값 설정 (기존 데이터 호환)
UPDATE users SET full_name = user_name WHERE full_name IS NULL;
# v2 애플리케이션: 두 컬럼 모두 지원
class User:
    def get_display_name(self):
        # 새 컬럼 우선, 없으면 기존 컬럼 사용
        return self.full_name or self.user_name

    def save(self):
        # 두 컬럼 모두 업데이트 (하위 호환)
        self.full_name = self.display_name
        self.user_name = self.display_name  # v1 호환

Phase 2: Migrate (마이그레이션)

# 배치 마이그레이션 스크립트
def migrate_user_names():
    users = User.objects.filter(full_name__isnull=True)
    for batch in chunked(users, 1000):
        for user in batch:
            user.full_name = user.user_name
        User.objects.bulk_update(batch, ['full_name'])

Phase 3: Contract (축소)

-- v1이 완전히 제거된 후에만 실행
-- 충분한 모니터링 기간 후 (예: 2주)
ALTER TABLE users DROP COLUMN user_name;

5.3 마이그레이션 체크리스트

단계작업Blue(v1)Green(v2)롤백 가능
1새 컬럼 추가무시읽기/쓰기
2데이터 복사무시읽기/쓰기
3v2로 전환-활성
4v1 제거삭제활성⚠️
5이전 컬럼 삭제-활성

중요: Phase 5는 롤백이 불가능하므로, 충분한 모니터링 기간(최소 1-2주)을 거친 후 실행해야 합니다.

6. 다음 단계

이 글에서는 Kubernetes에서 Blue/Green 배포를 구현하는 다양한 방법을 살펴보았습니다.

다음 글에서는 클라우드 환경과 CI/CD 파이프라인 통합을 다룹니다:

  • AWS ALB + Target Group
  • GCP Cloud Load Balancing
  • GitHub Actions 자동화
  • GitLab CI/CD 통합

이전 글: Blue/Green 배포 (1) - 기초와 전략 ← 다음 글: Blue/Green 배포 (3) - 클라우드 & CI/CD →