Kubernetes에서 Blue/Green 배포를 Service selector, Argo Rollouts, Istio 세 단계로 구현하고 DB 마이그레이션까지 다룹니다.
TL;DR
- Kubernetes에서 Blue/Green은
Service selector만으로도 가능하지만 운영 자동화가 어렵습니다. - 프로덕션에서는
Argo Rollouts로 전환/검증/롤백 자동화를 우선 고려하는 것이 안전합니다. - Istio를 결합하면 점진 전환, 트래픽 미러링, 헤더 기반 라우팅까지 확장할 수 있습니다.
Kubernetes에서의 Blue/Green 개요
핵심은 "트래픽 라우팅 제어 방식"을 먼저 선택하는 것입니다. 운영 성숙도에 따라 Service selector -> Argo Rollouts -> Istio 순으로 고도화하는 접근이 현실적입니다.
Kubernetes는 선언적 배포 관리를 제공하지만, 기본적으로는 Rolling Update 전략을 사용합니다. Blue/Green 배포를 구현하려면 추가적인 설정이 필요합니다.
구현 방법 비교
- Service Selector: 복잡도 낮음, 자동화/롤백 수동, 추천 상황은 학습·소규모
- Argo Rollouts: 복잡도 중간, 자동화/롤백 자동, 추천 상황은 프로덕션
- Istio: 복잡도 높음, 자동화/롤백 자동, 추천 상황은 대규모·복잡 라우팅
- Flagger: 복잡도 중간, 자동화/롤백 자동, 추천 상황은 GitOps(Git 저장소를 배포 상태의 단일 소스로 두는 운영 방식) 환경
순수 Kubernetes로 구현
이 방식은 학습과 빠른 Proof of Concept(PoC, 개념 검증)에 가장 적합합니다. 다만 전환 검증, 실패 감지, 롤백 트리거를 직접 스크립트로 관리해야 합니다.
가장 기본적인 방법으로, Service의 selector를 변경하여 트래픽을 전환합니다.
아키텍처
flowchart TB
subgraph cluster[Kubernetes Cluster]
svc[Service<br/>selector: color=blue]
svc --> blue
svc -.-> green
subgraph blue[Deployment: my-app-blue]
b1[color: blue<br/>v1.0.0]
end
subgraph green[Deployment: my-app-green]
g1[color: green<br/>v1.1.0]
end
traffic[트래픽] --> svc
end
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: 20Green Deployment
Green 매니페스트는 Blue와 구조가 같습니다. 식별용 이름·라벨(color)과 버전·이미지에 해당하는 5개 라인만 바뀝니다. replicas·resources·probe·port는 동일합니다.
# green-deployment.yaml — blue-deployment.yaml과 아래 라인만 다르다
metadata:
- name: my-app-blue
+ name: my-app-green
labels:
app: my-app
- color: blue
+ color: green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
- color: blue
+ color: green
template:
metadata:
labels:
app: my-app
- color: blue
- version: v1.0.0
+ color: green
+ version: v1.1.0
spec:
containers:
- name: app
- image: my-app:1.0.0
+ image: my-app:1.1.0
# ports·resources·readinessProbe·livenessProbe는 Blue와 동일color 라벨을 Service의 selector가 비교해 트래픽 라우팅을 결정합니다. 두 Deployment에는 서로 다른 라벨 값을 지정합니다.
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전환 스크립트
#!/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수동 구현의 한계
selector 전환을 사람이 직접 다루면 다음 문제가 누적됩니다.
- 휴먼 에러: selector 변경 시 오타 가능성
- 자동화 어려움: 지속적 통합/지속적 배포(CI/CD) 파이프라인에서 상태 관리 복잡
- 롤백 자동화 없음: 문제 발생 시 수동 개입 필요
- 모니터링 연동 없음: 자동 롤백 트리거 불가
Argo Rollouts로 자동화
선언형 리소스로 배포 상태를 관리하고, 승인 지점과 분석 단계를 내장할 수 있습니다. 수동 스위치 대비 휴먼 에러를 줄이고, 문제 발생 시 자동 롤백 정책을 구현하기 쉽습니다.
Argo Rollouts는 Kubernetes용 점진적 배포(Progressive Delivery) 컨트롤러입니다. 트래픽을 새 버전에 단계적으로 넘기며 검증하고, Blue/Green과 Canary 배포를 선언적으로 관리합니다.
설치
# 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-rolloutsRollout 리소스
# 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: 2Services 구성
# 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배포 워크플로우
# 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대시보드에서 상태 확인
# Argo Rollouts 대시보드 실행
kubectl argo rollouts dashboard
# 브라우저에서 http://localhost:3100 접속flowchart TB
subgraph dashboard[Argo Rollouts Dashboard]
subgraph app[my-app]
status[Status: Paused<br/>Strategy: BlueGreen]
subgraph active[Active: revision:2]
a1[my-app:1.1.0<br/>Pods: 4/4 Ready]
end
subgraph preview[Preview: revision:3]
p1[my-app:1.2.0<br/>Pods: 2/2 Ready]
end
end
buttons[Promote / Abort / Retry]
end
Analysis를 통한 자동 검증
AnalysisTemplate은 전환 전후로 측정할 지표를 정의합니다. successCondition은 통과 기준으로, 예시에서는 2xx 응답 비율 95% 이상입니다. failureLimit은 허용하는 실패 횟수이고, interval·count는 측정 주기와 반복 횟수입니다.
# 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-activeIstio Service Mesh 연동
트래픽을 비율 단위로 제어하거나, 헤더 기반 분기와 미러링 테스트를 추가할 수 있습니다. 고급 라우팅이 필요한 대규모 환경에서 Blue/Green 운영 유연성이 크게 향상됩니다.
Istio를 사용하면 더 세밀한 트래픽 제어가 가능합니다.
아키텍처
flowchart TB
subgraph mesh[Istio Service Mesh]
igw[Istio Ingress Gateway]
igw --> vs
vs[VirtualService<br/>weight: blue=100, green=0]
vs --> dr
dr[DestinationRule<br/>subsets: blue, green]
dr --> blue
dr --> green
subgraph blue[Blue v1.0.0]
be[Envoy Sidecar]
end
subgraph green[Green v1.1.0]
ge[Envoy Sidecar]
end
end
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.0VirtualService (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점진적 전환 (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트래픽 미러링 (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.0DB 스키마 마이그레이션 전략
Blue/Green에서는 두 버전이 같은 DB에 동시에 접근합니다. DB 스키마는 두 버전과 모두 호환되어야 합니다.
Expand -> Migrate -> Contract 순서를 지키면 안전하게 전환하고 롤백 가능성을 확보할 수 있습니다.
Blue/Green 배포에서 가장 어려운 부분은 DB 스키마 변경입니다. 두 버전이 동시에 같은 DB를 사용하기 때문입니다.
Expand & Contract 패턴
flowchart TB
subgraph phase1[Phase 1: EXPAND]
p1[v1 Blue: user_name 컬럼 사용<br/>v2 Green: user_name + full_name 둘 다 지원<br/>DB: 두 컬럼 모두 존재]
end
subgraph phase2[Phase 2: MIGRATE]
p2[v2만 운영: full_name 사용<br/>배치 작업: user_name → full_name 복사]
end
subgraph phase3[Phase 3: CONTRACT]
p3[v3: full_name만 사용<br/>DB: user_name 컬럼 삭제]
end
phase1 --> phase2 --> phase3
단계별 마이그레이션 예시
확장 (Phase 1)
-- 새 컬럼 추가 (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)
# 배치 마이그레이션 스크립트
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)
-- v1이 완전히 제거된 후에만 실행
-- 충분한 모니터링 기간 후 (예: 2주)
ALTER TABLE users DROP COLUMN user_name;마이그레이션 체크리스트
| 단계 | Blue(v1) | Green(v2) | 롤백 |
|---|---|---|---|
| 1. 새 컬럼 추가 | 무시 | 읽기/쓰기 | 가능 |
| 2. 데이터 복사 | 무시 | 읽기/쓰기 | 가능 |
| 3. v2로 전환 | 비활성 | 활성 | 가능 |
| 4. v1 제거 | 삭제 | 활성 | 주의 |
| 5. 이전 컬럼 삭제 | 비활성 | 활성 | 불가 |
중요: Phase 5는 롤백이 불가능하므로, 충분한 모니터링 기간(최소 1-2주)을 거친 후 실행해야 합니다.
정리
Kubernetes에서 Blue/Green을 구현하는 방법은 운영 성숙도에 따라 갈립니다. 학습·소규모는 Service selector 전환으로 충분하고, 프로덕션은 Argo Rollouts로 승인·분석·자동 롤백을 선언적으로 관리하는 편이 안전합니다. 비율 단위 제어나 헤더 기반 분기, 트래픽 미러링이 필요한 대규모 환경이면 Istio를 결합합니다.
두 버전이 같은 DB를 동시에 사용하므로 DB 스키마를 Expand -> Migrate -> Contract 순서로 변경합니다. 하위 호환을 유지해야 롤백할 수 있습니다. 다음 글에서는 클라우드 환경과 CI/CD 파이프라인에 Blue/Green을 통합하는 방법을 다룹니다.