Step 05 — 디플로이먼트

학습 목표

  • Deployment 가 ReplicaSet·Pod 를 어떻게 계층적으로 관리하는지 이해한다
  • 스케일(scale)과 롤링 업데이트(rolling update)를 직접 해본다
  • maxSurge/maxUnavailable 이 업데이트 중 가용성을 어떻게 지키는지 관찰한다
  • rollout status/history/undo 로 배포를 추적하고 롤백한다
  • 잘못된 이미지로 배포하면 롤아웃이 멈추는 것을, 그리고 셀렉터 불변 규칙을 재현한다

선행 스텝: Step 04 — 레이블·셀렉터·네임스페이스 예상 소요: 50분


5-1. 왜 Deployment 인가 — Pod 를 직접 만들지 않는 이유

Step 03 에서 만든 Pod 는 죽으면 끝입니다. 아무도 되살려주지 않습니다. 노드가 빠지거나 파드가 크래시하면 그냥 사라집니다. 개수를 4개로 유지하고 싶어도, 버전을 무중단으로 바꾸고 싶어도 손으로는 못 합니다.

Deployment 는 이 문제를 3단 계층으로 풉니다.

Deployment  (원하는 상태 선언: "web 을 4개, 이 이미지로")
   └── ReplicaSet  (개수 보장: "이 템플릿의 파드를 정확히 4개")
          └── Pod, Pod, Pod, Pod  (실제 실행 단위)
  • ReplicaSet 은 "특정 파드 템플릿을 N개 유지"만 담당합니다. 하나 죽으면 즉시 새로 만듭니다.
  • Deployment 는 ReplicaSet 을 여러 벌 두고 갈아끼우면서 버전 전환(롤아웃/롤백) 을 담당합니다.

실무에서 ReplicaSet 을 직접 만드는 일은 거의 없습니다. 항상 Deployment 를 만들고, Deployment 가 ReplicaSet 을 대신 만들게 합니다.

# manifests/deployment.yaml (발췌)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: step05
spec:
  replicas: 4
  selector:
    matchLabels:
      app: web            # 이 Deployment 가 소유할 파드 조건
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: web          # selector 와 반드시 일치
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          readinessProbe:   # 준비된 파드만 "available" 로 카운트
            httpGet: { path: /, port: 80 }
            initialDelaySeconds: 1
            periodSeconds: 3
kubectl apply -f manifests/deployment.yaml
kubectl rollout status deployment/web -n step05
kubectl get deploy,rs,pod -n step05

실행 결과

deployment "web" successfully rolled out

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   4/4     4            4           2s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-7b6659848b   4         4         4       1s

NAME                       READY   STATUS    RESTARTS   AGE
pod/web-7b6659848b-dc5vd   1/1     Running   0          1s
pod/web-7b6659848b-fw9vv   1/1     Running   0          1s
pod/web-7b6659848b-lv6rx   1/1     Running   0          1s
pod/web-7b6659848b-rsg97   1/1     Running   0          1s

내가 만든 건 Deployment 하나인데 ReplicaSet 하나와 Pod 4개가 딸려 나왔습니다. ReplicaSet 이름의 해시(7b6659848b)는 파드 템플릿의 해시이고, 파드에도 pod-template-hash 레이블로 붙습니다.

kubectl get pods -n step05 --show-labels
NAME                   READY   STATUS    RESTARTS   AGE   LABELS
web-7b6659848b-dc5vd   1/1     Running   0          1s    app=web,pod-template-hash=7b6659848b
...

💡 실무 팁: pod-template-hash 는 Kubernetes 가 자동으로 붙입니다. 이 레이블 덕분에 "구버전 파드"와 "신버전 파드"를 서로 다른 ReplicaSet 으로 구분할 수 있고, 롤링 업데이트가 가능해집니다. 여러분이 직접 이 레이블을 건드리면 안 됩니다.

자가 치유를 확인하려면 파드 하나를 지워보세요. 곧바로 새 파드가 생겨 다시 4개가 됩니다.

