infra
Platform

모듈 맵

[Kubernetes] Deployment를 이용한 안정적인 서비스 배포와 롤백 전략

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 05 / 29

[Kubernetes] Deployment를 이용한 안정적인 서비스 배포와 롤백 전략

Deployment → ReplicaSet → Pod 계층 구조를 이해하고, 롤링 업데이트와 롤백을 실습합니다

🚨INCIDENT ALERT
HIGH

금요일 오후 새 버전을 배포했더니 일부 사용자가 502를 보기 시작했습니다. 운영팀은 파드를 직접 지우는 대신 Deployment가 ReplicaSet과 롤링 업데이트를 어떻게 관리하는지 알아야 안전하게 되돌릴 수 있습니다. Deployment는 Kubernetes에서 무중단 배포를 다루는 기본 단위입니다.

Deployment 기본

배포 당일 오전, 팀장이 말했다. "새 버전 올려주세요." 긴장한 채 kubectl set image를 입력했다. 롤링 업데이트가 시작됐다. 그런데 모니터에 502 에러가 치솟기 시작했다. 새 Pod들이 아직 DB 연결을 맺는 중인데, K8s가 이미 트래픽을 보내고 있었다. readinessProbe가 없었던 것이다. 이 사고는 Deployment가 어떻게 동작하는지 정확히 몰랐기 때문에 발생했다. Deployment는 단순히 Pod를 여러 개 실행하는 것이 아니다. 버전 관리, 롤링 업데이트, 자동 롤백까지 내포하는 K8s의 핵심 리소스다. 구조를 이해하면 배포는 두렵지 않다.


이번 챕터에서 배울 것

Deployment를 사용해 Pod를 선언적으로 관리하고, 롤링 업데이트와 롤백을 안전하게 수행하는 방법을 마스터합니다.

  • 1Deployment, ReplicaSet, Pod의 3계층 구조와 각자의 역할을 설명할 수 있다
  • 2maxUnavailable과 maxSurge를 조정해 롤링 업데이트 전략을 설계할 수 있다
  • 3kubectl rollout으로 배포 상태를 확인하고 문제 시 롤백할 수 있다
  • 4readinessProbe와 롤링 업데이트의 관계를 이해해 무중단 배포 조건을 갖출 수 있다
  • 5kubectl scale로 Deployment를 스케일링할 수 있다
실습 환경 준비

실습은 deploy-lab 네임스페이스에서 진행합니다. 마지막에 kubectl config set-context --current --namespace=default로 원복하세요.

클러스터 연결 확인
kubectl cluster-info
실습용 네임스페이스 생성
kubectl create namespace deploy-lab 2>/dev/null || echo 'exists'
현재 네임스페이스 설정 (선택)
kubectl config set-context --current --namespace=deploy-lab
💡개념

Deployment → ReplicaSet → Pod 계층 구조

실무에서 Pod를 직접 만드는 경우는 거의 없습니다. Deployment를 정의하면 K8s가 자동으로 ReplicaSet을 생성하고, ReplicaSet이 Pod를 관리합니다.

Deployment→ReplicaSet→Pod 계층 — Deployment를 정의하면 K8s가 ReplicaSet을 자동 생성하고, ReplicaSet이 지정 수만큼 Pod를 관리·복구. Deployment는 버전·롤링 업데이트를 담당하고 새 버전마다 새 ReplicaSet을 만듦(롤백 시 이전 ReplicaSet으로 복귀). 실무에선 Pod를 직접 만들지 않고 Deployment로 관리확대

왜 Deployment가 필요한가?

Kubernetes
# Pod를 직접 생성했을 때의 문제
kubectl run my-pod --image=nginx:alpine
# Pod가 죽으면? → 자동 복구 없음
# 새 버전 배포? → 삭제 후 재생성 (다운타임)
# 여러 개 필요? → 일일이 kubectl run 반복

# Deployment를 쓰면
kubectl create deployment my-app --image=nginx:alpine --replicas=3
# → Pod 3개 자동 생성 + 자동 복구
# → 이미지 변경 시 롤링 업데이트 자동
# → 실패 시 자동 롤백 가능

3계층 관계 확인

