Theme:

새 버전을 배포할 때 서비스 중단 없이 Pod을 교체하려면 내부에서 어떤 일이 벌어져야 할까요?

Deployment는 Kubernetes에서 가장 많이 사용하는 워크로드 오브젝트입니다. kubectl apply로 이미지 버전을 바꾸면 알아서 롤링 업데이트가 되고, 문제가 생기면 롤백도 한 줄이면 됩니다. 하지만 이 "알아서"가 내부에서 어떻게 동작하는지 이해하지 못하면, 배포 중 장애가 발생했을 때 원인을 찾기 어렵습니다.

Deployment와 ReplicaSet의 관계

Deployment는 직접 Pod을 관리하지 않습니다. 대신 ReplicaSet을 생성하고, ReplicaSet이 Pod을 관리합니다.

PLAINTEXT
Deployment
  └── ReplicaSet (revision 1) → Pod, Pod, Pod
  └── ReplicaSet (revision 2) → Pod, Pod, Pod  ← 현재 활성

업데이트가 발생할 때마다 새로운 ReplicaSet이 생성됩니다. 이전 ReplicaSet은 Pod 수가 0으로 줄어들지만 삭제되지 않고 남아 있습니다. 이것이 롤백이 가능한 이유입니다.

SHELL
# ReplicaSet 확인 — DESIRED 0인 것이 이전 버전
kubectl get rs -l app=my-app
# NAME                    DESIRED   CURRENT   READY   AGE
# my-app-6d4f5b8c9d      3         3         3       10m
# my-app-7b9f8c7d2a      0         0         0       1h

롤링 업데이트 동작 원리

기본 업데이트 전략은 RollingUpdate입니다. 두 가지 핵심 파라미터가 동작을 제어합니다.

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 원하는 수 이상으로 추가 생성할 수 있는 Pod 수
      maxUnavailable: 1    # 업데이트 중 사용 불가능할 수 있는 Pod 수
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:2.0  # 버전 변경

maxSurge와 maxUnavailable

replicas가 3일 때, 설정에 따라 업데이트 과정이 달라집니다.

설정의미동시 Pod 수
maxSurge=1, maxUnavailable=0새 Pod을 먼저 띄우고 기존 제거3 → 4 → 4 → 4 → 3
maxSurge=0, maxUnavailable=1기존 Pod을 먼저 내리고 새로 띄움3 → 2 → 3 → 2 → 3
maxSurge=1, maxUnavailable=1동시 교체 (더 빠름)3 → 3 → 3 → 3

무중단 배포가 중요한 경우 maxUnavailable: 0을 권장합니다. 항상 최소 replicas 수만큼의 Pod이 유지됩니다.

업데이트 단계별 흐름

maxSurge: 1, maxUnavailable: 0, replicas: 3일 때의 과정입니다.

PLAINTEXT
단계 1: 새 ReplicaSet 생성, 새 Pod 1개 추가
  기존: ●●●  새: ○         (총 4개, 가용 3개)

단계 2: 새 Pod Ready 확인 후, 기존 Pod 1개 제거
  기존: ●●   새: ●         (총 3개, 가용 3개)

단계 3: 새 Pod 1개 추가
  기존: ●●   새: ●○        (총 4개, 가용 3개)

단계 4: 새 Pod Ready 확인 후, 기존 Pod 1개 제거
  기존: ●    새: ●●        (총 3개, 가용 3개)

단계 5-6: 반복하여 완료
  기존: (없음) 새: ●●●     (총 3개, 가용 3개)

Recreate 전략

동시에 두 버전이 실행되면 안 되는 경우(예: DB 스키마 호환 불가)에는 Recreate를 사용합니다.

YAML
spec:
  strategy:
    type: Recreate  # 모든 기존 Pod 삭제 후 새 Pod 생성

다운타임이 발생하지만 버전 충돌 위험이 없습니다.

롤백

배포 후 문제가 발생하면 이전 revision으로 돌아갈 수 있습니다.

SHELL
# 업데이트 이력 확인
kubectl rollout history deployment/my-app

# 특정 revision의 상세 정보
kubectl rollout history deployment/my-app --revision=2

# 직전 버전으로 롤백
kubectl rollout undo deployment/my-app

# 특정 revision으로 롤백
kubectl rollout undo deployment/my-app --to-revision=3

롤백은 사실 새로운 ReplicaSet을 만드는 게 아니라, 이전 ReplicaSet의 replicas를 다시 올리는 것입니다. 그래서 빠르게 동작합니다.

revisionHistoryLimit

YAML
spec:
  revisionHistoryLimit: 10  # 기본값: 이전 ReplicaSet 10개 보관

이 값을 너무 작게 설정하면 롤백할 수 있는 버전이 줄어들고, 너무 크면 불필요한 ReplicaSet이 쌓입니다. 운영 환경에서는 5~10 정도가 적당합니다.

업데이트 상태 모니터링

SHELL
# 업데이트 진행 상황 실시간 확인
kubectl rollout status deployment/my-app

# 업데이트 일시 중지
kubectl rollout pause deployment/my-app

# 업데이트 재개
kubectl rollout resume deployment/my-app

rollout pause는 카나리 배포를 수동으로 구현할 때 유용합니다. 새 버전을 일부만 배포한 상태에서 모니터링한 후, 문제가 없으면 resume으로 나머지를 진행합니다.

progressDeadlineSeconds

업데이트가 지정 시간 내에 완료되지 않으면 실패로 판정합니다.

YAML
spec:
  progressDeadlineSeconds: 600  # 기본값: 600초 (10분)

이미지가 잘못되었거나 리소스가 부족해서 새 Pod이 Ready가 되지 않으면, 이 시간이 지난 후 Deployment의 condition이 Progressing=False가 됩니다.

SHELL
# 조건 확인
kubectl get deployment my-app -o jsonpath='{.status.conditions[*].type}'

minReadySeconds

새 Pod이 Ready 상태가 된 후에도 일정 시간 기다린 다음 다음 단계로 넘어가게 합니다.

YAML
spec:
  minReadySeconds: 30  # Ready 후 30초 대기

이 설정은 Pod이 Ready로 표시되었지만 실제로는 아직 불안정한 경우를 대비합니다. 애플리케이션이 시작 직후 잠시 불안정하다면 이 값을 설정하는 것이 좋습니다.

실무에서 권장하는 Deployment 설정

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
  annotations:
    kubernetes.io/change-cause: "v2.1.0 배포 - 로그인 버그 수정"
spec:
  replicas: 3
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 300
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: production-app
  template:
    metadata:
      labels:
        app: production-app
        version: v2.1.0
    spec:
      containers:
        - name: app
          image: my-app:2.1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi

change-cause 어노테이션을 추가하면 kubectl rollout history에서 각 revision의 변경 사유를 확인할 수 있어 운영이 편해집니다.

정리

Deployment의 롤링 업데이트는 "새 ReplicaSet 생성 → Pod 점진 교체 → 이전 ReplicaSet 보관"이라는 단순한 원리로 동작합니다. maxSurgemaxUnavailable로 교체 속도와 가용성의 균형을 조절하고, revisionHistoryLimit으로 롤백 가능 범위를 관리합니다. 이 구조를 이해하면 배포 전략을 상황에 맞게 세밀하게 조정할 수 있습니다.

댓글 로딩 중...