kubectl delete pod -n step05 -l app=web --field-selector=status.phase=Running | head -1
kubectl get pods -n step05      # 잠시 뒤 다시 4개

5-2. 스케일 — 개수 바꾸기

kubectl scale deployment/web -n step05 --replicas=5
kubectl rollout status deployment/web -n step05
kubectl get deploy web -n step05
deployment "web" successfully rolled out
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    5/5     5            5           18s

replicas 만 바꾸는 건 롤아웃이 아닙니다(파드 템플릿이 그대로라 새 ReplicaSet 이 안 생깁니다). 같은 ReplicaSet 에 파드만 늘거나 줄 뿐입니다. 다시 4개로 줄여 둡니다.

kubectl scale deployment/web -n step05 --replicas=4

💡 실무 팁: 명령형 kubectl scale 은 편하지만, 매니페스트의 replicas 와 달라지면 다음 apply 때 되돌아갑니다. Git 으로 관리한다면 YAML 의 replicas 를 고쳐 apply 하는 게 일관적입니다. (HPA 를 쓰면 replicas 는 아예 HPA 가 관리 — Step 18)


5-3. 롤링 업데이트 — 무중단 버전 전환

파드 템플릿을 바꾸면(이미지·env·리소스 등) Deployment 는 새 ReplicaSet 을 만들어 서서히 교체합니다. 여기서는 환경 변수 하나를 추가해 롤아웃을 트리거합니다(이미지 태그를 바꾸는 것과 원리가 같습니다).

kubectl set env deployment/web -n step05 APP_VERSION=v2
kubectl annotate deployment/web -n step05 \
  kubernetes.io/change-cause="set APP_VERSION=v2" --overwrite
kubectl rollout status deployment/web -n step05

실행 결과 (진행 로그, 핵심만)

Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "web" rollout to finish: 3 of 4 updated replicas are available...
deployment "web" successfully rolled out

이제 ReplicaSet 이 두 개입니다. 구버전은 0개로 줄고 신버전이 4개를 차지합니다.

kubectl get rs -n step05
NAME             DESIRED   CURRENT   READY   AGE
web-7b6659848b   0         0         0       42s    <- 구버전 (비워짐, 이력용으로 남음)
web-7d86f6d776   4         4         4       8s     <- 신버전

maxSurge/maxUnavailable 의 의미 (우리 설정: 둘 다 1, replicas=4)

파라미터이 설정의 효과
maxSurge: 1목표 개수 대비 초과 생성 허용치일시적으로 최대 5개까지
maxUnavailable: 1업데이트 중 부족 허용치항상 최소 3개는 available 유지

즉 롤링 업데이트 내내 서비스 가능한 파드가 3개 아래로 떨어지지 않습니다. 이것이 무중단 배포의 핵심입니다. readinessProbe 가 있어서 "새 파드가 진짜 준비됐을 때만" 다음 교체로 넘어갑니다.

이력을 봅니다.

kubectl rollout history deployment/web -n step05
REVISION  CHANGE-CAUSE
1         kubectl set image deployment/web nginx=nginx:1.27-alpine --namespace=step05 --record=true
2         set APP_VERSION=v2

💡 실무 팁: CHANGE-CAUSEkubernetes.io/change-cause 어노테이션에서 옵니다. 비워두면 "무엇을 바꿨는지" 이력에 안 남아 롤백 판단이 어렵습니다. 배포 파이프라인에서 커밋 해시/버전을 이 어노테이션에 넣으세요. (--record 플래그는 폐지 예정이라 어노테이션 직접 설정을 권장)


5-4. ⚠️ 함정: 잘못된 이미지 → 롤아웃이 멈춘다

존재하지 않는 이미지 태그로 업데이트해 봅시다. 명령은 성공(image updated)하지만 배포는 실패합니다.

kubectl set image deployment/web nginx=nginx:1.27-doesnotexist -n step05
kubectl rollout status deployment/web -n step05 --timeout=25s
Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
error: timed out waiting for the condition       <- 멈췄다

상태를 보면 구버전이 그대로 서비스 중입니다. 이게 RollingUpdate 의 안전장치입니다.