Kubernetes
# Deployment 생성
kubectl create deployment my-app \
  --image=nginx:alpine \
  --replicas=3 \
  -n deploy-lab

# 계층 구조 확인
kubectl get deployment,replicaset,pod -n deploy-lab
# NAME                    READY   UP-TO-DATE   AVAILABLE
# deployment.apps/my-app  3/3     3            3
#
# NAME                              DESIRED   CURRENT   READY
# replicaset.apps/my-app-5d85b4bdc  3         3         3
#
# NAME                        READY   STATUS    RESTARTS
# pod/my-app-5d85b4bdc-abc12  1/1     Running   0
# pod/my-app-5d85b4bdc-def34  1/1     Running   0
# pod/my-app-5d85b4bdc-ghi56  1/1     Running   0
🔍실행 후 확인할 것
  • kubectl get deployment에서 READY 열(예: 3/3)을 먼저 확인 — 분자=분모면 정상, 0/3이면 파드가 하나도 뜨지 않은 것으로 kubectl describe deployment <name>의 Events 확인
  • AVAILABLE 수치 기준: replicas 설정 값과 일치해야 정상. 롤링 업데이트 중에는 maxSurge만큼 일시적으로 replicas+1이 될 수 있으며, 5분 이상 불일치하면 이미지 오류 또는 리소스 부족 의심
  • READY=2/3이고 RESTARTS가 계속 오르면 → 특정 파드만 CrashLoopBackOff 진입 중. kubectl get pods -l app=<name>으로 어떤 파드가 문제인지 특정 후 kubectl logs <pod> --previous로 이전 크래시 로그 확인

ReplicaSet의 역할

Kubernetes
# Pod 하나를 수동으로 삭제해 보자
kubectl delete pod my-app-5d85b4bdc-abc12 -n deploy-lab

# 즉시 새 Pod가 생성됨
kubectl get pods -n deploy-lab
# NAME                        READY   STATUS    RESTARTS   AGE
# my-app-5d85b4bdc-def34      1/1     Running   0          5m
# my-app-5d85b4bdc-ghi56      1/1     Running   0          5m
# my-app-5d85b4bdc-xyz99      1/1     Running   0          5s  ← 새로 생성됨

ReplicaSet은 "항상 3개가 실행 중이어야 한다"는 원하는 상태를 유지합니다.

Deployment YAML 구조

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: deploy-lab
spec:
  replicas: 3                    # 원하는 Pod 수
  selector:
    matchLabels:
      app: my-app                # 어느 Pod를 관리할지
  strategy:
    type: RollingUpdate          # 배포 전략
    rollingUpdate:
      maxUnavailable: 1          # 업데이트 중 최대 불가 Pod 수
      maxSurge: 1               # 추가로 생성 가능한 최대 Pod 수
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.24        # 이미지 버전 관리의 핵심
        ports:
        - containerPort: 80
        readinessProbe:          # 롤링 업데이트의 안전 장치
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"
💡개념

롤링 업데이트 — 무중단 배포의 원리

새 버전을 배포할 때 기존 파드를 모두 내리고 새 파드를 올리는 방식은 짧은 다운타임이 생깁니다. 트래픽이 있는 서비스에서 다운타임은 사용자 경험과 SLA에 직접 영향을 줍니다. 롤링 업데이트는 기존 파드를 하나씩 교체하면서 항상 일정 수 이상이 요청을 처리하도록 유지합니다. readinessProbe가 없으면 새 파드가 아직 초기화 중인데 트래픽이 들어와 502가 발생하므로, 롤링 업데이트와 readinessProbe는 함께 설정해야 진정한 무중단이 됩니다. maxUnavailable과 maxSurge 수치는 배포 속도와 가용성 사이의 트레이드오프를 결정하는 운영 파라미터입니다.

롤링 업데이트 — 무중단 배포의 원리확대

롤링 업데이트 동작 과정

초기 상태: Pod v1.0 × 3개

1단계: 새 ReplicaSet 생성 (v2.0)
       Pod v1.0 × 3, Pod v2.0 × 1 (maxSurge: 1)

