Published on

Kubernetes CrashLoopBackOff 원인 12가지와 진단

Authors

서버가 죽었다가 살아나는 것을 반복하면, 애플리케이션 문제가 아니라 운영(런타임) 상태 머신이 문제를 드러내는 경우가 많습니다. Kubernetes에서 CrashLoopBackOff는 그 대표적인 신호로, “Pod가 계속 재시작되고 있으며, kubelet이 백오프(backoff)를 적용해 재시작 간격을 늘리고 있다”는 의미입니다.

중요한 점은 CrashLoopBackOff 자체가 원인이 아니라 결과라는 것입니다. 즉, 컨테이너가 종료되는 이유(Exit Code, OOMKilled, Probe 실패로 인한 kill, 이미지/명령/권한/의존성 문제 등)를 찾아야 합니다.

이 글에서는 현장에서 자주 만나는 원인 12가지를 정리하고, 재현 가능한 진단 루틴과 함께 해결 포인트를 제공합니다.

CrashLoopBackOff를 보는 관점: “왜 컨테이너가 종료됐는가?”

CrashLoopBackOff는 크게 3가지 축으로 쪼개면 진단이 빨라집니다.

  1. 프로세스가 스스로 종료: 앱 크래시, 설정 오류, 의존성 실패 등
  2. Kubernetes가 종료: liveness probe 실패, preStop/termination, 정책 위반 등
  3. 커널/노드가 종료: OOMKill, cgroup 제한, 디스크/파일시스템 문제 등

이제부터는 “무슨 이벤트가 있었고, 마지막 종료가 왜 발생했는지”를 중심으로 파고듭니다.

0) 5분 진단 루틴(가장 먼저 할 것)

아래 순서로 보면 대부분 1~2번 안에 원인이 좁혀집니다.

1) 상태/재시작 횟수/마지막 종료 사유 확인

kubectl get pod -n <ns> <pod> -o wide
kubectl describe pod -n <ns> <pod>

describe에서 특히 다음을 봅니다.

  • State: Waiting / Reason: CrashLoopBackOff
  • Last State: Terminated / Reason / Exit Code / Finished
  • Events(Back-off restarting failed container, OOMKilled, probe failed 등)

2) “이전(previous)” 로그 확인

CrashLoop이면 현재 컨테이너가 이미 재시작된 상태일 수 있어 이전 로그가 핵심입니다.

kubectl logs -n <ns> <pod> -c <container> --previous
# 멀티컨테이너가 아니면 -c 생략 가능

3) 컨테이너 시작 명령/환경/마운트 확인

kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[0].command} {.spec.containers[0].args}'
kubectl get pod -n <ns> <pod> -o yaml | sed -n '1,200p'

4) 프로브(liveness/readiness/startup)와 타이밍 확인

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

프로브가 원인인지 판단하려면 EventsLiveness probe failed가 찍히는지부터 확인합니다.

원인 12가지와 진단 포인트

아래 12가지는 “CrashLoopBackOff를 만드는 실제 원인”을 빈도 순으로 묶은 것입니다. 각 항목마다 징후 → 확인 방법 → 해결을 같이 적었습니다.

1) 애플리케이션 설정 오류(필수 ENV/파일 누락)

  • 징후: 로그에 KeyError, Missing env, config file not found, cannot parse 등. Exit Code 1이 흔함.
  • 확인:
kubectl logs -n <ns> <pod> --previous
kubectl describe pod -n <ns> <pod> | sed -n '/Environment:/,/Mounts:/p'
  • 해결: ConfigMap/Secret 키 이름 오타, envFrom 대상 누락, 파일 마운트 경로 불일치 수정.

2) 컨테이너 커맨드/엔트리포인트 오류(즉시 종료)

  • 징후: exec: "...": executable file not found in $PATH, permission denied, no such file or directory.
  • 확인:
kubectl logs -n <ns> <pod> --previous
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[0].command} {.spec.containers[0].args}'
  • 해결: 이미지 내부 경로/권한 확인, command/args 재정의 제거 또는 올바른 실행 파일 지정.

