서비스가 간헐적으로 끊기는데 파드는 Running으로 보이고 원인은 보이지 않습니다. 온콜 담당자가 Pod의 Pending, Running, CrashLoopBackOff, Terminating 단계를 이해하지 못하면 재시작이 정상인지 장애인지 판단하기 어렵습니다. Pod 생명주기는 컨테이너 장애를 Kubernetes 관점에서 읽는 기준입니다.
Pod 생명주기
배포 명령을 실행했다. Pod가 생겼다. 그런데 몇 초 후 보니 CrashLoopBackOff다. RESTARTS 숫자가 1, 2, 5, 10... 계속 올라간다. 처음 이 상황을 마주한 사람은 당황한다. 무슨 뜻인지 모르면 Pod를 지웠다가 다시 만들기를 반복하다가 결국 시니어한테 묻게 된다. Pod는 단순히 "켜짐/꺼짐"이 아니다. 생성부터 종료까지 명확한 상태를 거치고, 각 상태는 K8s가 지금 무엇을 하고 있는지를 정확히 알려준다. 상태를 읽을 줄 알면, 어떤 에러가 나도 어느 단계에서 무엇을 확인해야 할지 즉시 알 수 있다.
Pod의 상태 머신을 이해하고, 실무에서 가장 자주 만나는 Pod 장애를 체계적으로 진단하고 해결합니다.
- 1Pod 상태 전환(Pending, Running, Succeeded/Failed/Unknown)을 설명할 수 있다
- 2Init Container와 메인 컨테이너의 실행 순서를 설명할 수 있다
- 3liveness probe와 readiness probe의 역할과 차이를 설명할 수 있다
- 4logs, describe, exec 3단계로 CrashLoopBackOff를 진단할 수 있다
- 5ImagePullBackOff를 원인별로 진단하고 해결할 수 있다
- 6terminationGracePeriodSeconds와 preStop hook으로 Pod 종료 프로세스를 제어할 수 있다
실습은 pod-lab 네임스페이스에서 진행합니다. 각 명령어에 -n pod-lab을 붙이거나, kubectl config set-context --current --namespace=pod-lab으로 기본값을 변경하세요.
kubectl cluster-infokubectl get nodeskubectl create namespace pod-lab 2>/dev/null || echo 'namespace already exists'Pod 상태 머신 — 5가지 Phase
온콜 중 "파드가 이상하다"는 알림이 왔습니다. kubectl get pods를 실행했더니 어떤 파드는 Pending, 어떤 건 CrashLoopBackOff, 어떤 건 0/1 Running입니다. 각 상태가 무엇을 뜻하는지 모르면 재시작이 정상인지 장애인지, 어디를 먼저 봐야 하는지 판단할 수 없습니다. Pod의 Phase는 Kubernetes가 현재 무엇을 하고 있는지 알려주는 신호등입니다. 각 Phase를 이해하면 어떤 에러가 나도 어느 단계에서 무엇을 확인해야 할지 즉시 파악할 수 있습니다.
Pod는 생성부터 종료까지 명확한 Phase를 거칩니다. kubectl get pods의 STATUS 컬럼이 이 상태를 보여줍니다.
확대
5가지 Phase
kubectl apply →
Pending : 스케줄링 대기 또는 이미지 pull 중
↓ (스케줄 + 이미지 pull + Init Container 완료)
Running : 하나 이상의 컨테이너가 실행 중
↓
Succeeded : 모든 컨테이너가 성공적으로 종료 (exit 0)
Failed : 하나 이상의 컨테이너가 실패 종료 (exit != 0)
Unknown : 노드와 통신 불가 (노드 장애 시)
Pending — 이 시간 동안 무슨 일이?
kubectl get pod my-pod
# NAME READY STATUS RESTARTS AGE
# my-pod 0/1 Pending 0 30s
# Pending인 이유 파악
kubectl describe pod my-pod | grep -A15 Events
# Events:
# Warning FailedScheduling ...
# 0/1 nodes are available: 1 Insufficient cpu.
# ← 노드 CPU 부족으로 스케줄 불가
- kubectl get pods에서 STATUS 열을 먼저 확인 — Pending/Running/Completed 외에 CrashLoopBackOff, ImagePullBackOff, OOMKilled, Evicted 중 하나이면 각각 다른 원인으로 접근 방법이 다름
- RESTARTS 수치 기준: 3회 미만은 일시적 문제, 5회 이상은 kubectl logs <pod> --previous로 이전 크래시 로그 확인 필수, 20회 이상은 liveness probe timeout 설정 또는 메모리 limits 부족 가능성 높음
- STATUS=Running이지만 READY=0/1이면 → readinessProbe 실패로 트래픽 차단 중. STATUS=Pending이고 RESTARTS=0이면 → 스케줄링 단계에서 막힌 것으로 kubectl describe pod의 Events로 원인 확인
Pending의 주요 원인:
- 리소스 부족 (CPU/메모리 requests를 충족하는 노드 없음)
- nodeSelector/affinity 조건에 맞는 노드 없음
- PersistentVolumeClaim 바인딩 대기
- 이미지 pull 중 (Pulling 이벤트)
Pending 동안 Init Container가 있으면 앱 컨테이너보다 먼저 순차 실행됩니다(STATUS가 Init:0/2처럼 표시). DB 마이그레이션·설정 파일 다운로드·볼륨 권한 설정 같은 초기화를 담당하며, 하나라도 실패(exit≠0)하면 restartPolicy에 따라 재시도하고 앱 컨테이너는 시작되지 않습니다.
확대
Running — 정말 정상인가?
kubectl get pod my-pod
# NAME READY STATUS RESTARTS AGE
# my-pod 0/1 Running 3 5m
# ↑
# READY가 0/1이면 컨테이너가 실행 중이지만 readiness probe 실패
# 서비스 트래픽을 받지 못하는 상태
READY 컬럼 1/1이어야 실제로 정상입니다. 0/1은 Running처럼 보이지만 트래픽을 받지 못합니다.
Exit Code로 실패 원인 파악
kubectl describe pod my-pod | grep "Exit Code"
# Exit Code: 1 ← 앱 자체 에러 (코드 버그, 설정 오류 등)
# Exit Code: 137 ← SIGKILL (OOM Killer 또는 강제 종료)
# Exit Code: 143 ← SIGTERM (graceful shutdown, 정상)
# Exit Code: 125 ← docker/container 실행 자체 실패
# Exit Code: 126 ← 명령어 실행 권한 없음
# Exit Code: 127 ← 명령어를 찾을 수 없음 (잘못된 CMD)
kubectl apply 한 줄에서 Running까지 — 파드가 뜨는 전체 흐름
kubectl apply 한 번에 파드가 뜨기까지 — 요청부터 Running까지 9단계
kubectl apply -f pod.yaml 한 줄. Enter를 누르면 잠시 뒤 kubectl get pods에 파드가 Running 1/1로 뜹니다. 이 짧은 사이에 클러스터 안에서는 인증·인가 → API Server 검증·저장 → 스케줄링 → kubelet 감지 → 이미지 pull → 네트워크·볼륨 구성 → 컨테이너 시작 → probe 통과 → Endpoints 등록이 순서대로 일어납니다. 파드가 Pending에서 안 넘어가거나 ContainerCreating에서 멈추거나 ImagePullBackOff가 뜰 때, 이 흐름을 알면 "지금 어느 단계에서 막혔나"를 한눈에 좁힐 수 있습니다. 사실 각 상태 이름은 이 파이프라인이 '멈춘 지점'을 가리키는 라벨입니다.
[내 PC] kubectl apply -f pod.yaml
│
① 인증(AuthN) → 인가(AuthZ) → Admission 검사
│
② API Server가 스키마 검증 → etcd에 '원하는 상태(desired state)' 저장
│ → 여기까지면 파드 객체는 생겼지만 아직 어느 노드에도 없음
│
③ Scheduler가 조건 맞는 노드 선택
│ (리소스 requests·nodeSelector·affinity·taint/toleration)
│
④ 선택된 노드의 kubelet이 자기에게 배정된 파드를 감지
│
⑤ CRI(containerd)로 이미지 pull
│
⑥ CNI 플러그인이 파드 IP 구성 + 볼륨 attach/mount
│
⑦ 컨테이너 생성·시작 (Init Container 먼저 순차 완료 → 앱 컨테이너)
│
⑧ probe 통과 (startup → readiness → liveness)
│
⑨ READY 1/1 → Service Endpoints 등록 → 트래픽 수신
▼
[결과] STATUS=Running, READY 1/1
각 단계가 하는 일과, 막히면 어떤 상태로 나타나나:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 인증·인가 | kubeconfig로 신원 확인(AuthN) → RBAC 권한 확인(AuthZ) → Admission Webhook 통과 | error: ... is forbidden / Unauthorized — 파드 객체가 아예 안 생김 |
| ② 검증·저장 | API Server가 매니페스트 스키마를 검증하고 etcd에 desired state 기록 | 필드 오타·잘못된 값 → error validating data로 apply 거부 |
| ③ 스케줄링 | Scheduler가 리소스·nodeSelector·affinity·taint를 만족하는 노드를 고름 | Pending — Events에 FailedScheduling(Insufficient cpu/memory, taint 불일치, PVC 미바인딩) |
| ④ kubelet 감지 | 배정된 노드의 kubelet이 파드 스펙을 받아 기동 시작 | 노드가 NotReady면 Pending 지속 |
| ⑤ 이미지 pull | kubelet이 CRI로 컨테이너 이미지를 내려받음 | ErrImagePull → 반복 실패 시 ImagePullBackOff(이미지 없음·태그 오타·레지스트리 인증) |
| ⑥ 네트워크·볼륨 | CNI가 파드 IP를 부여하고 볼륨을 attach/mount | ContainerCreating 지속 — FailedCreatePodSandBox(CNI), FailedMount(볼륨·PVC) |
| ⑦ 컨테이너 시작 | Init Container를 순차 실행 후 앱 컨테이너 실행 | Init 실패 → Init:Error·Init:CrashLoopBackOff, 앱이 exit≠0 → CrashLoopBackOff |
| ⑧ probe | startup 통과 후 readiness·liveness 감시 시작 | readiness 실패 → Running인데 0/1 · liveness 실패 → kubelet이 재시작(CrashLoopBackOff) |
| ⑨ Endpoints 등록 | READY가 된 파드 IP를 Service Endpoints에 추가 | READY 1/1인데 트래픽이 안 오면 Service selector·포트 불일치 의심 |
즉 파드의 STATUS는 곧 이 파이프라인이 멈춘 지점의 이름입니다 — Pending은 ③ 스케줄링 이전, ContainerCreating은 ⑥ 네트워크·볼륨, ImagePullBackOff는 ⑤ 이미지, CrashLoopBackOff는 ⑦·⑧(앱 크래시 또는 probe 실패)입니다. 그래서 어떤 파드 장애든 kubectl describe pod의 Events를 보면 이 단계 중 어디서 무엇을 기다리다 실패했는지가 시간순으로 찍혀 있고, 그게 진단의 출발점입니다.
Probe — 컨테이너 건강 감시
파드가 Running이고 로그도 정상인데 간헐적으로 요청이 타임아웃되는 상황이 있습니다. 애플리케이션 내부에서 데드락이 걸렸거나, DB 연결은 됐지만 응답을 못 하는 상태일 수 있습니다. Kubernetes는 컨테이너가 실행 중이라는 것만으로 정상이라고 판단합니다. Probe는 이 빈틈을 메웁니다. kubelet이 주기적으로 컨테이너의 실제 상태를 확인해 장애를 자동으로 복구하거나 트래픽을 차단합니다. liveness와 readiness 두 가지 Probe를 올바르게 설정하면 온콜 없이도 많은 장애가 자동으로 처리됩니다.
kubelet은 컨테이너의 상태를 주기적으로 확인합니다. 이를 위해 두 가지 Probe를 설정할 수 있습니다.
liveness probe — 살아있는가?
컨테이너가 살아있는지 확인합니다. 실패하면 컨테이너 재시작.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # 컨테이너 시작 후 첫 체크까지 대기
periodSeconds: 10 # 체크 간격
failureThreshold: 3 # 연속 3번 실패 시 재시작
# 데드락에 걸린 앱 → liveness probe 실패 → kubelet이 컨테이너 재시작
# → 새 컨테이너가 정상적으로 동작 재개
readiness probe — 트래픽 받을 준비가 됐는가?
컨테이너가 서비스 트래픽을 받을 준비가 됐는지 확인합니다. 실패하면 Service 엔드포인트에서 제거 (재시작 안 함).
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
# 앱이 DB 연결 중 → readiness probe 실패
# → Service가 이 Pod로 트래픽 전달 안 함
# → DB 연결 완료 → readiness 성공 → 트래픽 수신 시작
startup probe — 느린 앱을 위해
앱이 시작되기까지 오래 걸리는 경우, liveness probe가 너무 일찍 실패하지 않도록 합니다.
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 30번 × 10초 = 최대 300초 대기
periodSeconds: 10
# startupProbe 성공 후에 liveness/readiness probe 시작
liveness vs readiness 요약
| liveness | readiness | |
|---|---|---|
| 실패 시 | 컨테이너 재시작 | 엔드포인트 제거 (트래픽 차단) |
| 용도 | 데드락, 무한루프 감지 | 준비 전 트래픽 차단 |
| 언제 쓰나 | 항상 권장 | DB 연결, 캐시 워밍업 등 |
실습: Pod 상태 변화 직접 경험하기
실습 1: Pending 상태 재현 (리소스 초과 요청)
cat <<EOF > /tmp/pending-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: resource-hungry
namespace: pod-lab
spec:
containers:
- name: app
image: nginx:alpine
resources:
requests:
memory: "100Ti" # 터무니없이 큰 메모리 요청
cpu: "1000"
EOF
kubectl apply -f /tmp/pending-pod.yaml
# 상태 확인
kubectl get pod resource-hungry -n pod-lab
# NAME READY STATUS RESTARTS AGE
# resource-hungry 0/1 Pending 0 30s
# 원인 확인
kubectl describe pod resource-hungry -n pod-lab | grep -A5 Events
# Warning FailedScheduling ... 0/1 nodes are available: 1 Insufficient memory.
실습 파드 삭제 — 현재 컨텍스트가 프로덕션이면 실제 파드 삭제
안전한 실행 조건: kubectl config current-context 로 개발 클러스터(minikube 등)인지 확인 후 실행하세요.
실행 전 반드시 확인
- kubectl config current-context 로 현재 컨텍스트가 개발 클러스터인지 확인
- kubectl get pod resource-hungry -n pod-lab 로 삭제 대상 파드 이름 확인
kubectl delete pod resource-hungry -n pod-lab위 항목을 모두 확인한 후 복사할 수 있습니다
kubectl delete pod resource-hungry -n pod-lab
실습 2: CrashLoopBackOff 재현 및 진단
cat <<EOF > /tmp/crash-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: crash-test
namespace: pod-lab
spec:
containers:
- name: crash-app
image: alpine
command: ["sh", "-c", "echo 'Starting...'; echo 'DB_HOST is: '$DB_HOST; exit 1"]
env:
- name: DB_HOST
value: ""
EOF
kubectl apply -f /tmp/crash-pod.yaml
# 잠시 후 상태 확인
sleep 15
kubectl get pod crash-test -n pod-lab
# NAME READY STATUS RESTARTS AGE
# crash-test 0/1 CrashLoopBackOff 2 30s
# 진단 Step 1: 현재 로그
kubectl logs crash-test -n pod-lab
# Starting...
# DB_HOST is:
# ← DB_HOST가 비어있어서 exit 1
# 진단 Step 2: 이전 컨테이너 로그 (이미 죽었을 때)
kubectl logs crash-test -n pod-lab --previous
# 진단 Step 3: describe로 재시작 횟수와 이벤트
kubectl describe pod crash-test -n pod-lab | grep -E "Restart Count|Exit Code|Events" -A5
kubectl delete pod crash-test -n pod-lab
실습 3: ImagePullBackOff 재현
cat <<EOF > /tmp/bad-image-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: bad-image
namespace: pod-lab
spec:
containers:
- name: app
image: nginx:this-tag-does-not-exist-xyz
EOF
kubectl apply -f /tmp/bad-image-pod.yaml
sleep 10
kubectl get pod bad-image -n pod-lab
# NAME READY STATUS RESTARTS AGE
# bad-image 0/1 ImagePullBackOff 0 10s
# 원인 확인
kubectl describe pod bad-image -n pod-lab | grep -A10 Events
# Warning Failed ... Failed to pull image "nginx:this-tag-does-not-exist-xyz":
# rpc error: ... manifest for nginx:this-tag-does-not-exist-xyz not found
kubectl delete pod bad-image -n pod-lab
실습 4: Probe 동작 확인
cat <<EOF > /tmp/probe-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: probe-test
namespace: pod-lab
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 3
EOF
kubectl apply -f /tmp/probe-pod.yaml
# 시작 직후 (아직 readiness 미통과)
kubectl get pod probe-test -n pod-lab
# NAME READY STATUS RESTARTS AGE
# probe-test 0/1 Running 0 3s
# 잠시 후 (readiness 통과)
sleep 10
kubectl get pod probe-test -n pod-lab
# NAME READY STATUS RESTARTS AGE
# probe-test 1/1 Running 0 10s
kubectl delete pod probe-test -n pod-lab
상황
kubectl get pods -n production
# NAME READY STATUS RESTARTS AGE
# api-server-0 0/1 CrashLoopBackOff 8 15m
RESTARTS가 계속 올라가고 있습니다.
진단 3단계
Step 1: logs — 앱이 왜 죽는지 확인
# 현재 컨테이너 로그 (죽기 전 출력)
kubectl logs api-server-0 -n production
# 이미 재시작됐다면 이전 컨테이너 로그
kubectl logs api-server-0 -n production --previous
# Error: connect ECONNREFUSED 10.96.100.1:5432
# ← DB 연결 실패로 앱이 exit 1
Step 2: describe — 재시작 패턴과 이벤트 확인
kubectl describe pod api-server-0 -n production
# Containers:
# api-server:
# State: Waiting
# Reason: CrashLoopBackOff
# Last State: Terminated
# Reason: Error
# Exit Code: 1
# Started: Mon, 15 Jan 2024 10:23:45
# Finished: Mon, 15 Jan 2024 10:23:46 ← 1초만에 죽음
# Restart Count: 8
Step 3: exec — 내부 환경 확인 (컨테이너가 잠깐이라도 살아있을 때)
# 컨테이너가 잠깐 실행되는 순간 exec 시도
kubectl exec -it api-server-0 -n production -- sh
# 또는 sleep으로 살아있게 만들어서 디버깅
# (임시방편: command를 sleep infinity로 override)
kubectl debug -it api-server-0 -n production --copy-to=debug-pod --set-image=api-server=alpine -- sleep infinity
kubectl exec -it debug-pod -n production -- sh
원인별 해결
| 로그 메시지 | 원인 | 해결 |
|---|---|---|
ECONNREFUSED <db-host> | DB 연결 실패 | DB 서비스/Pod 상태 확인 |
Cannot find module | 앱 빌드 오류 | 이미지 재빌드 |
Permission denied | 파일/소켓 권한 | securityContext 확인 |
exec format error | CPU 아키텍처 불일치 | 이미지 플랫폼 확인 |
OOMKilled (exit 137) | 메모리 부족 | resources.limits.memory 증가 |
| 로그 없음 | CMD 자체가 없거나 즉시 종료 | Dockerfile CMD/ENTRYPOINT 확인 |
핵심: CrashLoopBackOff에서 kubectl logs --previous가 90%의 원인을 알려줍니다.
심화 — 모든 CrashLoopBackOff가 '앱 크래시'는 아니다
심화: 앱이 죽은 건가, kubelet이 죽인 건가
"CrashLoopBackOff = 앱이 스스로 죽는다"까지만 알면, 정작 logs --previous에 아무 에러도 없을 때 길을 잃습니다. BackOff는 '재시작 간격을 늘리는 상태'일 뿐, 왜 컨테이너가 끝났는지는 두 가지로 완전히 다릅니다.
- (A) 앱이 스스로 종료: 설정 오류·의존성 실패로 프로세스가 exit 1 같은 0이 아닌 코드로 끝납니다.
logs --previous에 스택트레이스나 에러 메시지가 남습니다. 앞 TroubleCase가 이 경우입니다. - (B) kubelet이 컨테이너를 죽임: 앱은 멀쩡히 기동 중인데 liveness probe가 실패해 kubelet이 SIGTERM(→시간 초과 시 SIGKILL)을 보냅니다. 앱 코드는 에러를 안 냈으니 로그엔 정상 기동 메시지가 중간에 뚝 끊긴 모습만 남고, Exit Code는 143(SIGTERM)이나 137(SIGKILL)이 됩니다.
특히 흔한 함정은 느린 기동 + 너무 조인 liveness입니다. 기동에 40초 걸리는 앱에 initialDelaySeconds: 10을 걸면, /healthz가 아직 안 뜬 상태에서 probe가 연속 실패하고 kubelet이 죽입니다. 재시작해도 또 기동 중에 죽으니 무한 반복 — STATUS는 (A)와 똑같이 CrashLoopBackOff지만 원인도 해결책도 정반대입니다.
둘을 가르는 결정적 단서는 Events와 Exit Code입니다. Events에 Liveness probe failed/Killing/Unhealthy가 보이고 Last State Exit Code가 143·137이면 (B), --previous 로그에 앱 에러가 있고 Exit Code가 1 등이면 (A)입니다. 참고로 readiness 실패는 재시작을 유발하지 않으므로 CrashLoop의 범인이 될 수 없습니다 — 트래픽만 끊습니다.
상황: RESTARTS는 계속 오르는데 kubectl logs --previous를 봐도 스택트레이스나 exit 에러가 없습니다. 정상 부팅 메시지가 기동 도중에 뚝 끊겨 있을 뿐입니다. "logs가 90%를 알려준다"는 조언이 이번엔 통하지 않습니다 — 볼 에러 자체가 없기 때문입니다.
원인: 앱이 스스로 죽은 게 아니라, 앱이 뜨기도 전에 liveness probe가 실패해 kubelet이 컨테이너를 죽인 것입니다. 기동에 걸리는 시간보다 initialDelaySeconds가 짧아, /healthz가 준비되기 전에 probe가 failureThreshold만큼 연속 실패했고, kubelet이 SIGTERM(→SIGKILL)으로 재시작시켰습니다. 재시작해도 매번 기동 중에 죽으니 CrashLoop가 반복됩니다.
진단: 로그가 아니라 Events와 종료 상태를 봅니다.
kubectl describe pod <pod> -n <ns> | grep -E "Liveness|Killing|Unhealthy|Last State|Reason|Exit Code" -A1
# Warning Unhealthy Liveness probe failed: Get http://.../healthz: connection refused
# Normal Killing Container app failed liveness probe, will be restarted
# Last State: Terminated Reason: Error Exit Code: 143 ← SIGTERM (앱 자체 에러 아님)
Events에 Liveness probe failed/Killing이 있고 Exit Code가 143·137이면서 --previous 로그에 앱 에러가 없으면 (B) 유형으로 확정입니다.
해결: 먼저 앱의 실제 기동 시간을 재고, 정석은 startupProbe 추가입니다. startupProbe가 통과하기 전까지 liveness/readiness를 유예하고, failureThreshold × periodSeconds를 실제 기동 상한보다 넉넉히 잡습니다 — 그러면 느린 부팅 동안 kubelet이 죽이지 않고, 기동이 끝난 뒤부터 liveness가 감시합니다. startupProbe 없이 liveness의 initialDelaySeconds만 늘려도 임시로 되지만, 기동 시간이 유동적이면 startupProbe가 더 깔끔합니다. 또 하나 — liveness 엔드포인트는 '가벼운 생존 신호'여야 합니다. 여기서 DB 같은 외부 의존성까지 확인하면, 의존성이 잠깐 흔들릴 때 멀쩡한 파드가 죄다 재시작되는 폭풍이 납니다(그 검사는 readiness의 몫). 이 경계는 Liveness, Readiness, Startup Probe 헬스 체크 설정에서 더 깊이 다룹니다.
Pending 상태 재현 — 리소스 초과 요청
kubectl run resource-hungry --image=nginx:alpine --requests='memory=100Ti,cpu=1000' -n pod-lab
sleep 5 && kubectl get pod resource-hungry -n pod-lab예상 출력
NAME READY STATUS RESTARTS AGE resource-hungry 0/1 Pending 0 ...
Pending 원인 파악 — Events 확인
kubectl describe pod resource-hungry -n pod-lab | grep -A 3 'Warning'예상 출력
Warning FailedScheduling ... 0/1 nodes are available: 1 Insufficient memory.
CrashLoopBackOff 재현 및 진단
kubectl run crash-test --image=alpine --command -- sh -c 'echo starting; exit 1' -n pod-lab
sleep 20 && kubectl get pod crash-test -n pod-lab예상 출력
NAME READY STATUS RESTARTS AGE crash-test 0/1 CrashLoopBackOff 2 ...
CrashLoopBackOff 로그로 원인 확인
kubectl logs crash-test -n pod-lab
kubectl logs crash-test -n pod-lab --previous예상 출력
starting
실습 파드 정리
kubectl delete pod resource-hungry crash-test -n pod-lab --ignore-not-found
echo cleaned예상 출력
pod "resource-hungry" deleted pod "crash-test" deleted cleaned
실무 시나리오: 신규 서비스 배포 후 Pod 상태 모니터링
상황: 새 마이크로서비스를 프로덕션에 처음 배포합니다.
배포 직후 체크리스트
# 1. Phase 확인 (Pending이 30초 이상 지속되면 스케줄링 문제)
kubectl get pods -n production -w
# NAME READY STATUS RESTARTS AGE
# payment-xxx 0/1 Pending 0 5s
# payment-xxx 0/1 Init:0/1 0 8s ← Init Container 실행 중
# payment-xxx 0/1 Running 0 15s ← 메인 컨테이너 시작
# payment-xxx 1/1 Running 0 20s ← readiness 통과
# 2. READY 1/1인지 확인 (서비스 트래픽 수신 가능 여부)
kubectl get pods -n production
# READY 0/1이면 readiness probe 실패 중
# 3. 초반 로그 확인
kubectl logs payment-xxx -n production --since=5m
# 4. 이벤트 확인
kubectl describe pod payment-xxx -n production | tail -30
배포 성공 판단 기준:
- STATUS = Running
- READY = 정의한 컨테이너 수와 일치 (예: 1/1, 2/2)
- RESTARTS = 0 (배포 직후 기준)
- 로그에 에러 없음
실무 팁: kubectl rollout status deployment/payment -n production으로 Deployment 레벨에서 롤아웃 완료를 한 번에 확인할 수 있습니다. 다음 모듈에서 자세히 다룹니다.
명령어·단축키 빠른 참조
이 모듈에서 파드 생명주기를 진단할 때 쓴 kubectl 명령을 상태 읽기 순서대로 모았습니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
kubectl get pods | STATUS·READY·RESTARTS 한눈에 | kubectl get pods -w (상태 전환 실시간 관찰) |
kubectl describe pod | Events·Exit Code·재시작 이유 | kubectl describe pod X | grep -A15 Events |
kubectl logs | 앱이 왜 죽는지 확인 | kubectl logs X --previous (죽기 전 로그, 원인 90%) |
kubectl logs --since | 최근 로그만 좁혀 보기 | kubectl logs X --since=5m |
kubectl exec | 살아있는 컨테이너 내부 진입 | kubectl exec -it X -- sh |
kubectl debug | CrashLoop 파드를 복제해 디버깅 | kubectl debug -it X --copy-to=dbg --set-image=app=alpine -- sleep infinity |
kubectl rollout status | Deployment 롤아웃 완료 확인 | kubectl rollout status deployment/X |
kubectl run | 임시 파드로 상태 재현 | kubectl run t --image=alpine --command -- sh -c 'exit 1' |
kubectl delete pod | 실습 파드 정리 | kubectl delete pod X --ignore-not-found |
| Exit Code 읽기 | 종료 원인 판별 | 1=앱 에러 · 137=OOM/SIGKILL · 143=SIGTERM |
-w (watch) | 상태 변화 실시간 추적 | kubectl get pods -n <ns> -w |
관련 모듈로 더 깊이:
- Liveness, Readiness, Startup Probe 헬스 체크 설정 — Probe 실패가 어느 생명주기 단계에서 재시작·트래픽 차단을 트리거하는지
- Kubernetes 장애 원인을 빠르게 격리하는 트러블슈팅 가이드 — CrashLoopBackOff·ImagePullBackOff를 로그와 이벤트로 추적하는 디버깅 흐름
- Deployment를 이용한 안정적인 서비스 배포와 롤백 전략 — 파드 상태 전환 위에서 선언적 롤아웃이 어떻게 동작하는지
- 포트 바인딩 오류 방지와 컨테이너의 핵심 상태 변화 — 파드가 감싸는 그 컨테이너 자체의 상태 전환(Created→Running→Exited)과 재시작 동작 (Docker 트랙 · 선수 개념)
다음 모듈 deployment-basics에서는 Deployment와 ReplicaSet의 관계, 롤링 업데이트와 롤백을 실습합니다. Pod를 직접 관리하지 않고 Deployment를 통해 선언적으로 관리하는 방법을 마스터합니다.