Published on

K8s CrashLoopBackOff 원인 10가지·즉시 진단법

Authors

CrashLoopBackOff는 Pod 가 시작하자마자 종료되고, kubelet이 재시작을 반복하면서 백오프가 걸린 상태입니다. 중요한 포인트는 “왜 죽었는지”가 아니라 “어떤 컨테이너가, 어떤 종료 코드로, 어떤 시점에, 무엇을 하기 직전에 죽었는지”를 재현 가능한 증거로 모으는 것입니다.

이 글은 현장에서 가장 빨리 원인을 좁히는 순서(이벤트 -> 로그 -> 종료 코드 -> 리소스 -> 프로브 -> 설정/권한/의존성)로 구성했습니다. 커맨드는 그대로 복사해도 되도록 최소 세트로 제공합니다.

1) 3분 안에 끝내는 즉시 진단 체크리스트

1-1. 무엇이 반복 재시작되는지부터 확정

# 어떤 파드가 CrashLoopBackOff 인지
kubectl get pod -n <namespace>

# 특정 파드의 컨테이너/재시작 횟수/상태를 한 번에
kubectl get pod -n <namespace> <pod> -o wide

여기서 확인할 것

  • READY0/1 인지, 혹은 사이드카 포함 1/2 인지
  • RESTARTS 가 빠르게 증가하는지
  • 노드가 특정 노드로 고정되어 있는지

1-2. 이벤트로 “왜”가 아니라 “언제/무엇이”를 잡기

kubectl describe pod -n <namespace> <pod>

Events 섹션에서 다음을 우선 확인합니다.

  • Back-off restarting failed container
  • Killing 과 함께 OOMKilled 같은 힌트
  • Readiness probe failed, Liveness probe failed
  • FailedMount, Failed to pull image 등 (이 경우는 CrashLoopBackOff 이전 단계일 수 있음)

이미지 풀 자체 문제는 CrashLoopBackOff가 아니라 ImagePullBackOff 로 나타나는 경우가 많습니다. 관련 케이스는 K8s Pod ImagePullBackOff - ECR 403 해결 가이드도 함께 참고하면 좋습니다.

1-3. “직전 크래시 로그”를 반드시 본다

CrashLoopBackOff는 재시작이 빠르기 때문에 “현재 떠 있는 컨테이너 로그”가 아니라 “이전 인스턴스 로그”가 핵심입니다.

# 기본 컨테이너의 직전 로그
kubectl logs -n <namespace> <pod> --previous

# 멀티 컨테이너면 컨테이너 지정
kubectl logs -n <namespace> <pod> -c <container> --previous

로그가 비어 있으면 다음 가능성을 의심합니다.

  • 애플리케이션이 stdout/stderr로 로그를 안 찍음
  • 프로세스가 너무 빨리 죽어 로그가 flush 되기 전에 종료
  • 엔트리포인트 자체가 실행되지 못함(권한, 파일 없음 등)

1-4. 종료 코드와 종료 사유를 구조적으로 확인

kubectl get pod -n <namespace> <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason} {.status.containerStatuses[*].lastState.terminated.exitCode} {.status.containerStatuses[*].lastState.terminated.message}'

자주 보는 패턴

  • exitCode137 이면 OOM 가능성이 큼
  • exitCode1 이면 애플리케이션 예외/설정 오류가 흔함
  • reasonError 로만 나오면 로그와 이벤트가 더 중요

1-5. 디버그용 임시 컨테이너로 “환경”을 재현

애플리케이션이 너무 빨리 죽어서 내부를 못 보면, ephemeral container 로 컨테이너 네임스페이스에 진입해 파일/권한/환경변수를 확인합니다.

kubectl debug -n <namespace> -it <pod> --image=busybox --target=<container>

2) CrashLoopBackOff 대표 원인 10가지와 처방

아래 10가지는 실제 운영에서 빈도가 높고, describe 이벤트/종료 코드/로그로 빠르게 분기되는 항목들입니다.

2-1. OOMKilled: 메모리 제한이 너무 낮음

증상

  • describe podOOMKilled 가 보이거나
  • exitCode137 로 반복

즉시 진단

kubectl describe pod -n <namespace> <pod> | sed -n '/State:/,/Conditions:/p'

kubectl top pod -n <namespace> <pod>

해결

  • resources.limits.memory 를 올리거나
  • 애플리케이션 힙/캐시를 제한(예: JVM -Xmx, Node.js --max-old-space-size)
  • 메모리 릭 의심 시, 동일 트래픽에서 RSS가 계속 증가하는지 장기 관찰