3) 이미지/아키텍처 불일치(Exec format error)

  • 징후: exec format error(예: ARM 노드에서 AMD64 이미지 실행).
  • 확인:
kubectl logs -n <ns> <pod> --previous
kubectl get node -o wide
  • 해결: 멀티아키텍처 이미지로 빌드(linux/amd64, linux/arm64), 노드 셀렉터/테인트로 스케줄링 제어.

4) OOMKilled(메모리 제한 초과)

  • 징후: describeLast State: Terminated / Reason: OOMKilled, Exit Code 137.
  • 확인:
kubectl describe pod -n <ns> <pod> | sed -n '/Last State:/,/Events:/p'
kubectl top pod -n <ns> <pod>
  • 해결: resources.limits.memory 상향, 메모리 누수/캐시 정책 개선, JVM/Node/Python 런타임 튜닝.

5) Liveness Probe 실패로 kubelet이 강제 재시작

  • 징후: 앱은 살아있는데 EventsLiveness probe failed 반복, 재시작 간격 증가.
  • 확인:
kubectl describe pod -n <ns> <pod> | grep -n "Liveness" -n
kubectl get pod -n <ns> <pod> -o yaml | sed -n '/livenessProbe:/,/readinessProbe:/p'
  • 해결: 초기 부팅 시간이 긴 앱이면 startupProbe 도입, initialDelaySeconds/timeoutSeconds/failureThreshold 조정.

Readiness/Liveness 설계가 애매하면 아래 글의 체크리스트도 도움이 됩니다: EKS에서 Readiness 실패인데 로그는 정상일 때

6) Readiness 실패를 Liveness로 착각(잘못된 프로브 설계)

  • 징후: 외부 트래픽이 안 되거나 503인데, liveness까지 실패하게 설계되어 재시작 루프. 예: DB 연결이 안 되면 liveness도 실패하도록 구성.
  • 확인:
kubectl describe pod -n <ns> <pod>
# Events에 readiness/liveness가 동시에 실패하는지 확인
  • 해결:
    • readiness는 “트래픽 받을 준비”
    • liveness는 “프로세스가 회복 불가능하게 죽었는지”

서비스가 503인데 Pod는 Running인 케이스와 경계가 헷갈릴 수 있습니다. 증상 분리에는 다음 글도 참고할 만합니다: EKS에서 Pod는 Running인데 503가 뜰 때 점검

7) CrashLoop의 원인이 ‘의존 서비스 연결 실패’(DB, Redis, 외부 API)

  • 징후: 로그에 connection refused, timeout, DNS lookup failed, TLS handshake 등. 앱이 “의존성 없으면 종료” 형태면 루프.
  • 확인:
kubectl logs -n <ns> <pod> --previous
# 임시 디버그 파드로 네트워크 확인
kubectl run -n <ns> net-debug --rm -it --image=curlimages/curl -- sh
# inside
curl -v http://<service>.<ns>.svc.cluster.local:port/health
  • 해결: 앱을 “재시도(backoff) + readiness로 차단” 패턴으로 변경하거나, initContainer로 의존성 준비를 기다리게 구성.

8) Secret/ConfigMap 마운트 실패 또는 키 오타

  • 징후: MountVolume.SetUp failed, secret "..." not found, configmap "..." not found 이벤트.
  • 확인:
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
kubectl get secret,configmap -n <ns> | grep <name>
  • 해결: 리소스 존재 여부, 네임스페이스 일치, 키 이름/볼륨 마운트 경로 확인.

9) 파일 권한/읽기 전용 루트 파일시스템 문제

  • 징후: permission denied, read-only file system, /tmp 쓰기 실패 등. 보안 강화를 위해 readOnlyRootFilesystem: true 적용 시 흔함.
  • 확인:
kubectl logs -n <ns> <pod> --previous
kubectl get pod -n <ns> <pod> -o yaml | grep -n "readOnlyRootFilesystem" -n
  • 해결: writable 경로를 emptyDir로 마운트(/tmp, /var/run 등), UID/GID 및 fsGroup 설정.

