파드(Pod) 생명주기 완전 이해

K8s Pod의 5가지 Phase(Pending/Running/Succeeded/Failed/Unknown), 4가지 Conditions, restartPolicy, 생성부터 삭제까지 전체 타임라인을 깊이 분석합니다.

· 5 min read · PALDYN Team

지난 글에서 kubectl 플러그인으로 생산성을 높이는 방법을 살펴봤다. 이제 K8s의 가장 기본 실행 단위인 파드(Pod) 의 생명주기를 깊이 이해할 차례다. “왜 내 파드가 Pending에서 안 넘어가지?”, “CrashLoopBackOff는 어떤 상태인가?”를 정확히 이해하려면 파드 생명주기를 제대로 알아야 한다.

Phase: 파드의 큰 그림 상태

파드의 status.phase는 5가지 값을 갖는다.

파드 생명주기 상태 머신

Phase의미
Pendingetcd에 저장됐지만 아직 실행 중이 아님. 스케줄링 대기 또는 이미지 pull 중
Running노드에 바인딩되어 최소 1개 컨테이너가 실행 중
Succeeded모든 컨테이너가 exit 0으로 정상 종료 (Job에서 중요)
Failed모든 컨테이너가 종료됐고 최소 1개가 비정상 종료
Unknown노드와 통신 불가 (노드 장애, 네트워크 분리)
# Phase 확인
kubectl get pod my-pod -o jsonpath='{.status.phase}'

# 넓은 시야로 확인
kubectl get pods -o wide

# 모든 파드 Phase 필터링
kubectl get pods --field-selector=status.phase=Pending
kubectl get pods --field-selector=status.phase=Failed

Conditions: 세부 체크포인트

Phase가 큰 그림이라면, Conditions는 파드 상태의 세부 체크포인트다.

파드 생성 타임라인과 Conditions

4가지 주요 Condition:

ConditionTrue 시점
PodScheduled스케줄러가 노드 배정 완료
Initialized모든 Init 컨테이너 성공적으로 완료
ContainersReady모든 컨테이너가 ready 상태
Ready파드가 트래픽 수신 가능 (Service Endpoints에 등록)
# Conditions 전체 확인
kubectl describe pod my-pod | grep -A 20 "Conditions:"

# jsonpath로 특정 condition 확인
kubectl get pod my-pod -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'

# 모든 condition 타입 확인
kubectl get pod my-pod \
  -o jsonpath='{range .status.conditions[*]}{.type}: {.status}{"\n"}{end}'

restartPolicy: 실패 시 동작

파드 스펙의 restartPolicy는 컨테이너 종료 후 동작을 결정한다.

spec:
  restartPolicy: Always    # 기본값: 항상 재시작 (Deployment, DaemonSet)
  # restartPolicy: OnFailure  # 비정상 종료 시만 재시작 (Job)
  # restartPolicy: Never      # 재시작 안 함 (Job, 일회성 작업)
  containers:
    - name: app
      image: my-app:1.0
# 재시작 횟수 확인
kubectl get pod my-pod -o jsonpath='{.status.containerStatuses[0].restartCount}'

# RESTARTS 컬럼으로 확인
kubectl get pods
# NAME       READY   STATUS    RESTARTS   AGE
# my-pod     1/1     Running   3          10m  ← 3번 재시작됨

CrashLoopBackOff는 Phase가 아니다. Running Phase에서 컨테이너가 계속 종료되고 재시작을 반복할 때 K8s가 backoff 지연을 증가시키며 표시하는 status.containerStatuses[].state.waiting.reason이다.

파드 삭제: Terminating과 grace period

파드를 삭제하면 즉시 사라지는 게 아니다.

# 파드 삭제 (graceful)
kubectl delete pod my-pod

# 삭제 흐름:
# 1. API Server가 deletionTimestamp 설정
# 2. kubelet이 감지 → PreStop hook 실행
# 3. SIGTERM 신호 전송
# 4. terminationGracePeriodSeconds(기본 30s) 대기
# 5. 아직 살아있으면 SIGKILL
# 6. 파드 오브젝트 삭제

# 강제 즉시 삭제 (grace period 0)
kubectl delete pod my-pod --grace-period=0 --force

# Terminating 상태 파드 확인
kubectl get pods | grep Terminating

파드 상태 자세히 보기

# 전체 상태 확인
kubectl describe pod my-pod

# 컨테이너 상태 (Running/Waiting/Terminated)
kubectl get pod my-pod -o jsonpath='{.status.containerStatuses}'

# 이전 컨테이너 종료 이유 확인
kubectl get pod my-pod \
  -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'

# Events: 가장 중요한 디버깅 정보
kubectl describe pod my-pod | tail -20
# Events:
#   Warning  FailedScheduling  Insufficient memory
#   Normal   Pulling           Pulling image "my-app:1.0"
#   Normal   Started           Started container app

파드 재시작과 backoff

restartPolicy=Always일 때 컨테이너가 계속 실패하면 K8s는 재시작 간격을 지수적으로 증가시킨다.

10s → 20s → 40s → 80s → 160s → 300s (최대)

이게 바로 CrashLoopBackOff다. “백오프 상태에서 다음 재시작을 기다리는 중”이라는 의미다.

# 재시작 이유 파악
kubectl logs my-pod --previous  # 이전 컨테이너의 로그

# 오류 이유
kubectl describe pod my-pod | grep -A 3 "Last State:"
# Last State:     Terminated
#   Reason:       OOMKilled    ← 메모리 초과
#   Exit Code:    137

파드 생명주기를 이해하면 트러블슈팅이 한결 수월해진다. 다음 글에서는 컨테이너 시작/종료 시점에 실행되는 생명주기 훅(hooks) 을 살펴본다.


지난 글: kubectl 플러그인 매니저 krew 완전 정리

다음 글: 파드 생명주기 훅: PostStart와 PreStop


읽어주셔서 감사합니다. 😊