infra
Platform

모듈 맵

[Kubernetes] Pending, Running, Failed, CrashLoopBackOff 생명주기 분석

0 / 29 완료

펼치기
0 / 29 완료0%

쿠버네티스 & GitOps · 04 / 29

[Kubernetes] Pending, Running, Failed, CrashLoopBackOff 생명주기 분석

Pending → Running → Succeeded/Failed 상태 전환을 이해하고, CrashLoopBackOff와 ImagePullBackOff를 체계적으로 진단합니다

🚨INCIDENT ALERT
HIGH

서비스가 간헐적으로 끊기는데 파드는 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-info
노드 상태 확인
kubectl get nodes
실습용 네임스페이스 생성
kubectl 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 컬럼이 이 상태를 보여줍니다.

Pod 상태 머신 5가지 Phase — Pending(스케줄링·이미지 Pull·Init Container)→Running(컨테이너 실행, READY 1/1=정상)→Succeeded(정상 종료, exit 0)/Failed(비정상, exit≠0·OOM). Unknown은 노드 통신 불가. CrashLoopBackOff·ImagePullBackOff 원인과 Exit Code(0 정상·137 OOMKill·143 SIGTERM) 참조 포함확대

5가지 Phase

kubectl apply →
    Pending          : 스케줄링 대기 또는 이미지 pull 중
        ↓ (스케줄 + 이미지 pull + Init Container 완료)
    Running          : 하나 이상의 컨테이너가 실행 중
        ↓
    Succeeded        : 모든 컨테이너가 성공적으로 종료 (exit 0)
    Failed           : 하나 이상의 컨테이너가 실패 종료 (exit != 0)
    Unknown          : 노드와 통신 불가 (노드 장애 시)

Pending — 이 시간 동안 무슨 일이?

Kubernetes
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에 따라 재시도하고 앱 컨테이너는 시작되지 않습니다.

Init Container — 앱 컨테이너 실행 전 초기화 단계. Pod 생성 후 Init Container 1·2가 순차 완료(Init:0/2→1/2→2/2)된 뒤에야 앱 컨테이너가 Running. exit≠0이면 재시도 루프(CrashLoopBackOff 가능). DB 마이그레이션 대기·S3에서 설정 다운로드·볼륨 권한 설정 등에 활용확대

Running — 정말 정상인가?

Kubernetes
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로 실패 원인 파악

Kubernetes
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가 뜰 때, 이 흐름을 알면 "지금 어느 단계에서 막혔나"를 한눈에 좁힐 수 있습니다. 사실 각 상태 이름은 이 파이프라인이 '멈춘 지점'을 가리키는 라벨입니다.

TEXT
[내 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이 파드 스펙을 받아 기동 시작노드가 NotReadyPending 지속
⑤ 이미지 pullkubelet이 CRI로 컨테이너 이미지를 내려받음ErrImagePull → 반복 실패 시 ImagePullBackOff(이미지 없음·태그 오타·레지스트리 인증)
⑥ 네트워크·볼륨CNI가 파드 IP를 부여하고 볼륨을 attach/mountContainerCreating 지속 — FailedCreatePodSandBox(CNI), FailedMount(볼륨·PVC)
⑦ 컨테이너 시작Init Container를 순차 실행 후 앱 컨테이너 실행Init 실패 → Init:Error·Init:CrashLoopBackOff, 앱이 exit≠0 → CrashLoopBackOff
⑧ probestartup 통과 후 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 podEvents를 보면 이 단계 중 어디서 무엇을 기다리다 실패했는지가 시간순으로 찍혀 있고, 그게 진단의 출발점입니다.

💡개념

Probe — 컨테이너 건강 감시

파드가 Running이고 로그도 정상인데 간헐적으로 요청이 타임아웃되는 상황이 있습니다. 애플리케이션 내부에서 데드락이 걸렸거나, DB 연결은 됐지만 응답을 못 하는 상태일 수 있습니다. Kubernetes는 컨테이너가 실행 중이라는 것만으로 정상이라고 판단합니다. Probe는 이 빈틈을 메웁니다. kubelet이 주기적으로 컨테이너의 실제 상태를 확인해 장애를 자동으로 복구하거나 트래픽을 차단합니다. liveness와 readiness 두 가지 Probe를 올바르게 설정하면 온콜 없이도 많은 장애가 자동으로 처리됩니다.

kubelet은 컨테이너의 상태를 주기적으로 확인합니다. 이를 위해 두 가지 Probe를 설정할 수 있습니다.

liveness probe — 살아있는가?

컨테이너가 살아있는지 확인합니다. 실패하면 컨테이너 재시작.

YAML
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30  # 컨테이너 시작 후 첫 체크까지 대기
  periodSeconds: 10         # 체크 간격
  failureThreshold: 3       # 연속 3번 실패 시 재시작
로컬 터미널
# 데드락에 걸린 앱 → liveness probe 실패 → kubelet이 컨테이너 재시작
# → 새 컨테이너가 정상적으로 동작 재개

readiness probe — 트래픽 받을 준비가 됐는가?

컨테이너가 서비스 트래픽을 받을 준비가 됐는지 확인합니다. 실패하면 Service 엔드포인트에서 제거 (재시작 안 함).

YAML
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3
로컬 터미널
# 앱이 DB 연결 중 → readiness probe 실패
# → Service가 이 Pod로 트래픽 전달 안 함
# → DB 연결 완료 → readiness 성공 → 트래픽 수신 시작

startup probe — 느린 앱을 위해

앱이 시작되기까지 오래 걸리는 경우, liveness probe가 너무 일찍 실패하지 않도록 합니다.

YAML
startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 30  # 30번 × 10초 = 최대 300초 대기
  periodSeconds: 10
# startupProbe 성공 후에 liveness/readiness probe 시작

liveness vs readiness 요약

livenessreadiness
실패 시컨테이너 재시작엔드포인트 제거 (트래픽 차단)
용도데드락, 무한루프 감지준비 전 트래픽 차단
언제 쓰나항상 권장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)는 Pending 상태이므로 서비스 영향은 없지만, 습관적으로 컨텍스트를 확인하는 연습이 중요합니다.