예시 매니페스트

resources:
  requests:
    cpu: "200m"
    memory: "512Mi"
  limits:
    cpu: "1"
    memory: "1Gi"

2-2. Crash는 정상인데 프로브가 죽인다: liveness/readiness 오설정

증상

  • 이벤트에 Liveness probe failed 가 반복
  • 애플리케이션은 느리게 뜨는데 프로브 타임아웃이 짧음

즉시 진단

kubectl describe pod -n <namespace> <pod> | sed -n '/Liveness:/,/Environment:/p'

해결

  • 초기 기동이 긴 서비스는 startupProbe 를 추가
  • initialDelaySeconds, timeoutSeconds, failureThreshold 를 현실적으로 조정

예시

startupProbe:
  httpGet:
    path: /health
    port: 8080
  failureThreshold: 30
  periodSeconds: 2

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

2-3. 설정 누락: ConfigMap/Secret 키가 없거나 값이 비어 있음

증상

  • 로그에 missing env var 류 에러
  • CreateContainerConfigError 와 혼동되기도 하지만, 실행 후 설정 파싱 실패로 크래시 가능

즉시 진단

kubectl get deploy -n <namespace> <deploy> -o yaml | sed -n '/env:/,/resources:/p'

kubectl get cm -n <namespace>
kubectl get secret -n <namespace>

해결

  • 필수 키를 배포 파이프라인에서 검증
  • 애플리케이션 시작 시 필수 설정 검증을 “명확한 로그”로 남기기

예시

env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: app-secret
        key: databaseUrl

2-4. 파일/디렉터리 권한 문제: non-root 실행과 볼륨 권한 불일치

증상

  • 로그에 permission denied
  • 특정 경로에 PID 파일/소켓/로그 파일을 만들다 실패

즉시 진단

kubectl logs -n <namespace> <pod> -c <container> --previous

kubectl get pod -n <namespace> <pod> -o jsonpath='{.spec.securityContext} {.spec.containers[0].securityContext}'

해결

  • runAsUser, runAsGroup, fsGroup 를 볼륨과 맞춤
  • 이미지 내부 디렉터리의 소유권을 빌드 단계에서 정리

예시

securityContext:
  runAsNonRoot: true
  runAsUser: 10001
  runAsGroup: 10001
  fsGroup: 10001

2-5. 엔트리포인트/커맨드 오류: 실행 파일이 없거나 인자가 잘못됨

증상

  • 로그가 거의 없고 즉시 종료
  • 이벤트에 Error: failed to start container 류가 보이기도 함

즉시 진단

kubectl get pod -n <namespace> <pod> -o jsonpath='{.spec.containers[0].command} {.spec.containers[0].args}'

해결

  • commandargs 오버라이드가 Dockerfile의 ENTRYPOINT 를 깨지 않는지 확인
  • 이미지 태그 변경 시 바이너리 경로가 바뀌지 않았는지 점검

예시

command: ["/app/server"]
args: ["--port=8080"]

2-6. 의존 서비스 미준비: DB/Redis/Kafka 연결 실패로 즉시 종료

증상

  • 로그에 connection refused, timeout, no route to host
  • 재시작을 반복하며 외부 시스템에 부하를 줌

즉시 진단

# 같은 네임스페이스에서 DNS 확인
kubectl run -n <namespace> -it tmp --rm --image=busybox --restart=Never -- nslookup <service>

# TCP 포트 확인(이미지에 따라 nc 사용)
kubectl run -n <namespace> -it tmp --rm --image=busybox --restart=Never -- sh -c 'nc -vz <host> <port>'

해결

  • 애플리케이션을 “연결 실패 시 즉시 종료”가 아니라 “지수 백오프 재시도”로 변경
  • 준비 상태를 readinessProbe 로 표현하고, 연결 전에는 트래픽을 받지 않게 설계

DB 락/지연이 원인으로 기동이 멈추거나 타임아웃이 발생하는 경우도 있습니다. 애플리케이션 레벨에서 DB 교착 상태를 추적해야 한다면 MySQL InnoDB 데드락 로그로 원인 쿼리 찾기도 함께 보면 진단 시간이 줄어듭니다.

2-7. 마운트 실패/볼륨 문제: PV/PVC, CSI, Secret 볼륨

증상

  • 이벤트에 FailedMount
  • 파일이 있어야 하는데 없어서 애플리케이션이 크래시

즉시 진단