kubectl get deploy,rs -n step05
kubectl get pods -n step05
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   3/4     2            3           81s     <- 3개는 아직 정상 서비스

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-699f57cd74   2         2         0       25s   <- 신버전(불량): 2개 뜨려다 실패
replicaset.apps/web-7b6659848b   0         0         0       80s
replicaset.apps/web-7d86f6d776   3         3         3       46s   <- 이전 정상 버전: 3개 유지

NAME                   READY   STATUS             RESTARTS   AGE
web-699f57cd74-bptck   0/1     ImagePullBackOff   0          25s
web-699f57cd74-r7nn5   0/1     ImagePullBackOff   0          25s
web-7d86f6d776-gpx7q   1/1     Running            0          44s
web-7d86f6d776-t5qn9   1/1     Running            0          46s
web-7d86f6d776-z7h4t   1/1     Running            0          46s

maxUnavailable: 1 덕분에 구버전 3개는 죽지 않았습니다. maxSurge: 1 이라 불량 신버전은 2개까지만 뜨려다 ImagePullBackOff 에 걸려 멈췄습니다. 결과적으로 사용자 트래픽은 살아있는 3개가 계속 처리 합니다. 만약 maxUnavailable 을 크게 잡았다면 구버전을 먼저 다 내려버려 장애가 났을 겁니다.

왜 멈췄는지는 파드 이벤트에 정확히 찍힙니다.

kubectl describe pod -n step05 -l pod-template-hash=699f57cd74 | grep -iE "Failed|Back-off"
Warning  Failed   kubelet  Failed to pull image "nginx:1.27-doesnotexist": ... not found
Warning  Failed   kubelet  Error: ErrImagePull
Normal   BackOff  kubelet  Back-off pulling image "nginx:1.27-doesnotexist"
Warning  Failed   kubelet  Error: ImagePullBackOff

5-5. 롤백 — 직전 정상 리비전으로

멈춘 배포를 되돌립니다. undo 는 직전 리비전으로 되돌아갑니다.

kubectl rollout history deployment/web -n step05     # 3번이 불량
kubectl rollout undo deployment/web -n step05
kubectl rollout status deployment/web -n step05
REVISION  CHANGE-CAUSE
1         kubectl set image ... --record=true
2         set APP_VERSION=v2
3         bad image nginx:1.27-doesnotexist          <- 이걸 버리고

deployment.apps/web rolled back
deployment "web" successfully rolled out            <- 정상 복구

복구 후 파드는 다시 4/4 Running, 불량 ReplicaSet 은 0개로 비워집니다.

kubectl get deploy,rs -n step05
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   4/4     4            4           92s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-699f57cd74   0         0         0       36s   <- 불량, 비워짐
replicaset.apps/web-7d86f6d776   4         4         4       57s   <- 정상 복귀

특정 리비전으로 되돌리려면 --to-revision 을 씁니다. 구버전 ReplicaSet 이 DESIRED 0 으로 남아있기에 롤백이 즉시 가능합니다(보관 개수는 spec.revisionHistoryLimit, 기본 10).

kubectl rollout undo deployment/web -n step05 --to-revision=2

💡 실무 팁: 롤백은 "최근 코드를 배포로 되돌리는 것"이지 Git 을 되돌리는 게 아닙니다. undo 로 급한 불을 끈 뒤에는 반드시 매니페스트/코드도 정상 상태로 맞춰 apply 하세요. 안 그러면 다음 배포에서 다시 불량 버전이 나갑니다. (위 롤백 시 나오는 last-applied-configuration 경고가 바로 이 불일치를 알리는 것)


5-6. ⚠️ 함정: 셀렉터는 불변(immutable)

spec.selector 는 Deployment 를 만든 뒤에는 바꿀 수 없습니다. 셀렉터에 tier: frontend 를 추가해 apply 하면 거부됩니다.

kubectl apply -f manifests/bad-selector.yaml
The Deployment "web" is invalid: spec.selector: Invalid value:
{"matchLabels":{"app":"web","tier":"frontend"}}: field is immutable