2단계: v2.0 Pod가 Ready → v1.0 Pod 1개 종료
       Pod v1.0 × 2, Pod v2.0 × 1

3단계: v2.0 Pod 하나 더 생성
       Pod v1.0 × 2, Pod v2.0 × 2

4단계: v2.0 Pod Ready → v1.0 Pod 1개 종료
       Pod v1.0 × 1, Pod v2.0 × 2

... 반복 ...

최종: Pod v2.0 × 3, 구 ReplicaSet(v1.0) replicas=0으로 보관

이미지 업데이트 방법

Kubernetes
# 방법 1: kubectl set image (빠른 업데이트)
kubectl set image deployment/my-app nginx=nginx:1.25 -n deploy-lab

# 방법 2: kubectl edit (YAML 직접 편집)
kubectl edit deployment/my-app -n deploy-lab
# → image: nginx:1.24 를 nginx:1.25 로 변경

# 방법 3: YAML 파일 수정 후 apply (권장 — GitOps 방식)
# deployment.yaml의 image 필드 수정 후
kubectl apply -f deployment.yaml -n deploy-lab

롤아웃 상태 모니터링

Kubernetes
# 롤아웃 진행 상황 실시간 확인
kubectl rollout status deployment/my-app -n deploy-lab
# Waiting for deployment "my-app" rollout to finish: 1 out of 3 new replicas have been updated...
# Waiting for deployment "my-app" rollout to finish: 2 out of 3 new replicas have been updated...
# Waiting for deployment "my-app" rollout to finish: 1 old replicas are pending termination...
# deployment "my-app" successfully rolled out

# 배포 이력 확인
kubectl rollout history deployment/my-app -n deploy-lab
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         <none>

# 특정 revision 상세 확인
kubectl rollout history deployment/my-app --revision=2 -n deploy-lab

maxUnavailable과 maxSurge 전략

YAML
# 전략 1: 기본 (빠른 배포)
rollingUpdate:
  maxUnavailable: 1   # 교체 중 최대 1개 다운 허용
  maxSurge: 1         # 최대 1개 추가 생성

# 전략 2: 무중단 (가용성 최우선)
rollingUpdate:
  maxUnavailable: 0   # 다운 없이 교체
  maxSurge: 1         # 새 Pod 먼저 생성 후 교체

# 전략 3: 빠른 교체 (속도 최우선)
rollingUpdate:
  maxUnavailable: 2   # 2개까지 동시 교체
  maxSurge: 2         # 2개 추가 생성 허용

롤링 업데이트는 어떤 순서로 파드를 갈아끼우나

💡개념

이미지 태그를 바꾸면 파드가 무중단으로 교체되는 법 — 롤링 업데이트 6단계

kubectl set image deployment/web-app nginx=nginx:1.25 한 줄. Enter를 누르면 잠시 뒤 모든 파드가 새 버전이 되어 있는데, 그동안 서비스는 한 번도 끊기지 않습니다. 이 사이에 Deployment 컨트롤러는 새 ReplicaSet 생성 → 새 파드 추가 → readiness 통과 확인 → 옛 파드 종료를 정해진 여유 폭(maxSurge·maxUnavailable) 안에서 반복합니다. 이 흐름을 알면 "롤아웃이 왜 여기서 멈췄지", "왜 502가 났지"를 단계로 좁힐 수 있습니다.

TEXT
[변경]  spec.template 의 image: nginx:1.24 → 1.25
   │
   ① 컨트롤러가 template 해시 변경 감지 → 새 ReplicaSet(rev N+1) 생성 (replicas=0)
   │
   ② 새 RS를 maxSurge 만큼 +늘림 → 새 파드 기동 (잠깐 replicas+surge)
   │
   ③ 새 파드가 readinessProbe 통과 → Ready → Service 엔드포인트 편입
   │      (readiness 통과가 트래픽 전환의 방아쇠)
   │
   ④ Ready 확보분만큼 옛 RS 파드 종료 (maxUnavailable 범위 내)
   │
   ⑤ ②~④ 점진 반복 → 새 RS가 목표 replicas 도달, 옛 RS는 0으로 축소(삭제 아님·보관)
   │
   ⑥ 완료 → 옛 RS는 revision 이력으로 남아 rollout undo 시 복귀 대상
   ▼