10) 자격 증명/권한 문제로 앱이 즉시 종료(IAM, STS, API 403)

  • 징후: AWS SDK 사용 앱에서 AccessDenied, STS 403, InvalidIdentityToken 후 프로세스 종료.
  • 확인:
kubectl logs -n <ns> <pod> --previous | egrep -i "accessdenied|sts|forbidden|403"
  • 해결: IRSA/서비스어카운트 어노테이션, 정책 누락, 토큰/시간 동기화 확인.

특히 EKS에서 STS 403은 CrashLoop의 단골 원인입니다. 원인 분해는 아래 글이 실전적입니다: EKS Pod에서 STS 403 AccessDenied 원인 8가지

11) 시간 드리프트로 TLS/STS 실패 → 앱 종료

  • 징후: x509: certificate has expired or is not yet valid, STS 서명 오류, 토큰 검증 실패.
  • 확인:
kubectl logs -n <ns> <pod> --previous | egrep -i "x509|not yet valid|signature|expired"

12) 노드 리소스/런타임 이슈(디스크, containerd, ephemeral-storage)

  • 징후: 이벤트에 Evicted, DiskPressure, 이미지 풀/레이어 추출 실패, no space left on device.
  • 확인:
kubectl describe node <node>
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 50
  • 해결: 노드 디스크 확장, 로그/이미지 정리, ephemeral-storage request/limit 설정, 런타임(containerd) 헬스 점검.

실전: CrashLoopBackOff를 재현/완화하는 디버그 패턴

CrashLoop은 너무 빨리 죽어서 kubectl exec도 못 들어가는 경우가 많습니다. 아래 패턴을 알아두면 진단 시간이 크게 줄어듭니다.

1) 재시작을 멈추고 로그/환경을 확보하기(명령 덮어쓰기)

가장 안전한 방식은 디버그용으로 command를 sleep으로 바꾼 임시 배포를 만드는 것입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-debug
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp-debug
  template:
    metadata:
      labels:
        app: myapp-debug
    spec:
      containers:
        - name: app
          image: myrepo/myapp:tag
          command: ["sh", "-c", "sleep 3600"]
          envFrom:
            - secretRef:
                name: myapp-secret

이후 kubectl exec로 들어가서 설정 파일, DNS, 네트워크, 권한 등을 확인합니다.

kubectl exec -n <ns> -it deploy/myapp-debug -- sh

2) kubectl debug로 임시 컨테이너 붙이기(가능한 경우)

클러스터/버전에 따라 ephemeral container를 붙여 네트워크/프로세스 상태를 볼 수 있습니다.

kubectl debug -n <ns> -it <pod> --image=nicolaka/netshoot --target=<container>

체크리스트: Exit Code로 빠르게 분기하기

  • Exit Code 137 → OOMKilled 또는 SIGKILL(메모리/노드 압박)
  • Exit Code 1/2 → 앱 설정/인자/의존성/권한 오류가 대부분
  • Reason: Error + exec format error → 아키텍처/이미지 문제
  • Events: Liveness probe failed → 프로브 설계/타이밍 문제

마무리: “원인”이 아니라 “마지막 종료 이벤트”부터 잡자

CrashLoopBackOff는 보통 한 번에 해결되지 않습니다. 하지만 진단 관점을 “왜 종료됐는가(Exit Code/Reason/Events)”로 고정하면, 다음과 같은 흐름으로 거의 항상 수렴합니다.

  1. describe pod에서 Last State / Exit Code / Events 확인
  2. --previous 로그로 마지막 크래시 직전 메시지 확인
  3. 프로브/자원/권한/의존성 중 하나로 분류
  4. 재시작 루프를 멈추는 디버그 패턴으로 재현 가능한 상태 확보

원인 12가지 중 어디에 속하는지 분류만 되면, 해결은 대개 YAML 몇 줄 또는 런타임 설정 조정으로 끝납니다.