Published on

K8s CrashLoopBackOff 즉시 원인 찾는 법

Authors

CrashLoopBackOff는 쿠버네티스가 컨테이너를 재시작하다가 백오프(backoff)로 간격을 늘리는 상태입니다. 중요한 포인트는 CrashLoopBackOff 자체가 원인이 아니라, 컨테이너가 왜 종료됐는지(Exit Code, Signal, OOMKilled, Probe 실패, 이미지/권한/설정 오류 등) 를 빠르게 특정하는 것이 핵심이라는 점입니다.

운영에서 “즉시 원인 찾기”는 보통 다음 3가지만 확보하면 됩니다.

  1. describe 이벤트에서 무슨 이유로 재시작하는지
  2. lastState.terminated에서 Exit Code / Reason / Signal
  3. --previous 로그에서 죽기 직전 무엇을 하다 죽었는지

아래 순서대로 따라 하면, 재현 환경이 없어도 대부분 5분 내로 원인 범위를 좁힐 수 있습니다.

1) 30초 컷: Pod 이벤트부터 본다

가장 먼저 해야 할 일은 kubectl describe pod로 이벤트를 보는 것입니다. CrashLoopBackOff의 1차 단서는 대부분 이벤트에 그대로 찍힙니다.

# 네임스페이스 지정
kubectl -n <ns> describe pod <pod>

MDX 빌드 에러를 피하기 위해 위의 <ns>, <pod> 같은 토큰은 인라인 코드로 감쌌습니다.

여기서 특히 아래 라인을 찾습니다.

  • Back-off restarting failed container
  • Readiness probe failed / Liveness probe failed
  • OOMKilled
  • Error: failed to start container (이미지, 권한, 마운트 문제)

이벤트만으로도 “프로브 때문에 재시작인지”, “앱이 즉시 크래시인지”, “노드/런타임 문제인지”가 분리됩니다.

자주 보이는 이벤트 패턴

  • 프로브 실패 반복: 컨테이너는 살아있지만 헬스체크가 계속 실패해서 재시작
  • Exit Code 1, 2, 137, 139 등: 앱 프로세스 자체가 종료
  • CreateContainerConfigError / ImagePullBackOff: CrashLoopBackOff로 보이기 전에 다른 상태가 먼저 찍히는 경우도 있음

2) 1분 컷: Exit Code와 Reason을 구조적으로 확인

describe는 사람이 읽기 좋지만, 빠르게 핵심만 뽑으려면 JSONPath가 강력합니다.

kubectl -n <ns> get pod <pod> -o jsonpath='{.status.containerStatuses[*].name}{"\n"}{.status.containerStatuses[*].state}{"\n"}{.status.containerStatuses[*].lastState.terminated}{"\n"}'

여기서 봐야 하는 필드:

  • lastState.terminated.exitCode
  • lastState.terminated.reason
  • lastState.terminated.signal
  • lastState.terminated.finishedAt
  • state.waiting.reasonCrashLoopBackOff 인지

Exit Code 빠른 해석표

  • 1 / 2: 앱 설정 오류, 인자/환경변수 누락, 마이그레이션 실패 등 “일반적인 실패”
  • 126: 실행 권한 문제(바이너리 실행 불가)
  • 127: 엔트리포인트/커맨드 없음(PATH 문제)
  • 137: SIGKILL로 종료. OOMKilled 또는 노드가 강제 종료했을 가능성 큼
  • 139: SIGSEGV(세그폴트)

reasonOOMKilled로 찍히면 거의 게임 끝입니다. 메모리 제한과 실제 사용량을 맞추거나, 앱의 메모리 급증 원인을 찾아야 합니다.

3) 2분 컷: --previous 로그로 “죽기 직전”을 본다

CrashLoopBackOff에서 가장 가치 있는 로그는 현재 컨테이너 로그가 아니라 직전 크래시 인스턴스 로그입니다.

# 단일 컨테이너 Pod
kubectl -n <ns> logs <pod> --previous