실습 파드 삭제 — 현재 컨텍스트가 프로덕션이면 실제 파드 삭제

안전한 실행 조건: 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

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

Kubernetes
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

상황

Kubernetes
kubectl get pods -n production
# NAME           READY   STATUS             RESTARTS   AGE
# api-server-0   0/1     CrashLoopBackOff   8          15m

RESTARTS가 계속 올라가고 있습니다.

진단 3단계

Step 1: logs — 앱이 왜 죽는지 확인

Kubernetes
# 현재 컨테이너 로그 (죽기 전 출력)
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 — 재시작 패턴과 이벤트 확인

Kubernetes
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 — 내부 환경 확인 (컨테이너가 잠깐이라도 살아있을 때)

Kubernetes
# 컨테이너가 잠깐 실행되는 순간 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 errorCPU 아키텍처 불일치이미지 플랫폼 확인
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와 종료 상태를 봅니다.

Kubernetes
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 헬스 체크 설정에서 더 깊이 다룹니다.

실습 단계
1

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          ...
2

Pending 원인 파악 — Events 확인

kubectl describe pod resource-hungry -n pod-lab | grep -A 3 'Warning'

예상 출력

Warning  FailedScheduling  ...  0/1 nodes are available: 1 Insufficient memory.
3

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          ...
4

CrashLoopBackOff 로그로 원인 확인

kubectl logs crash-test -n pod-lab kubectl logs crash-test -n pod-lab --previous

예상 출력

starting
5

실습 파드 정리

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 상태 모니터링

상황: 새 마이크로서비스를 프로덕션에 처음 배포합니다.

배포 직후 체크리스트

Kubernetes
# 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

배포 성공 판단 기준:

  1. STATUS = Running
  2. READY = 정의한 컨테이너 수와 일치 (예: 1/1, 2/2)
  3. RESTARTS = 0 (배포 직후 기준)
  4. 로그에 에러 없음

실무 팁: kubectl rollout status deployment/payment -n production으로 Deployment 레벨에서 롤아웃 완료를 한 번에 확인할 수 있습니다. 다음 모듈에서 자세히 다룹니다.



명령어·단축키 빠른 참조

이 모듈에서 파드 생명주기를 진단할 때 쓴 kubectl 명령을 상태 읽기 순서대로 모았습니다.

명령어/단축키용도자주 쓰는 예
kubectl get podsSTATUS·READY·RESTARTS 한눈에kubectl get pods -w (상태 전환 실시간 관찰)
kubectl describe podEvents·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 debugCrashLoop 파드를 복제해 디버깅kubectl debug -it X --copy-to=dbg --set-image=app=alpine -- sleep infinity
kubectl rollout statusDeployment 롤아웃 완료 확인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

관련 모듈로 더 깊이:

다음 모듈 deployment-basics에서는 Deployment와 ReplicaSet의 관계, 롤링 업데이트와 롤백을 실습합니다. Pod를 직접 관리하지 않고 Deployment를 통해 선언적으로 관리하는 방법을 마스터합니다.

지식 확인

퀴즈 — 8문제

Q1

Pod가 오랫동안 Pending 상태에 머물고 있을 때 가장 먼저 확인해야 할 것은?

Q2

CrashLoopBackOff 상태의 의미는?

Q3

Pod의 liveness probe 실패 시 Kubernetes는 어떻게 반응하는가?

Q4

ImagePullBackOff와 ErrImagePull의 차이는?

Q5

Pod의 Phase 중 'Succeeded'와 'Failed'는 주로 어떤 워크로드에서 보게 되는가?

Q6

파드 STATUS가 Running인데 READY가 0/1이다. 무슨 뜻인가?

Q7

[심화] logs --previous에 앱 에러가 전혀 없는데 CrashLoopBackOff가 계속된다. 앱이 스스로 죽은 게 아니라 kubelet이 죽인 것임을 가장 잘 보여주는 신호는?

Q8

[심화] 기동에 40초 걸리는 앱에 liveness의 initialDelaySeconds가 10초로 걸려 있어, 앱이 뜨기 전에 probe가 실패하며 CrashLoopBackOff가 반복된다. 가장 올바른 해결은?

0 / 8 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

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

이것도 배워보세요