infra
Platform

모듈 맵

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

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 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의 역할과 차이
  • 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-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

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 부족으로 스케줄 불가
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

Pending의 주요 원인:

  • 리소스 부족 (CPU/메모리 requests를 충족하는 노드 없음)
  • nodeSelector/affinity 조건에 맞는 노드 없음
  • PersistentVolumeClaim 바인딩 대기
  • 이미지 pull 중 (Pulling 이벤트)

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)
💡개념

Probe — 컨테이너 건강 감시

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

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

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 -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%의 원인을 알려줍니다.

💼
실무 맥락
현업 패턴

실무 시나리오: 신규 서비스 배포 후 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 레벨에서 롤아웃 완료를 한 번에 확인할 수 있습니다. 다음 모듈에서 자세히 다룹니다.


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

지식 확인

퀴즈 — 4문제

Q1

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

Q2

CrashLoopBackOff 상태의 의미는?

Q3

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

Q4

ImagePullBackOff와 ErrImagePull의 차이는?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

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

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

이것도 배워보세요

kubernetes입문 · 65
[Kubernetes] ClusterIP, NodePort, LoadBalancer 서비스 완전 분석
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점