[결과]  전 파드 nginx:1.25, 다운타임 0, revision N+1 기록

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① 새 RS 생성template(파드 스펙) 해시가 바뀌면 새 ReplicaSet을 만든다. 이미지뿐 아니라 env·label 등 template 변경도 트리거태그 문자열은 같은데 내용만 바뀐 경우(:latest 재푸시)엔 해시가 안 바뀌어 롤아웃이 아예 시작 안 됨
② surge 파드 추가maxSurge(기본 25%)만큼 새 파드를 추가로 띄운다 — 잠깐 replicas+surge가 뜬다노드에 CPU/메모리 여유가 없으면 surge 파드가 Pending → 롤아웃 정지(describe pod에 FailedScheduling)
③ readiness 대기새 파드가 readinessProbe를 통과해야 Ready로 세고 Service 엔드포인트에 넣는다probe 없으면 기동 즉시 트래픽 유입 → 초기화 중 파드가 502/503. probe 실패 지속이면 Available이 안 차 정지
④ 옛 파드 종료새 Ready 수만큼 옛 파드를 종료 — maxUnavailable(기본 25%)이 "동시에 몇 개까지 빠져도 되나"를 정한다maxUnavailable 0이면 새 파드가 Ready여야만 옛 파드가 빠져 더 느리지만 더 안전
⑤ 점진 반복목표 수를 채울 때까지 ②~④ 반복. 옛 RS는 replicas 0으로 줄여 보관새 파드가 CrashLoopBackOff면 Ready가 안 돼 여기서 멈춤 — 옛 파드는 살아 있어 서비스는 구버전 유지
⑥ 완료·기록새 RS가 목표 도달, revision 이력에 추가progressDeadlineSeconds(기본 600초) 초과 시 ProgressDeadlineExceeded 조건만 붙음(자동 롤백 아님)

핵심은 "readiness 통과가 트래픽 전환의 방아쇠"라는 것입니다 — 새 파드가 Ready가 되기 전엔 트래픽이 안 가고 옛 파드도 안 빠지므로, 이 두 조건이 무중단을 만듭니다. 반대로 새 파드가 CrashLoop나 probe 실패로 Ready가 못 되면 롤아웃은 그 지점에서 멈추고 옛 버전이 계속 서비스합니다(데이터가 사라지는 게 아니라 전환이 안 될 뿐). maxSurge·maxUnavailable는 이 교체의 속도와 여유를 정하는 손잡이입니다.

진단은 세 명령으로 좁힙니다. kubectl rollout status deployment/web-app으로 어디까지 진행됐는지(몇 개가 updated·pending인지) 보고, kubectl get rs로 새 RS와 옛 RS의 READY 수가 어떻게 넘어가는지 확인합니다. 멈춰 있으면 kubectl describe deployment의 Conditions·Events에서 ProgressDeadlineExceeded나 파드 스케줄 실패 원인을 읽고, 문제가 크면 kubectl rollout undo로 옛 RS를 즉시 복귀시킵니다.

실습: Deployment 생성 및 롤링 업데이트

실습 1: Deployment 생성 및 계층 구조 확인

로컬 터미널
cat <<EOF > /tmp/my-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: deploy-lab
  annotations:
    kubernetes.io/change-cause: "Initial deployment with nginx 1.24"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: nginx
        image: nginx:1.24-alpine
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 3
        resources:
          requests:
            memory: "32Mi"
            cpu: "10m"
EOF

kubectl apply -f /tmp/my-deployment.yaml

# 생성 확인
kubectl get deployment web-app -n deploy-lab
kubectl get replicaset -n deploy-lab
kubectl get pods -n deploy-lab -l app=web-app

실습 2: 롤링 업데이트

Kubernetes
# 이미지 버전 변경
kubectl set image deployment/web-app nginx=nginx:1.25-alpine -n deploy-lab

# 또는 annotation 추가하면 history에 reason이 기록됨
kubectl annotate deployment/web-app kubernetes.io/change-cause="Update nginx to 1.25" -n deploy-lab