왜일까요? 셀렉터를 바꾸면 "어떤 파드가 이 Deployment 소유인가"의 기준이 바뀌어, 기존 파드가 고아가 되거나 소유권이 꼬입니다. Kubernetes 는 아예 변경을 막습니다.

⚠️ 함정: 셀렉터를 바꿔야 한다면 방법은 하나 — Deployment 를 지우고 새로 만드는 것뿐입니다(kubectl delete deploy web → 새 매니페스트로 apply). 그래서 셀렉터는 처음에 신중히 정해야 합니다. 또한 template.metadata.labels 는 반드시 selector.matchLabels포함해야 합니다. 안 그러면 생성 자체가 거부됩니다.


정리

개념한 줄 요약대표 명령
계층 구조Deployment → ReplicaSet → Podkubectl get deploy,rs,pod
스케일개수만 변경(롤아웃 아님)kubectl scale --replicas=N
롤링 업데이트템플릿 변경 시 새 RS 로 점진 교체kubectl set image/env, rollout status
maxSurge/maxUnavailable업데이트 중 가용성 보장(매니페스트 strategy)
이력·롤백리비전 추적과 되돌리기rollout history/undo
셀렉터 불변생성 후 변경 불가(재생성만 가능)

연습 과제 → challenge.md

다음 단계

Step 06 — 서비스와 DNS


실습 파일

이 스텝은 매니페스트 2개와 실행 스크립트 1개로 구성됩니다. 먼저 manifests/deployment.yamlapply 해서 step05 네임스페이스와 web Deployment(replicas=4)를 띄우고, 스케일 → 롤링 업데이트 → 불량 이미지 → 롤백 순서로 진행합니다. 마지막에 manifests/bad-selector.yaml 을 apply 해서 셀렉터 불변 규칙을 몸으로 확인하는 흐름이며, commands.sh 는 이 전 과정을 강의 순서 그대로 옮겨 담은 참고용 스크립트입니다.

manifests/deployment.yaml

이 스텝의 출발점이 되는 매니페스트로, 5-1 절 맨 처음에 kubectl apply -f manifests/deployment.yaml 로 적용합니다. 한 파일 안에 --- 로 두 오브젝트를 담았습니다 — 먼저 kind: Namespacestep05 네임스페이스를 만들고, 이어서 그 안에 kind: Deploymentweb 을 만듭니다. 덕분에 kubectl create namespace 를 따로 칠 필요가 없고, 실습이 끝나면 kubectl delete namespace step05 한 방으로 전부 정리됩니다.

  • spec.replicas: 4 — ReplicaSet 이 유지할 파드 개수입니다. 5-2 절에서 kubectl scale --replicas=5 로 바꿔보지만, 템플릿이 그대로이므로 새 ReplicaSet 은 생기지 않습니다.
  • spec.selector.matchLabels.app: webspec.template.metadata.labels.app: web똑같아야 합니다. 템플릿 레이블이 셀렉터를 포함하지 않으면 생성 자체가 거부되고, 셀렉터는 만든 뒤 바꿀 수 없습니다(5-6 절).
  • strategy.rollingUpdatemaxSurge: 1maxUnavailable: 1 이 replicas=4 와 곱해져 "일시적으로 최대 5개, 최소 available 3개" 라는 롤아웃 규칙을 만듭니다. 5-4 절에서 불량 이미지를 넣어도 구버전 3개가 살아남는 이유가 정확히 이 두 줄입니다.
  • containers[0]name: nginx, image: nginx:1.27-alpine 이고 ports.containerPort: 80 을 선언합니다. 5-4 절에서 kubectl set image deployment/web nginx=... 로 이미지를 바꿀 때 지정하는 nginx= 가 바로 이 컨테이너 이름입니다.
  • readinessProbehttpGet: path: /, port: 80 + initialDelaySeconds: 1 + periodSeconds: 3 — 새 파드가 80 포트로 응답해야 비로소 available 로 집계됩니다. 이 프로브가 없으면 컨테이너가 뜨자마자 "준비됨"으로 취급돼, 실제로는 아직 못 받는 파드로 트래픽이 흘러가고 롤아웃이 너무 빨리 넘어갑니다.
