콘텐츠로 이동

9. 배포 전략 (Deployment Strategies)

운영 환경에서 애플리케이션을 안전하게 배포하는 것은 쿠버네티스 활용의 핵심입니다. 이 챕터에서는 실전에서 사용되는 주요 배포 전략과 자동 스케일링, 그리고 배포 시 반드시 확인해야 할 체크리스트를 다룹니다.


1. Rolling Update (기본 전략)

Kubernetes Deployment의 기본 배포 전략입니다. 기존 Pod를 점진적으로 새 버전으로 교체하여 다운타임 없이 배포합니다.

동작 원리

Rolling Update는 새 Pod를 하나씩 생성하고, 정상 동작이 확인되면 기존 Pod를 하나씩 종료합니다. 전체 과정에서 항상 일정 수 이상의 Pod가 트래픽을 처리합니다.

Rolling Update 과정
Step 1: 초기 상태
v1
v1
v1
모든 Pod가 v1으로 동작 중
Step 2: 새 Pod 생성
v1
v1
v1
v2
maxSurge에 따라 v2 Pod 추가 생성
Step 3: 교체 진행
v1
v1
v1
v2
v2가 Ready 되면 v1 하나를 종료
Step 4: 계속 교체
v1
v2
v2
v1 Pod를 하나씩 v2로 교체
Step 5: 완료
v2
v2
v2
모든 Pod가 v2로 교체 완료

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 = 새 버전)을 동시에 운영하고, 준비가 되면 트래픽을 한 번에 전환하는 방식입니다.

Blue/Green 배포 과정
Step 1: Blue 운영 중
Blue (v1) — LIVE
v1
v1
v1
Green (v2) — 대기
-
-
-
현재 Blue 환경이 트래픽 처리 중
Step 2: Green 환경 준비
Blue (v1) — LIVE
v1
v1
v1
Green (v2) — 준비 완료
v2
v2
v2
Green 환경에 v2를 배포하고 테스트
Step 3: 트래픽 전환
Blue (v1) — 대기
v1
v1
v1
Green (v2) — LIVE
v2
v2
v2
Service selector를 변경하여 트래픽을 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 배포

개념 설명

소수의 사용자에게 먼저 새 버전을 노출하고, 문제가 없으면 점진적으로 확대하는 방식입니다. "탄광의 카나리아"에서 이름이 유래되었습니다 — 위험을 미리 감지하는 역할입니다.

Canary 배포 과정
Step 1: Canary 투입 (10%)
90% 트래픽
10%
v1
v1
v1
v1
v1
v1
v1
v1
v1
v2
v2 Pod 1개를 투입하여 에러율, 응답 시간 모니터링
Step 2: 확대 (50%)
50% 트래픽
50%
v1
v1
v1
v1
v1
v2
v2
v2
v2
v2
이상 없으면 v2 비율을 50%로 확대
Step 3: 전체 전환 (100%)
100% 트래픽
v2
v2
v2
v2
v2
v2
v2
v2
v2
v2
모든 트래픽을 v2로 전환 완료

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 헤더, 쿠키, 지역 등의 조건으로 라우팅을 분기합니다.

A/B 테스팅 — Ingress 기반 라우팅
Ingress Controller
Header: X-Version = beta v2 Service
Default (나머지 전체) v1 Service
v1 Service
v1
v1
v1
v2 Service
v2
v2

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 수를 자동 조절합니다.

HPA 동작 원리
평상시: CPU 30%
3개 Pod, 평균 CPU 30% — 안정 상태
트래픽 급증: CPU 85%
CPU 사용량이 목표(60%)를 초과 — HPA가 Scale-out 결정
Scale-out 완료: CPU 45%
6개 Pod로 확장 — 부하 분산으로 평균 CPU 45%로 안정화

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

배포는 기술이 아니라 문화

좋은 배포 전략은 도구 설정만으로 완성되지 않습니다. 배포 전 체크리스트 확인, 모니터링 대시보드 관찰, 문제 발생 시 빠른 롤백 판단 — 이런 팀 문화가 함께해야 안정적인 서비스 운영이 가능합니다.

댓글