# 실시간 모니터링 (다른 터미널에서)
kubectl rollout status deployment/web-app -n deploy-lab

# Pod 교체 과정 확인 (-w로 watch)
kubectl get pods -n deploy-lab -l app=web-app -w
# NAME                      READY   STATUS              RESTARTS
# web-app-abc-xxx           1/1     Running             0
# web-app-abc-yyy           1/1     Running             0
# web-app-abc-zzz           1/1     Running             0
# web-app-def-new1          0/1     ContainerCreating   0      ← 새 Pod 시작
# web-app-def-new1          1/1     Running             0      ← Ready
# web-app-abc-xxx           1/1     Terminating         0      ← 구 Pod 종료

실습 3: 롤백

Kubernetes
# 배포 이력 확인
kubectl rollout history deployment/web-app -n deploy-lab
# REVISION  CHANGE-CAUSE
# 1         Initial deployment with nginx 1.24
# 2         Update nginx to 1.25

# 이전 버전으로 롤백
kubectl rollout undo deployment/web-app -n deploy-lab
# deployment.apps/web-app rolled back

# 롤백 확인
kubectl rollout status deployment/web-app -n deploy-lab
kubectl get replicasets -n deploy-lab
# NAME              DESIRED   CURRENT   READY   AGE
# web-app-abc-...   3         3         3       10m  ← 이전 RS가 복구됨
# web-app-def-...   0         0         0       5m   ← 새 RS가 0으로

# 특정 revision으로 롤백
kubectl rollout undo deployment/web-app --to-revision=1 -n deploy-lab

실습 4: 스케일링

Kubernetes
# 스케일 아웃
kubectl scale deployment/web-app --replicas=5 -n deploy-lab

# 확인
kubectl get pods -n deploy-lab -l app=web-app
# 5개의 Pod가 실행 중

# 스케일 인
kubectl scale deployment/web-app --replicas=2 -n deploy-lab

# YAML 파일 수정 방식 (GitOps 권장)
# deployment.yaml에서 replicas: 2로 변경 후
kubectl apply -f /tmp/my-deployment.yaml -n deploy-lab

상황

Kubernetes
kubectl set image deployment/api-server app=api-server:v2 -n production
# 업데이트 시작

# 모니터링 대시보드: 502 에러율 30% → 갑자기 폭증
# 사용자 신고: "사이트가 안 됩니다"

원인 분석

Kubernetes
# 롤링 업데이트 확인
kubectl get pods -n production -l app=api-server
# NAME              READY   STATUS    RESTARTS   AGE
# api-server-v2-1   1/1     Running   0          10s  ← v2, Running 상태지만...
# api-server-v1-2   1/1     Running   0          5m
# api-server-v1-3   1/1     Running   0          5m

# Deployment 확인
kubectl describe deployment api-server -n production | grep -A5 "Ready Probe"
# (readinessProbe 없음)

readinessProbe가 없으니, v2 Pod가 Running이 되는 즉시 Service 엔드포인트에 추가됩니다. 하지만 앱은 아직 DB 연결과 캐시 초기화 중입니다. 이 시간 동안 들어오는 요청이 502를 냅니다.

즉각 대응: 롤백

위험 명령어롤백은 현재 ReplicaSet의 파드를 점진적으로 종료하고 이전 버전을 시작합니다. 이전 버전에 DB 스키마 변경 비호환, 다른 설정 의존성이 있다면 롤백 자체가 새 장애를 유발할 수 있습니다. 또한 롤백 중에도 일시적으로 502가 발생할 수 있습니다.

프로덕션 Deployment 롤백 — 현재 버전 즉시 교체

안전한 실행 조건: kubectl rollout history deployment/api-server -n production 으로 롤백 대상 revision의 이미지 버전과 변경사항을 먼저 확인하세요. 이전 버전이 현재 DB/설정과 호환되는지 검토 후 실행합니다.

실행 전 반드시 확인

  • kubectl rollout history deployment/api-server -n production 으로 이전 버전 revision 확인
  • kubectl rollout history deployment/api-server --revision=<N> -n production 으로 이전 이미지 태그 확인
  • 이전 버전이 현재 DB 스키마, ConfigMap, Secret과 호환되는지 팀과 확인
  • 롤백 중 에러율 모니터링 대시보드 준비