# 멀티 컨테이너 Pod
kubectl -n <ns> logs <pod> -c <container> --previous

여기서 흔히 나오는 즉시 원인:

  • 설정 파일 경로 오류, 시크릿/컨피그맵 키 누락
  • 외부 의존성(DB, Redis, STS, S3, DNS) 초기 연결 실패 후 즉시 종료
  • 마이그레이션 실패로 프로세스 종료
  • 바인딩 실패(Address already in use, 포트 권한 문제)

외부 의존성(특히 AWS 자격증명, STS, S3, DNS) 때문에 초기화 단계에서 죽는 경우가 많습니다. EKS라면 IRSA/IMDS 이슈도 매우 흔하니, 401 또는 메타데이터 접근 실패가 보이면 아래 글도 같이 확인해보세요.

4) 프로브가 원인인지 10초 만에 판별하는 법

CrashLoopBackOff의 “가짜 원인” 1위가 프로브입니다. 앱은 정상인데 프로브 설정이 공격적으로 되어 있으면, 컨테이너가 뜨자마자 재시작 루프에 들어갑니다.

describe pod에서 아래를 확인합니다.

  • Liveness probe failed 이벤트가 반복되는가
  • initialDelaySeconds 가 너무 짧지 않은가
  • timeoutSeconds 가 너무 짧지 않은가
  • failureThreshold 가 너무 낮지 않은가

즉시 조치(원인 분리용)

원인 분리를 위해서만 일시적으로 liveness를 완화하거나 제거하는 방법이 있습니다.

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30
  timeoutSeconds: 2
  periodSeconds: 10
  failureThreshold: 6

이렇게 했는데도 계속 죽는다면 “프로브”가 아니라 “프로세스 크래시/종료” 쪽으로 봐야 합니다.

5) OOMKilled(Exit 137)면 무엇을 더 봐야 하나

OOMKilled는 컨테이너 메모리 제한을 넘었을 때 커널이 프로세스를 강제 종료하는 전형적인 케이스입니다.

즉시 확인:

kubectl -n <ns> describe pod <pod> | sed -n '/Containers:/,/Conditions:/p'

여기서 Limits / Requests 메모리 값을 확인하고, 애플리케이션 특성과 맞는지 봅니다.

추가로 노드 레벨 압박인지 확인하려면:

kubectl describe node <node> | sed -n '/Allocated resources:/,/Events:/p'
  • 노드 자체가 메모리 압박이면 여러 Pod가 연쇄적으로 불안정해질 수 있습니다.
  • 특정 Pod만 OOM이면 해당 워크로드의 메모리 사용 패턴(캐시, 배치, 대용량 응답, JVM 힙 등)을 점검합니다.

6) CreateContainerConfigError / 마운트/시크릿 문제로 즉시 죽는 케이스

CrashLoopBackOff처럼 보이지만 실제로는 컨테이너가 제대로 시작도 못 하는 경우가 있습니다.

대표 원인:

  • secretKeyRef 키 이름 오타
  • configMapKeyRef 키 누락
  • 볼륨 마운트 경로/권한 문제
  • 서비스어카운트/이미지풀 시크릿 누락

이때는 이벤트에 답이 있습니다.

kubectl -n <ns> get events --sort-by='.lastTimestamp' | tail -n 30

이벤트에 MountVolume.SetUp failed 같은 문구가 있으면 애플리케이션 로그를 보기 전에 쿠버네티스 리소스 정의부터 고쳐야 합니다.

7) 엔트리포인트/커맨드 문제(Exit 126/127) 빠르게 잡기

도커 이미지 자체는 잘 빌드됐는데, 런타임에서 실행 파일을 못 찾거나 권한이 없어 죽는 케이스입니다.

  • exitCode: 127 이면 커맨드가 존재하지 않을 확률이 큽니다.
  • exitCode: 126 이면 실행 권한 또는 아키텍처 불일치 가능성이 있습니다.