# manifests/deployment.yaml
# Namespace + Deployment 를 한 파일에 담았습니다.
apiVersion: v1
kind: Namespace
metadata:
  name: step05
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: step05
  labels:
    app: web
spec:
  replicas: 4
  # selector: 이 Deployment 가 소유할 파드를 고르는 조건 (생성 후 변경 불가)
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # 목표 대비 최대 1개 더 (일시적으로 5개까지)
      maxUnavailable: 1    # 동시에 최대 1개까지 없어도 됨 (최소 3개 유지)
  template:
    metadata:
      labels:
        app: web           # selector.matchLabels 와 반드시 일치해야 함
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80
          readinessProbe:      # 준비된 파드만 롤아웃 진행에 카운트
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 1
            periodSeconds: 3

manifests/bad-selector.yaml

일부러 실패하도록 만든 매니페스트입니다. 5-6 절에서 kubectl apply -f manifests/bad-selector.yaml 로 적용하면 성공하는 게 아니라 다음 에러가 나야 정상입니다.

The Deployment "web" is invalid: spec.selector: Invalid value:
{"matchLabels":{"app":"web","tier":"frontend"}}: field is immutable
  • deployment.yaml 과 같은 name: web, namespace: step05 를 쓰기 때문에 기존 Deployment 를 수정하는 apply 가 됩니다. 그런데 selector.matchLabelstier: frontend추가했으므로 불변 필드 변경 시도가 되어 API 서버가 거부합니다.
  • template.metadata.labels 에도 tier: frontend 를 같이 넣어 두었습니다. 즉 "템플릿 레이블과 셀렉터를 짝 맞춰 제대로 썼는데도" 거부된다는 점이 학습 포인트입니다 — 문법이 틀려서가 아니라 셀렉터라는 필드 자체가 불변이기 때문입니다.
  • 셀렉터를 바꾸려면 kubectl delete deployment web -n step05 로 지우고 새로 만드는 수밖에 없습니다. 이때는 같은 파일을 그대로 apply 해도 성공합니다(신규 생성이므로).
  • 이 파일에는 deployment.yaml 과 달리 kind: Namespace 오브젝트가 없고, strategy·readinessProbe·portsmetadata.labels 도 빠져 있습니다. replicas: 4selector + template 처럼 함정 재현에 필요한 부분만 남긴 것입니다.
  • 주의: 실습 순서상 이 파일은 deployment.yaml 이 이미 적용된 상태에서 apply 해야 의미가 있습니다. step05 네임스페이스는 있지만 web Deployment 가 없는 상태에서 적용하면 신규 생성이라 그냥 성공해버려 함정을 재현하지 못하고, 아무것도 없는 빈 클러스터에서는 네임스페이스가 없어 namespaces "step05" not found 라는 엉뚱한 에러가 납니다.
# manifests/bad-selector.yaml
# 셀렉터 불변 규칙 재현용: 기존 web Deployment 의 selector 에 키를 추가해 apply 하면
# "field is immutable" 에러로 거부됩니다. (일부러 실패하는 예제)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: step05
spec:
  replicas: 4
  selector:
    matchLabels:
      app: web
      tier: frontend      # <-- 셀렉터에 키 추가 = 변경 시도 → 거부됨
  template:
    metadata:
      labels:
        app: web
        tier: frontend
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine

commands.sh