kubectl rollout undo deployment/api-server -n production

위 항목을 모두 확인한 후 복사할 수 있습니다

Kubernetes
# 즉시 롤백
kubectl rollout undo deployment/api-server -n production

# 롤백 완료 확인
kubectl rollout status deployment/api-server -n production
# deployment "api-server" successfully rolled out

근본 해결: readinessProbe 추가

YAML
containers:
- name: app
  image: api-server:v2
  readinessProbe:
    httpGet:
      path: /health/ready    # 앱이 준비됐을 때 200을 반환하는 엔드포인트
      port: 8080
    initialDelaySeconds: 10  # 앱 시작 후 첫 체크까지 대기
    periodSeconds: 5
    failureThreshold: 3
    successThreshold: 1
로컬 터미널
# 앱 코드에서 /health/ready 구현 (Node.js 예시)
app.get('/health/ready', async (req, res) => {
  try {
    await db.ping()          # DB 연결 확인
    await cache.ping()       # 캐시 연결 확인
    res.status(200).json({ status: 'ready' })
  } catch (err) {
    res.status(503).json({ status: 'not ready', error: err.message })
  }
})

결과: readinessProbe 추가 후 재배포 → v2 Pod가 DB/캐시 연결 완료 후에만 트래픽 수신 → 502 에러 없는 무중단 배포.

핵심 교훈: 롤링 업데이트에서 무중단을 보장하려면 readinessProbe가 필수입니다. 이 설정 없이는 maxUnavailable: 0으로 설정해도 의미가 없습니다.

실습 단계
1

Deployment 생성 및 계층 구조 확인

kubectl create deployment nginx --image=nginx:1.24-alpine --replicas=3 -n deploy-lab kubectl get deployment,replicaset,pod -n deploy-lab

예상 출력

NAME                    READY   UP-TO-DATE   AVAILABLE
deployment.apps/nginx   3/3     3            3
2

롤링 업데이트 실행 및 진행 상황 모니터링

kubectl set image deployment/nginx nginx=nginx:1.25-alpine -n deploy-lab kubectl rollout status deployment/nginx -n deploy-lab

예상 출력

deployment "nginx" successfully rolled out
3

배포 이력 확인

kubectl rollout history deployment/nginx -n deploy-lab

예상 출력

REVISION  CHANGE-CAUSE
1         <none>
2         <none>
4

이전 버전으로 롤백

kubectl rollout undo deployment/nginx -n deploy-lab kubectl rollout status deployment/nginx -n deploy-lab

예상 출력

deployment "nginx" successfully rolled out
5

스케일 아웃 및 정리

kubectl scale deployment/nginx --replicas=5 -n deploy-lab kubectl get pods -n deploy-lab | grep nginx | wc -l

예상 출력

5

심화 — 롤아웃이 '멈춰 있는' 것과 '실패한' 것은 다르다

💡개념

심화: 롤아웃은 어떻게 '완료'를 판정하는가 — Available과 progressDeadline

Deployment 컨트롤러는 계속 돌면서 현재 상태를 원하는 상태에 맞추려 하는데, 롤아웃의 '성공 판정' 기준을 알아야 멈춘 배포를 오해하지 않습니다.

  • 완료 기준은 Running이 아니라 Available입니다: 새 ReplicaSet의 파드가 Ready가 되고 minReadySeconds(기본 0초)만큼 그 상태를 유지해야 Available로 셉니다. maxUnavailable/maxSurge 창 안에서 이 Available 수를 채워야 다음 파드를 교체합니다. readinessProbe가 흔들리면 Available이 안 채워져 롤아웃이 그 자리에 정지합니다.
  • maxSurge는 여유 자원을 전제로 합니다: maxSurge가 1이면 교체 도중 replicas+1개가 잠깐 떠야 하는데, 노드에 그만한 CPU/메모리 여유가 없으면 surge 파드가 Pending에 걸리고 롤아웃 전체가 멈춥니다. 스케줄러가 자리를 못 찾는 것이지 이미지 문제가 아닙니다.
  • progressDeadlineSeconds(기본 600초): 이 시간 동안 진전이 없으면 Deployment에 ProgressDeadlineExceeded 조건이 붙습니다. 중요한 오해 하나 — 이건 자동 롤백이 아닙니다. 컨트롤러는 계속 재시도할 뿐, 옛 파드를 그대로 두고 멈춰 섭니다. 되돌리려면 사람이 rollout undo를 해야 합니다.