kubectl describe pod -n <namespace> <pod> | sed -n '/Volumes:/,/QoS Class:/p'

kubectl get pvc -n <namespace>
kubectl describe pvc -n <namespace> <pvc>

해결

  • PVC 바인딩 상태, 스토리지클래스, 접근 모드 확인
  • Secret/ConfigMap 볼륨이면 키 존재 여부 재확인

2-8. 노드/런타임 이슈: 특정 노드에서만 반복 크래시

증상

  • 동일 파드가 특정 노드에 스케줄될 때만 CrashLoopBackOff
  • 노드에 디스크 부족, 컨테이너 런타임 오류, CNI 문제 등이 동반

즉시 진단

kubectl get pod -n <namespace> <pod> -o wide
kubectl describe node <node>

kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -n 50

네트워크 플러그인 장애로 노드가 불안정해지는 경우도 많습니다. 노드 상태가 NotReady 로 흔들린다면 K8s NodeNotReady - CNI 플러그인 장애 복구 가이드 흐름으로 함께 점검하세요.

해결

  • 문제 노드 드레인 후 교체
  • taint 로 격리하고 원인 분석(디스크, inode, runtime, CNI)

2-9. SIGTERM 처리 미흡: 종료 훅/그레이스 기간 동안 정리 실패로 재기동 루프

증상

  • 롤링 업데이트나 스케일링 시점에만 CrashLoopBackOff
  • 종료 시 로그에 정리 작업 실패, 강제 종료 흔적

즉시 진단

kubectl get pod -n <namespace> <pod> -o jsonpath='{.spec.terminationGracePeriodSeconds} {.spec.containers[0].lifecycle}'

해결

  • preStop 훅을 짧고 안전하게
  • 애플리케이션에서 SIGTERM을 받아 리스닝 중단 후 처리
  • terminationGracePeriodSeconds 를 실제 정리 시간에 맞게 조정

예시

terminationGracePeriodSeconds: 60
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 5"]

2-10. 애플리케이션 자체 패닉/예외: 미처리 예외, 마이그레이션 실패, 스키마 불일치

증상

  • 로그에 스택 트레이스/패닉 메시지
  • 배포 직후부터 즉시 재현

즉시 진단

kubectl logs -n <namespace> <pod> -c <container> --previous --tail=200

# 배포 변경 이력 확인
kubectl rollout history deploy -n <namespace> <deploy>

해결

  • DB 마이그레이션을 앱 스타트업과 분리(Job로 실행)
  • 기능 플래그로 위험 변경을 단계적으로
  • 크래시 시 “원인 로그”가 남도록 표준 에러 출력과 종료 코드 정리

3) 케이스별로 바로 써먹는 진단 커맨드 모음

3-1. 컨테이너 상태를 한 번에 보기

kubectl get pod -n <namespace> <pod> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.lastState.terminated.exitCode}{"\t"}{.restartCount}{"\n"}{end}'

3-2. 이벤트 타임라인으로 보기

kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp | tail -n 30

3-3. 프로브만 빠르게 확인

kubectl get pod -n <namespace> <pod> -o yaml | sed -n '/livenessProbe:/,/readinessProbe:/p'

3-4. 리소스 제한과 QoS 확인

kubectl describe pod -n <namespace> <pod> | sed -n '/Limits:/,/QoS Class:/p'

4) 재발 방지 체크리스트(운영 관점)

  • 애플리케이션 시작 실패 시, 필수 설정/의존성/마이그레이션 실패를 한 줄 요약 로그로 남기기
  • startupProbe 로 “기동 시간”과 “장애”를 분리하기
  • 메모리 제한이 있는 런타임(JVM, Node.js 등)은 힙을 제한값에 맞게 고정하기
  • CrashLoopBackOff가 외부 의존성을 때리는 폭주가 되지 않도록 재시도에 지수 백오프 적용
  • 배포 전후 kubectl rollout status--previous 로그를 파이프라인에 포함

5) 마무리: 가장 빠른 분기점은 3가지

CrashLoopBackOff를 가장 빨리 줄이는 분기점은 아래 3개입니다.

  1. kubectl describe pod 의 이벤트에서 OOM/프로브/마운트 힌트를 찾기
  2. kubectl logs --previous 로 “직전 크래시” 로그 확보하기
  3. 종료 코드(137, 1 등)로 리소스 문제와 앱 예외를 1차 분리하기

이 세 가지를 먼저 고정하면, 원인 10가지 중 어디로 들어가야 하는지 대부분 5분 안에 결정할 수 있습니다.