강의 5-1 ~ 5-6 절에 나오는 kubectl 명령을 등장 순서 그대로 정리한 스크립트입니다. 한 번에 통째로 실행하기보다, 섹션 주석(--- 5-3. 롤링 업데이트 --- 등)을 따라가며 한 줄씩 붙여넣고 출력을 관찰하도록 만들어졌습니다.

  • 첫 줄은 #!/usr/bin/env bash 이고, 이어지는 export PATH="/opt/homebrew/bin:$PATH" 는 Homebrew 로 설치한 kubectl 을 찾기 위한 macOS(Apple Silicon) 전용 줄입니다. Linux 나 Intel Mac 이라면 이 줄은 없어도 무방합니다.
  • 그다음 kubectl config current-context 로 컨텍스트가 kind-learn 인지 먼저 확인합니다. 다른 클러스터(운영 등)에 붙어 있으면 아래 명령들이 엉뚱한 곳에 적용되니 반드시 확인하고 시작하세요.
  • 5-4 절의 kubectl describe pod -n step05 -l pod-template-hash=699f57cd74 에 박혀 있는 해시는 강의 당시 예시값입니다. 파드 템플릿 해시는 매번 달라지므로, kubectl get pods -n step05 로 실제 불량 ReplicaSet 의 해시를 확인해 바꿔 넣어야 합니다.
  • kubectl set image ... nginx:1.27-doesnotexist 는 존재하지 않는 태그를 일부러 넣는 줄이라 rollout status --timeout=25stimed out 으로 끝나는 게 정상입니다. 실패가 아니라 의도된 결과입니다.
  • kubectl annotate ... kubernetes.io/change-cause=... --overwrite 를 롤아웃 유발 명령과 나란히 둔 이유는, 어노테이션을 같은 시점에 걸어야 그 리비전의 CHANGE-CAUSE 로 남기 때문입니다.
  • ⚠️ 마지막 줄 kubectl delete namespace step05파괴적 명령입니다. 이 네임스페이스의 Deployment·ReplicaSet·Pod 가 전부 삭제되고 rollout 이력도 사라지므로, 실습을 완전히 마친 뒤에만 실행하세요.
#!/usr/bin/env bash
# Step 05 — 디플로이먼트
# README 순서 그대로. 한 줄씩 실행하며 rollout 을 관찰하세요.
export PATH="/opt/homebrew/bin:$PATH"

kubectl config current-context   # kind-learn 이어야 함

# --- 5-1. 배포 & 계층 구조 확인 ---
kubectl apply -f manifests/deployment.yaml
kubectl rollout status deployment/web -n step05
kubectl get deploy,rs,pod -n step05
kubectl get pods -n step05 --show-labels          # pod-template-hash 확인
# 자가 치유: 파드 하나 지워도 다시 4개로 회복
kubectl delete pod -n step05 -l app=web --field-selector=status.phase=Running | head -1
kubectl get pods -n step05

# --- 5-2. 스케일 (롤아웃 아님) ---
kubectl scale deployment/web -n step05 --replicas=5
kubectl rollout status deployment/web -n step05
kubectl get deploy web -n step05
kubectl scale deployment/web -n step05 --replicas=4   # 원복

# --- 5-3. 롤링 업데이트 (env 변경으로 트리거) ---
kubectl set env deployment/web -n step05 APP_VERSION=v2
kubectl annotate deployment/web -n step05 kubernetes.io/change-cause="set APP_VERSION=v2" --overwrite
kubectl rollout status deployment/web -n step05
kubectl get rs -n step05                          # RS 두 개 (old 0, new 4)
kubectl rollout history deployment/web -n step05

# --- 5-4. 함정: 잘못된 이미지 → 롤아웃 멈춤 ---
kubectl set image deployment/web nginx=nginx:1.27-doesnotexist -n step05
kubectl annotate deployment/web -n step05 kubernetes.io/change-cause="bad image nginx:1.27-doesnotexist" --overwrite
kubectl rollout status deployment/web -n step05 --timeout=25s   # timed out (멈춤)
kubectl get deploy,rs -n step05                   # 구버전은 살아있음
kubectl get pods -n step05                        # ImagePullBackOff
kubectl describe pod -n step05 -l pod-template-hash=699f57cd74 | grep -iE "Failed|Back-off"

# --- 5-5. 롤백 ---
kubectl rollout history deployment/web -n step05
kubectl rollout undo deployment/web -n step05
kubectl rollout status deployment/web -n step05
kubectl get deploy,rs -n step05
# 특정 리비전으로: kubectl rollout undo deployment/web -n step05 --to-revision=2

# --- 5-6. 함정: 셀렉터 불변 ---
kubectl apply -f manifests/bad-selector.yaml       # field is immutable 에러

# --- 정리 ---
kubectl delete namespace step05