그래서 kubectl rollout status가 타임아웃으로 끝나도 서비스는 (구버전으로) 살아 있는 경우가 많습니다. 멈춘 롤아웃은 대개 스케줄 실패나 probe 실패이지, 데이터 유실이 아닙니다.

상황: kubectl set image로 배포했는데 kubectl rollout status가 진행 메시지만 반복하다 멈춰 있습니다. 서비스는 여전히 구버전으로 동작 중이라 사용자 영향은 아직 없지만, 새 버전이 하나도 반영되지 않습니다.

원인: maxSurge로 추가된 새 파드가 노드에 자리를 못 잡아 Pending입니다. 파드의 resources.requests(CPU/메모리)를 채울 여유 노드가 없어 스케줄러가 배치에 실패한 것입니다. surge 파드가 Available이 되지 못하니 구버전 파드는 종료되지 않고, 롤아웃이 그 지점에서 정지합니다. progressDeadlineSeconds가 지나면 ProgressDeadlineExceeded 조건이 붙지만 자동 롤백은 일어나지 않습니다.

진단: kubectl get pods로 Pending 파드를 찾고 kubectl describe pod의 Events에서 FailedScheduling, Insufficient cpu 같은 메시지를 확인합니다. kubectl describe deployment의 Conditions에서 Progressing이 ProgressDeadlineExceeded인지, kubectl describe node로 Allocatable 대비 요청 합계가 꽉 찼는지 봅니다.

해결: 근본은 자원 부족이므로 노드를 늘리거나(Cluster Autoscaler), 요청량을 현실화하거나, 배포 중만이라도 maxSurge를 0으로 낮춰 추가 파드 없이 in-place로 교체합니다(대신 교체 중 가용 수가 잠깐 줄어듭니다). 급하면 kubectl rollout undo로 명확히 구버전에 고정한 뒤, 자원을 확보하고 다시 시도합니다. 배포가 스스로 롤백될 거라 믿고 방치하지 않습니다.

💼
실무 맥락
현업 패턴

실무 시나리오: 프로덕션 배포 체크리스트

시니어가 공유한 배포 전 체크리스트:

Kubernetes
# 1. Dry-run으로 변경사항 확인
kubectl apply -f deployment.yaml --dry-run=server -n production
# "deployment.apps/my-app configured (server dry run)"

# 2. diff로 변경 내용 비교
kubectl diff -f deployment.yaml -n production
# -   image: nginx:1.24-alpine
# +   image: nginx:1.25-alpine

# 3. 배포 실행
kubectl apply -f deployment.yaml -n production

# 4. 롤아웃 모니터링
kubectl rollout status deployment/my-app -n production --timeout=5m
# 5분 내 완료 안 되면 문제 상황

# 5. 배포 후 검증
kubectl get pods -n production -l app=my-app
# 모든 Pod가 1/1 Running인지 확인

kubectl logs -n production -l app=my-app --tail=20
# 에러 로그 없는지 확인

배포 실패 시 즉각 롤백 원칙:

"배포 후 5분 내 에러율 상승 시 즉시 롤백, 원인 파악은 롤백 후."

Kubernetes
# 에러 감지 → 즉시 롤백 (원인 파악은 나중에)
kubectl rollout undo deployment/my-app -n production

# 롤백 완료 확인
kubectl rollout status deployment/my-app -n production

# 이후 staging에서 원인 파악

revision 관리 팁:

Kubernetes
# CHANGE-CAUSE annotation으로 배포 이력 문서화
kubectl annotate deployment/my-app \
  kubernetes.io/change-cause="Fix: DB connection pool size increase, JIRA-1234" \
  -n production

# 이력 확인
kubectl rollout history deployment/my-app -n production
# REVISION  CHANGE-CAUSE
# 1         Initial deployment
# 2         Fix: DB connection pool size increase, JIRA-1234

