서비스가 간헐적으로 끊기는데 파드는 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의 역할과 차이
- 4CrashLoopBackOff 진단 3단계 (logs → describe → exec)
- 5ImagePullBackOff 원인별 진단과 해결
- 6Pod 종료 프로세스: terminationGracePeriodSeconds, preStop hook
실습은 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 부족으로 스케줄 불가
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
Pending의 주요 원인:
- 리소스 부족 (CPU/메모리 requests를 충족하는 노드 없음)
- nodeSelector/affinity 조건에 맞는 노드 없음
- PersistentVolumeClaim 바인딩 대기
- 이미지 pull 중 (Pulling 이벤트)
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)
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 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%의 원인을 알려줍니다.
실무 시나리오: 신규 서비스 배포 후 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 레벨에서 롤아웃 완료를 한 번에 확인할 수 있습니다. 다음 모듈에서 자세히 다룹니다.
다음 모듈 deployment-basics에서는 Deployment와 ReplicaSet의 관계, 롤링 업데이트와 롤백을 실습합니다. Pod를 직접 관리하지 않고 Deployment를 통해 선언적으로 관리하는 방법을 마스터합니다.