9. 배포 전략 (Deployment Strategies)¶
운영 환경에서 애플리케이션을 안전하게 배포하는 것은 쿠버네티스 활용의 핵심입니다. 이 챕터에서는 실전에서 사용되는 주요 배포 전략과 자동 스케일링, 그리고 배포 시 반드시 확인해야 할 체크리스트를 다룹니다.
1. Rolling Update (기본 전략)¶
Kubernetes Deployment의 기본 배포 전략입니다. 기존 Pod를 점진적으로 새 버전으로 교체하여 다운타임 없이 배포합니다.
동작 원리¶
Rolling Update는 새 Pod를 하나씩 생성하고, 정상 동작이 확인되면 기존 Pod를 하나씩 종료합니다. 전체 과정에서 항상 일정 수 이상의 Pod가 트래픽을 처리합니다.
maxSurge / maxUnavailable 설정¶
Rolling Update의 속도와 안정성을 제어하는 두 가지 핵심 파라미터입니다.
| 파라미터 | 설명 | 기본값 |
|---|---|---|
maxSurge |
원하는 replica 수 대비 추가로 생성할 수 있는 Pod 수 | 25% |
maxUnavailable |
업데이트 중 사용 불가능할 수 있는 최대 Pod 수 | 25% |
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 최대 5개까지 Pod 생성 가능 (4+1)
maxUnavailable: 1 # 최소 3개는 항상 가용
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:2.0.0
ports:
- containerPort: 8080
maxSurge와 maxUnavailable 조합 팁
- 안정성 우선:
maxSurge: 1,maxUnavailable: 0— 항상 전체 replica 유지, 느리지만 안전 - 속도 우선:
maxSurge: 50%,maxUnavailable: 50%— 빠르게 교체, 순간 가용 Pod 감소 가능 - 균형:
maxSurge: 25%,maxUnavailable: 25%— 기본값, 대부분의 경우 적절
minReadySeconds¶
새 Pod가 Ready 상태가 된 후 최소 대기 시간(초)을 설정합니다. 이 시간 동안 문제가 발생하지 않아야 다음 단계로 진행합니다.
spec:
minReadySeconds: 10 # Ready 후 10초간 안정적이어야 진행
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
minReadySeconds를 설정하지 않으면
Pod가 Ready 즉시 다음 Pod 교체로 진행합니다. 애플리케이션이 Ready가 되었지만 실제로는 아직 초기화 중인 경우 장애가 발생할 수 있습니다.
롤백: kubectl rollout undo¶
배포 후 문제가 발견되면 이전 버전으로 즉시 롤백할 수 있습니다.
# 배포 상태 확인
kubectl rollout status deployment/my-app
# 배포 히스토리 확인
kubectl rollout history deployment/my-app
# 직전 버전으로 롤백
kubectl rollout undo deployment/my-app
# 특정 리비전으로 롤백
kubectl rollout undo deployment/my-app --to-revision=3
# 배포 일시 중지 / 재개
kubectl rollout pause deployment/my-app
kubectl rollout resume deployment/my-app
revisionHistoryLimit
Deployment의 spec.revisionHistoryLimit (기본값 10)만큼 이전 ReplicaSet을 보관합니다. 너무 낮게 설정하면 롤백 대상이 부족할 수 있습니다.
2. Blue/Green 배포¶
개념 설명¶
Blue/Green 배포는 두 개의 완전한 환경(Blue = 현재 버전, Green = 새 버전)을 동시에 운영하고, 준비가 되면 트래픽을 한 번에 전환하는 방식입니다.
K8s에서 구현 방법¶
Kubernetes에서 Blue/Green은 Service의 selector를 전환하여 구현합니다.
Step 1: Blue Deployment (현재 운영 중)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-blue
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: blue
template:
metadata:
labels:
app: my-app
version: blue
spec:
containers:
- name: my-app
image: my-app:1.0.0
ports:
- containerPort: 8080
Step 2: Service — 현재 Blue를 가리킴
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app
version: blue # Blue를 가리킴
ports:
- port: 80
targetPort: 8080
Step 3: Green Deployment 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-green
spec:
replicas: 3
selector:
matchLabels:
app: my-app
version: green
template:
metadata:
labels:
app: my-app
version: green
spec:
containers:
- name: my-app
image: my-app:2.0.0
ports:
- containerPort: 8080
Step 4: 트래픽 전환
# Service의 selector를 green으로 변경
kubectl patch svc my-app-svc -p '{"spec":{"selector":{"version":"green"}}}'
# 문제 발생 시 즉시 롤백
kubectl patch svc my-app-svc -p '{"spec":{"selector":{"version":"blue"}}}'
장단점¶
| 구분 | 내용 |
|---|---|
| 장점 | 즉시 전환/롤백 가능, 다운타임 제로, 전환 전 Green 환경 충분히 테스트 가능 |
| 단점 | 리소스 2배 필요 (두 환경 동시 운영), 데이터베이스 스키마 변경 시 주의 필요 |
| 적합한 경우 | 빠른 롤백이 중요한 서비스, 배포 전 충분한 검증이 필요한 경우 |
3. Canary 배포¶
개념 설명¶
소수의 사용자에게 먼저 새 버전을 노출하고, 문제가 없으면 점진적으로 확대하는 방식입니다. "탄광의 카나리아"에서 이름이 유래되었습니다 — 위험을 미리 감지하는 역할입니다.
K8s에서 구현 방법 (Replica 비율 조절)¶
가장 간단한 방식은 같은 label을 공유하는 두 Deployment의 replica 비율을 조절하는 것입니다.
Stable Deployment (v1)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-stable
spec:
replicas: 9 # 전체의 90%
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
version: v1
spec:
containers:
- name: my-app
image: my-app:1.0.0
Canary Deployment (v2)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-canary
spec:
replicas: 1 # 전체의 10%
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
version: v2
spec:
containers:
- name: my-app
image: my-app:2.0.0
Service는 공통 label로 연결
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app # version label 없이 → 두 Deployment 모두 대상
ports:
- port: 80
targetPort: 8080
# Canary 비율 확대: v1 줄이고 v2 늘리기
kubectl scale deployment my-app-stable --replicas=5
kubectl scale deployment my-app-canary --replicas=5
# 전체 전환
kubectl scale deployment my-app-stable --replicas=0
kubectl scale deployment my-app-canary --replicas=10
Replica 비율 방식의 한계
트래픽 분배가 Pod 수 비율에 의존하기 때문에 정밀한 비율 제어가 어렵습니다. 예를 들어 1% Canary를 하려면 100개의 Pod가 필요합니다. 정밀 제어가 필요하면 Service Mesh를 사용합니다.
Istio / Service Mesh로 고급 Canary¶
Istio를 사용하면 Pod 수와 무관하게 정밀한 트래픽 비율 제어가 가능합니다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app-vs
spec:
hosts:
- my-app-svc
http:
- route:
- destination:
host: my-app-svc
subset: stable
weight: 95 # 95% → v1
- destination:
host: my-app-svc
subset: canary
weight: 5 # 5% → v2
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-app-dr
spec:
host: my-app-svc
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2
Argo Rollouts
Argo Rollouts를 사용하면 Canary 배포를 자동화할 수 있습니다. 단계별 트래픽 비율, 분석(metrics analysis), 자동 롤백을 선언적으로 정의할 수 있습니다.
4. A/B 테스팅¶
A/B 테스팅은 Canary와 유사하지만, 특정 조건의 사용자에게만 새 버전을 노출합니다. HTTP 헤더, 쿠키, 지역 등의 조건으로 라우팅을 분기합니다.
Ingress 기반 헤더/쿠키 라우팅¶
NGINX Ingress Controller의 annotation을 사용하여 구현합니다.
기본 Ingress (v1)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
spec:
ingressClassName: nginx
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-v1
port:
number: 80
Canary Ingress (v2) — 헤더 기반
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Version"
nginx.ingress.kubernetes.io/canary-by-header-value: "beta"
spec:
ingressClassName: nginx
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-v2
port:
number: 80
쿠키 기반 라우팅
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "beta-user"
# cookie 값이 "always"이면 v2로 라우팅
비율 기반 라우팅
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "20"
# 전체 트래픽의 20%를 v2로 라우팅
A/B 테스팅 우선순위
NGINX Ingress에서 canary annotation의 우선순위는 다음과 같습니다:
canary-by-header > canary-by-cookie > canary-weight
5. 배포 전략 비교¶
| 전략 | 다운타임 | 롤백 속도 | 리소스 비용 | 트래픽 제어 | 적합한 경우 |
|---|---|---|---|---|---|
| Rolling Update | 없음 | 보통 | 낮음 | 불가 | 일반적인 배포 |
| Blue/Green | 없음 | 즉시 | 2배 | 전체 전환 | 빠른 롤백 필요 |
| Canary | 없음 | 빠름 | 약간 추가 | 비율 제어 | 위험 최소화 |
| A/B 테스팅 | 없음 | 빠름 | 약간 추가 | 조건부 라우팅 | 기능 실험 |
6. Horizontal Pod Autoscaler (HPA)¶
배포 전략과 함께 자동 스케일링은 운영의 핵심입니다. HPA는 메트릭 기반으로 Pod 수를 자동 조절합니다.
CPU/Memory 기반 자동 스케일링¶
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # 평균 CPU 60% 유지 목표
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # 평균 메모리 70% 유지 목표
HPA 사용 시 필수 조건
HPA가 동작하려면 반드시 Pod에 resource requests가 설정되어 있어야 합니다. 또한 클러스터에 Metrics Server가 설치되어 있어야 합니다.
커스텀 메트릭¶
CPU/Memory 외에도 요청 수(RPS), 큐 길이 등 비즈니스 메트릭 기반으로 스케일링할 수 있습니다.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa-custom
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 50
metrics:
# Prometheus Adapter를 통한 커스텀 메트릭
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100" # Pod당 초당 100 요청 유지 목표
# 외부 메트릭 (예: SQS 큐 길이)
- type: External
external:
metric:
name: sqs_queue_length
selector:
matchLabels:
queue: my-app-tasks
target:
type: Value
value: "50" # 큐 길이가 50 이하가 되도록 스케일
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # 30초간 관찰 후 scale-up
policies:
- type: Percent
value: 50 # 한 번에 최대 50% 증가
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # 5분간 관찰 후 scale-down
policies:
- type: Pods
value: 2 # 한 번에 최대 2개씩 감소
periodSeconds: 60
behavior 설정의 중요성
scaleDown.stabilizationWindowSeconds를 충분히 설정하지 않으면 트래픽 변동 시 빈번한 scale-up/down 반복(flapping)이 발생할 수 있습니다. 보통 scale-down은 5~10분의 안정화 시간을 권장합니다.
HPA 확인 명령어¶
# HPA 상태 확인
kubectl get hpa
# 상세 정보 (현재 메트릭, 이벤트 등)
kubectl describe hpa my-app-hpa
# HPA 이벤트 모니터링
kubectl get events --field-selector involvedObject.name=my-app-hpa
7. 실전 배포 체크리스트¶
실무에서 안정적인 배포를 위해 반드시 설정해야 할 항목들입니다.
Health Check (Probe 설정)¶
Kubernetes는 3가지 Probe로 Pod의 상태를 확인합니다.
| Probe | 역할 | 실패 시 동작 |
|---|---|---|
| livenessProbe | Pod가 살아있는지 확인 | Pod 재시작 |
| readinessProbe | 트래픽을 받을 준비가 되었는지 확인 | Service에서 제외 |
| startupProbe | 초기 기동이 완료되었는지 확인 | 완료 전까지 다른 Probe 비활성화 |
spec:
containers:
- name: my-app
image: my-app:2.0.0
ports:
- containerPort: 8080
# 기동 완료 확인 (최대 5분 대기)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
# 생존 확인
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
# 트래픽 수신 준비 확인
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
livenessProbe와 readinessProbe를 같은 엔드포인트로 설정하지 마세요
readinessProbe 실패 = Service에서 제외(복구 가능), livenessProbe 실패 = Pod 재시작(강제 종료). 일시적인 부하로 응답이 느려진 경우, liveness 실패로 인한 재시작은 상황을 악화시킬 수 있습니다.
Resource 설정¶
spec:
containers:
- name: my-app
resources:
requests:
cpu: "250m" # 스케줄링 기준 (보장 자원)
memory: "256Mi"
limits:
cpu: "1000m" # 최대 사용 가능
memory: "512Mi" # 초과 시 OOMKill
requests와 limits의 차이
- requests: 스케줄러가 노드 배치 시 참고하는 값. 이만큼의 자원은 보장됨
- limits: 컨테이너가 사용할 수 있는 최대치. CPU는 throttle, Memory는 OOMKill
PodDisruptionBudget (PDB)¶
노드 유지보수, 클러스터 업그레이드 시 최소 가용 Pod 수를 보장합니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # 항상 최소 2개 이상 유지
# 또는: maxUnavailable: 1 # 최대 1개까지만 중단 허용
selector:
matchLabels:
app: my-app
Pod Anti-Affinity¶
Pod를 다른 노드에 분산 배치하여 단일 노드 장애 시에도 서비스를 유지합니다.
spec:
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname
preferred vs required
preferredDuringScheduling: 가능하면 분산 배치 (노드 부족 시 같은 노드에도 배치)requiredDuringScheduling: 반드시 분산 배치 (불가능하면 Pod가 Pending 상태)- 대부분의 경우
preferred를 사용하는 것이 안전합니다.
Graceful Shutdown¶
Pod 종료 시 진행 중인 요청을 안전하게 완료합니다.
spec:
terminationGracePeriodSeconds: 60 # 기본 30초 → 60초로 증가
containers:
- name: my-app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
# Service에서 제거되기 전 5초 대기
모니터링 / 로깅¶
배포 후 반드시 모니터링해야 할 핵심 지표입니다.
| 항목 | 모니터링 방법 | 경고 기준 (예시) |
|---|---|---|
| 에러율 | Prometheus + Grafana | 5xx 비율 > 1% |
| 응답 시간 | Prometheus histogram | p99 > 500ms |
| Pod 재시작 | kubectl get pods / AlertManager |
재시작 횟수 증가 |
| 리소스 사용량 | Metrics Server / Prometheus | CPU > 80%, Memory > 85% |
| HPA 이벤트 | kubectl describe hpa |
빈번한 scale 이벤트 |
# 배포 후 빠른 상태 확인 스크립트
kubectl rollout status deployment/my-app --timeout=300s
# Pod 상태 확인
kubectl get pods -l app=my-app -o wide
# 최근 이벤트 확인
kubectl get events --sort-by='.lastTimestamp' | head -20
# 로그 확인 (최근 배포된 Pod)
kubectl logs -l app=my-app --tail=100 -f
전체 배포 매니페스트 예시¶
위의 모든 체크리스트를 반영한 프로덕션 수준의 Deployment 예시입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
minReadySeconds: 10
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
terminationGracePeriodSeconds: 60
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname
containers:
- name: my-app
image: my-app:2.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-app
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleDown:
stabilizationWindowSeconds: 300
요약¶
| 배포 체크 항목 | 설정 여부 확인 |
|---|---|
| Strategy (Rolling / Blue-Green / Canary) | spec.strategy |
| Health Check (startup / liveness / readiness) | spec.containers[].xxxProbe |
| Resource requests / limits | spec.containers[].resources |
| PodDisruptionBudget | 별도 PDB 리소스 |
| Pod Anti-Affinity | spec.affinity |
| HPA (자동 스케일링) | 별도 HPA 리소스 |
| Graceful Shutdown | terminationGracePeriodSeconds + preStop |
| 모니터링 / 알림 | Prometheus + Grafana + AlertManager |
배포는 기술이 아니라 문화
좋은 배포 전략은 도구 설정만으로 완성되지 않습니다. 배포 전 체크리스트 확인, 모니터링 대시보드 관찰, 문제 발생 시 빠른 롤백 판단 — 이런 팀 문화가 함께해야 안정적인 서비스 운영이 가능합니다.