Kubernetes 입문 트랙 5개 모듈을 완료했습니다. 이제 컨테이너 오케스트레이션의 필요성부터 시작해, 클러스터 아키텍처, kubectl 기본 명령어, Pod 생명주기, 그리고 Deployment를 통한 선언적 관리까지 K8s 운영의 핵심 기반을 갖췄습니다.


명령어·단축키 빠른 참조

이 모듈에서 Deployment를 만들고 롤링 업데이트·롤백·스케일링할 때 쓴 kubectl 명령을 모았습니다. 배포 전 --dry-run/diff, 배포 후 rollout status 흐름이 핵심입니다.

명령어/단축키용도자주 쓰는 예
kubectl create deploymentDeployment 즉시 생성... my-app --image=nginx:alpine --replicas=3
kubectl get deployment,replicaset,pod3계층 구조 한눈에 확인READY·UP-TO-DATE·AVAILABLE 비교
kubectl set image deployment/이미지 교체(롤링 업데이트 시작)kubectl set image deployment/my-app nginx=nginx:1.25
kubectl rollout status롤아웃 진행·완료 확인... deployment/my-app --timeout=5m
kubectl rollout history배포 리비전 이력 확인... --revision=2 (특정 리비전 상세)
kubectl rollout undo이전/특정 버전으로 롤백... deployment/my-app --to-revision=1
kubectl scale deployment/레플리카 수 조정kubectl scale deployment/my-app --replicas=5
kubectl annotate ... change-cause배포 이력에 사유 기록kubernetes.io/change-cause="JIRA-1234"
kubectl apply --dry-run=server적용 전 서버 검증실제 반영 없이 유효성만 확인
kubectl diff -fGit YAML과 클러스터 차이 비교kubectl diff -f deployment.yaml
kubectl get pods -w롤링 교체 과정 실시간 관찰-l app=web-app -w (Terminating↔Running)
kubectl describe deploymentConditions·이벤트 확인정체 시 ProgressDeadlineExceeded 확인
kubectl describe podPending 원인(스케줄 실패) 확인EventsFailedScheduling/Insufficient cpu
kubectl logs -l라벨로 파드 로그 일괄 확인kubectl logs -l app=my-app --tail=20
kubectl delete pod파드 삭제로 자가복구 확인ReplicaSet이 즉시 새 파드 생성

관련 모듈로 더 깊이:

다음 모듈 service-types에서는 Deployment로 띄운 파드에 트래픽을 어떻게 연결하는지 다룹니다. ClusterIP, NodePort, LoadBalancer 세 가지 Service 타입과 kube-proxy가 iptables를 통해 트래픽을 라우팅하는 원리를 실습합니다.

지식 확인

퀴즈 — 8문제

Q1

Deployment, ReplicaSet, Pod의 관계를 가장 잘 설명한 것은?

Q2

롤링 업데이트 중 maxUnavailable: 0, maxSurge: 1 설정의 의미는?

Q3

`kubectl rollout undo deployment/my-app`을 실행하면 어떻게 되는가?

Q4

readinessProbe 없이 롤링 업데이트를 했을 때 발생할 수 있는 문제는?

Q5

Pod를 직접(kubectl run) 만들지 않고 Deployment로 관리하는 핵심 이점은?

Q6

바로 직전이 아니라 2단계 전 버전으로 롤백하려면 어떻게 하는가?

Q7

[심화] Deployment의 progressDeadlineSeconds(기본 600초)를 넘도록 롤아웃이 진전되지 않으면 무슨 일이 일어나는가?

Q8

[심화] 롤링 업데이트가 10분째 끝나지 않고, 새 파드 1개가 계속 Pending이며 나머지는 구버전 그대로다. 가장 먼저 확인할 것은?

0 / 8 답변

🧪 실습으로 확인하기

Pod CrashLoopBackOff — 진단과 복구

중급

배포 직후 Pod가 CrashLoopBackOff 상태에 빠졌다. kubectl 명령으로 원인을 진단하고 Deployment를 수정해 정상 상태로 복구한다.

35📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요