즉시 확인할 것:

  • spec.containers[].command / args 가 이미지 내부와 맞는지
  • 셸 스크립트라면 #!/bin/sh 경로가 존재하는지
  • 파일에 실행 권한이 있는지

원인 분리를 위해 임시로 엔트리포인트를 바꿔 컨테이너에 들어가 확인할 수도 있습니다.

kubectl -n <ns> patch deploy <deploy> --type='json' \
  -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["/bin/sh","-c","sleep 3600"]}]'

그 다음:

kubectl -n <ns> exec -it <pod> -- /bin/sh

컨테이너 내부에서 실제 실행 경로와 권한을 확인합니다.

8) “로그가 비어있다”면: stdout 이전에 죽는 것이다

kubectl logs가 비어 있으면 보통 아래 중 하나입니다.

  • 프로세스가 stdout을 남기기 전에 즉시 크래시
  • 애플리케이션 로그가 파일로만 찍히고 stdout으로 안 나감
  • 런타임이 시작 전에 막힘(이미지/마운트/권한)

이때는 --previous와 이벤트가 거의 유일한 단서입니다. 또한 Ingress나 ALB 5xx로 먼저 관측되는 경우도 많습니다. 외부에서 502/504가 보이는데 Pod 로그가 비어 있다면, 트래픽이 Pod까지 도달했는지(헬스체크, 타겟 그룹, readiness)부터 분리해야 합니다.

9) 디버깅용 임시 컨테이너: Ephemeral Container 활용

프로세스가 너무 빨리 죽어서 exec가 불가능하면, Ephemeral Container로 네트워크/DNS/파일을 확인할 수 있습니다.

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

여기서 즉시 확인할 체크:

  • DNS: nslookup <service>
  • 라우팅: wget -S -O- http://<service>:<port>/health
  • 환경변수: env | sort
  • 마운트: ls -al /path

DNS 타임아웃이 의심되면 CoreDNS 및 노드 레벨 이슈까지 확장해야 합니다.

10) 운영에서 바로 쓰는 “즉시 원인 찾기” 커맨드 세트

아래 6줄을 복사해 두면, 대부분의 CrashLoopBackOff를 빠르게 분류할 수 있습니다.

kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> logs <pod> --previous
kubectl -n <ns> get pod <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated}'
kubectl -n <ns> get events --sort-by='.lastTimestamp' | tail -n 50
kubectl -n <ns> get deploy <deploy> -o yaml | sed -n '1,200p'
  • describeevents로 “쿠버네티스가 관측한 실패”를 확보
  • lastState.terminated로 “종료 코드/시그널”을 확보
  • --previous로 “앱이 말한 마지막 한 마디”를 확보

이 3가지를 합치면, 원인을 대개 다음 중 하나로 즉시 귀결시킬 수 있습니다.

  • 프로브 설정 문제
  • OOMKilled(메모리 제한/누수/버스트)
  • 엔트리포인트/권한/파일 누락
  • 시크릿/컨피그맵/볼륨 마운트 오류
  • 외부 의존성(DNS, STS, S3, DB) 초기화 실패

마무리: CrashLoopBackOff는 “증상”, 답은 Exit Code에 있다

CrashLoopBackOff를 빨리 끝내는 요령은 복잡한 추측을 줄이고, 관측 가능한 사실을 순서대로 수집하는 것입니다.

  1. 이벤트로 큰 분기(프로브 vs 크래시 vs 런타임)를 나누고
  2. lastState.terminated로 종료 코드를 확정한 뒤
  3. --previous 로그로 애플리케이션 레벨 원인을 못 박습니다.

여기까지 해도 원인이 외부 의존성(특히 EKS의 DNS/인증/네트워크)로 남는다면, 위에 링크한 IRSA/IMDS, CoreDNS 점검 글을 함께 따라가면 “CrashLoopBackOff의 진짜 원인”까지 빠르게 도달할 수 있습니다.