Blue/Green 배포 (4): 운영 및 최적화
모니터링, 비용 최적화, 네트워킹 고려사항, 트러블슈팅 가이드와 실제 사례 연구
2026년 1월 27일·15 min read·
infra
deployment
blue-green
monitoring
prometheus
grafana
cost-optimization
troubleshooting
이 글은 Blue/Green 배포 시리즈의 마지막 글입니다.
- 기초와 전략
- Kubernetes 구현
- 클라우드 & CI/CD 통합
- 운영 및 최적화 (현재 글)
1. 전환/롤백 체크리스트
1.1 전환 전 (Green 검증)
PRE-DEPLOYMENT CHECKLIST
Infrastructure
- Green Pod/인스턴스 모두 Running & Ready
- Health check 엔드포인트 응답 확인 (HTTP 200)
- 리소스 사용량 정상 범위 (CPU < 80%, Memory < 80%)
Application
- Smoke test 통과
- Critical API 엔드포인트 테스트
- 의존성 서비스 연결 확인 (DB, Redis, 외부 API)
Database
- 마이그레이션 스크립트 성공 실행
- 스키마 하위 호환성 확인
- 필요 시 데이터 백필 완료
Monitoring
- 알림 채널 동작 확인 (Slack, PagerDuty)
- 대시보드 접근 가능
- 롤백 담당자 대기
1.2 전환 직후 (5분 모니터링)
POST-DEPLOYMENT MONITORING
Critical Metrics (첫 5분)
- HTTP 5xx 에러율 < 0.1%
- HTTP 4xx 에러율 급증 없음
- P99 레이턴시 < 기존 대비 20% 증가
- 요청 처리량(RPS) 정상 유지
Application Logs
- ERROR/CRITICAL 레벨 로그 급증 없음
- Exception stack trace 없음
- Connection refused/timeout 없음
Business Metrics
- 주요 전환율(로그인, 결제) 급감 없음
- 사용자 세션 유지됨
1.3 롤백 결정 기준
| 지표 | 임계값 | 조치 |
|---|---|---|
| 5xx 에러율 | > 1% (5분간) | 즉시 롤백 |
| P99 레이턴시 | > 2x 기존값 | 조사 후 결정 |
| 핵심 기능 장애 | 결제/로그인 실패 | 즉시 롤백 |
| CPU 사용률 | > 90% (지속) | 스케일 업 또는 롤백 |
| OOM Kill | 발생 | 즉시 롤백 |
2. 모니터링 설정
2.1 핵심 SLI/SLO 정의
# SLO 예시
slos:
availability:
target: 99.9% # 월 43분 다운타임 허용
window: 30d
indicator: |
sum(rate(http_requests_total{status!~"5.."}[5m])) /
sum(rate(http_requests_total[5m]))
latency:
target: 95% # 95%의 요청이 200ms 이내
window: 30d
indicator: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
error_budget:
monthly: 43.2m # 99.9% SLO = 43.2분/월
alert_threshold: 50% # 예산 50% 소진 시 알림
2.2 Prometheus 알림 규칙
# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: blue-green-alerts
spec:
groups:
- name: deployment
rules:
# 5xx 에러율 급증
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) /
sum(rate(http_requests_total[5m])) > 0.01
for: 2m
labels:
severity: critical
action: rollback
annotations:
summary: "High error rate detected after deployment"
description: "Error rate is {{ $value | humanizePercentage }} (threshold: 1%)"
runbook_url: "https://wiki.example.com/runbooks/high-error-rate"
# 레이턴시 급증
- alert: HighLatency
expr: |
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
> 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "P99 latency exceeded 500ms"
description: "Current P99: {{ $value | humanizeDuration }}"
# Pod 재시작 감지
- alert: PodRestartLoop
expr: |
increase(kube_pod_container_status_restarts_total{
pod=~"my-app-.*"
}[15m]) > 3
labels:
severity: critical
action: rollback
annotations:
summary: "Pod restart loop detected"
# 배포 후 롤백 감지
- alert: DeploymentRollback
expr: |
kube_deployment_status_observed_generation{deployment="my-app"}
< kube_deployment_metadata_generation{deployment="my-app"}
for: 1m
labels:
severity: info
annotations:
summary: "Deployment rollback in progress"
2.3 Grafana 대시보드
{
"dashboard": {
"title": "Blue/Green Deployment Dashboard",
"panels": [
{
"title": "Traffic Distribution",
"type": "piechart",
"targets": [
{
"expr": "sum(rate(http_requests_total{color=\"blue\"}[5m]))",
"legendFormat": "Blue"
},
{
"expr": "sum(rate(http_requests_total{color=\"green\"}[5m]))",
"legendFormat": "Green"
}
]
},
{
"title": "Error Rate by Version",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"5..\",color=\"blue\"}[5m])) / sum(rate(http_requests_total{color=\"blue\"}[5m]))",
"legendFormat": "Blue 5xx"
},
{
"expr": "sum(rate(http_requests_total{status=~\"5..\",color=\"green\"}[5m])) / sum(rate(http_requests_total{color=\"green\"}[5m]))",
"legendFormat": "Green 5xx"
}
]
},
{
"title": "P99 Latency Comparison",
"type": "timeseries",
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{color=\"blue\"}[5m])) by (le))",
"legendFormat": "Blue P99"
},
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{color=\"green\"}[5m])) by (le))",
"legendFormat": "Green P99"
}
]
},
{
"title": "Deployment Events",
"type": "annotations",
"datasource": "-- Grafana --",
"query": "tags=deployment"
}
]
}
}
2.4 배포 이벤트 기록
# deploy_marker.py - 배포 이벤트를 Grafana에 기록
import requests
from datetime import datetime
def mark_deployment(version: str, environment: str, color: str):
"""Grafana에 배포 이벤트 마커 생성"""
grafana_url = "https://grafana.example.com"
api_key = os.environ["GRAFANA_API_KEY"]
annotation = {
"time": int(datetime.now().timestamp() * 1000),
"tags": ["deployment", environment, color],
"text": f"Deployed {version} to {color} ({environment})"
}
response = requests.post(
f"{grafana_url}/api/annotations",
json=annotation,
headers={"Authorization": f"Bearer {api_key}"}
)
return response.status_code == 200
3. 비용 최적화
3.1 비용 분석
3.2 Spot/Preemptible 인스턴스 활용
# Green 환경에 Spot 인스턴스 사용 (AWS EKS)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
spec:
template:
spec:
nodeSelector:
node.kubernetes.io/lifecycle: spot
tolerations:
- key: "node.kubernetes.io/lifecycle"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
# Spot 중단 대비 graceful shutdown
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
3.3 자동 스케일링 설정
# green-hpa.yaml - Green 환경 최소 유지
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-green
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app-green
minReplicas: 1 # 평상시 최소 유지
maxReplicas: 10 # 배포 시 확장
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 즉시 스케일 업
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # 5분 대기 후 스케일 다운
3.4 배포 시간 외 Green 환경 축소
#!/bin/bash
# scale-green.sh - 업무 시간 외 Green 환경 축소
HOUR=$(date +%H)
DAY=$(date +%u)
# 업무 시간 (월-금 09:00-18:00) 외에는 축소
if [ $DAY -gt 5 ] || [ $HOUR -lt 9 ] || [ $HOUR -gt 18 ]; then
echo "Off-hours: Scaling down Green environment"
kubectl scale deployment my-app-green --replicas=0
else
echo "Business hours: Maintaining Green environment"
kubectl scale deployment my-app-green --replicas=1
fi
# CronJob으로 자동화
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-green-down
spec:
schedule: "0 19 * * 1-5" # 평일 19:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: deployment-scaler
containers:
- name: kubectl
image: bitnami/kubectl
command:
- kubectl
- scale
- deployment/my-app-green
- --replicas=0
restartPolicy: OnFailure
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-green-up
spec:
schedule: "0 8 * * 1-5" # 평일 08:00
jobTemplate:
spec:
template:
spec:
serviceAccountName: deployment-scaler
containers:
- name: kubectl
image: bitnami/kubectl
command:
- kubectl
- scale
- deployment/my-app-green
- --replicas=1
restartPolicy: OnFailure
4. 네트워킹 고려사항
4.1 DNS TTL 관리
# Route53 TTL 설정 예시
aws route53 change-resource-record-sets \
--hosted-zone-id Z123456 \
--change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"TTL": 60,
"ResourceRecords": [{"Value": "1.2.3.4"}]
}
}]
}'
4.2 CDN 캐시 무효화
# CloudFront 캐시 무효화
aws cloudfront create-invalidation \
--distribution-id E123456789 \
--paths "/*"
# Fastly 캐시 퍼지
curl -X POST "https://api.fastly.com/service/{service_id}/purge_all" \
-H "Fastly-Key: $FASTLY_API_KEY"
4.3 Connection Draining
# Kubernetes Service - Connection draining 설정
apiVersion: v1
kind: Service
metadata:
name: my-app
annotations:
# AWS ALB
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"
spec:
# ...
# Pod - Graceful shutdown
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
lifecycle:
preStop:
exec:
# 새 연결 중단, 기존 연결 완료 대기
command:
- /bin/sh
- -c
- |
# Health check 실패시키기
touch /tmp/shutdown
# 기존 연결 완료 대기
sleep 30
4.4 Sticky Session 처리
# Redis를 통한 세션 외부화 (Spring Boot)
# application.yml
spring:
session:
store-type: redis
redis:
namespace: spring:session
redis:
host: redis.example.com
port: 6379
---
# Kubernetes에서 Session Affinity 사용 시
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
5. 트러블슈팅 가이드
5.1 일반적인 문제와 해결책
문제 1: Green 환경이 Ready 상태가 되지 않음
# 진단
kubectl get pods -l color=green
kubectl describe pod my-app-green-xxx
# 일반적인 원인
# 1. 이미지 Pull 실패
kubectl get events --field-selector reason=Failed
# 2. Readiness Probe 실패
kubectl logs my-app-green-xxx
# 3. 리소스 부족
kubectl describe node | grep -A5 "Allocated resources"
문제 2: 전환 후 5xx 에러 급증
# 즉시 롤백
kubectl patch svc my-app -p '{"spec":{"selector":{"color":"blue"}}}'
# 원인 분석
# 1. 로그 확인
kubectl logs -l color=green --tail=100
# 2. 의존성 연결 확인
kubectl exec -it my-app-green-xxx -- curl -v http://db-service:5432
# 3. 환경 변수 비교
kubectl get deployment my-app-blue -o json | jq '.spec.template.spec.containers[0].env'
kubectl get deployment my-app-green -o json | jq '.spec.template.spec.containers[0].env'
문제 3: 세션 유실
# Redis 세션 확인
redis-cli KEYS "spring:session:*" | wc -l
# 해결책: 세션 마이그레이션 확인
# 1. 세션 스토어 연결 확인
kubectl exec my-app-green-xxx -- nc -zv redis.example.com 6379
# 2. 세션 키 포맷 확인 (버전 간 호환성)
redis-cli GET "spring:session:sessions:xxx"
5.2 트러블슈팅 플로우차트
6. 실제 사례 연구
6.1 사례 1: E-commerce 플랫폼 (결제 시스템)
상황: 결제 모듈 업데이트 시 Blue/Green 배포
환경
| 항목 | 스펙 |
|---|---|
| 인프라 | AWS EKS (Kubernetes 1.28) |
| 배포 도구 | Argo Rollouts |
| 트래픽 | 10,000 RPS (피크) |
배포 전략
결과
| 지표 | 값 |
|---|---|
| 다운타임 | 0초 |
| 롤백 필요 | 없음 |
| 총 소요 시간 | 55분 |
6.2 사례 2: API 서버 (DB 스키마 변경 포함)
상황: 사용자 테이블에 새 컬럼 추가
배포 계획 (Expand & Contract)
문제 발생 및 해결
| 단계 | 내용 |
|---|---|
| 문제 | Phase 1에서 v2 배포 후 일부 API에서 null 에러 |
| 원인 | 구 클라이언트가 새 컬럼 없이 데이터 전송 |
| 해결 | 기본값 설정 추가 후 재배포 |
교훈
- DB 스키마 변경은 최소 2주 배포 계획 필요
- 클라이언트 호환성 테스트 필수
6.3 사례 3: 마이크로서비스 (다중 서비스 동시 배포)
상황: 3개 서비스 동시 업데이트 (API Gateway, Auth, User)
배포 순서 (의존성 고려)
자동화 (GitHub Actions)
| 단계 | 설명 |
|---|---|
| 빌드 | 서비스별 병렬 빌드 |
| 배포 | 순차적 배포 (의존성 순서) |
| 승인 | 각 단계 수동 승인 |
문제 발생 및 해결
| 단계 | 내용 |
|---|---|
| 문제 | User 서비스 전환 후 Auth 호출 실패 |
| 원인 | Auth 새 버전의 API 변경을 User가 인지 못함 |
| 해결 | 즉시 User 롤백, Auth API 하위 호환 패치 후 재배포 |
교훈
- 마이크로서비스 동시 배포 시 API 계약 테스트 필수
- Consumer-Driven Contract Testing 도입 권장
7. 시리즈 요약
핵심 포인트
| 주제 | 핵심 내용 |
|---|---|
| 기초 | 두 환경 동시 운영, 즉시 롤백 가능 |
| Kubernetes | Argo Rollouts 또는 Istio 사용 권장 |
| 클라우드 | ALB Weight, Cloud Run Tags, App Service Slots |
| CI/CD | 자동화된 검증 → 수동 승인 → 모니터링 |
| 운영 | SLO 기반 모니터링, 비용 최적화, 체크리스트 |
권장 사항
- 시작은 간단하게: 순수 Kubernetes Service selector로 시작
- 점진적 고도화: Argo Rollouts → Istio 순으로 도입
- 자동화 필수: CI/CD 파이프라인에 Blue/Green 통합
- 모니터링 우선: 배포 전 SLO/알림 체계 구축
- 롤백 연습: 정기적인 롤백 훈련 실시
다음 학습 주제
- Canary 배포: Blue/Green의 점진적 버전
- GitOps: Argo CD를 통한 선언적 배포
- Chaos Engineering: 배포 안정성 검증
- Feature Flags: 코드 수준의 릴리스 제어
이전 글: Blue/Green 배포 (3) - 클라우드 & CI/CD ←
이 시리즈가 Blue/Green 배포를 이해하고 구현하는 데 도움이 되었기를 바랍니다. 질문이나 피드백은 언제든 환영합니다.