Published on

EKS Pod CrashLoopBackOff 로그 없을 때 7단계 진단

Authors

서버리스가 아니라 EKS(쿠버네티스)에서 CrashLoopBackOff가 뜨는데 kubectl logs가 비어 있으면, 보통 프로세스가 stdout/stderr로 아무것도 출력하기 전에 종료했거나, 컨테이너가 아예 시작되지 못한 상태입니다. 이때는 “로그를 더 찾는” 접근보다, 컨테이너 생명주기(스케줄링 → 이미지 풀 → 컨테이너 생성 → 프로브 통과 → 앱 기동) 중 어디에서 끊겼는지 단계적으로 좁히는 게 가장 빠릅니다.

아래 7단계는 현장에서 재현 빈도가 높은 순서대로 정리했습니다. 각 단계는 확인 포인트 → 대표 원인 → 바로 실행할 명령 중심으로 구성했습니다.

0) 전제: 로그가 왜 없을까?

로그가 없는 케이스는 크게 4가지로 압축됩니다.

  1. 컨테이너가 생성되기 전(이미지 풀 실패, 권한, CNI 문제 등)
  2. 컨테이너는 생성됐지만 즉시 종료(entrypoint 오류, 동적 로더/라이브러리 missing, config parsing 실패 등)
  3. 프로브가 너무 빨라서 강제 종료(readiness/liveness가 앱 기동보다 빠름)
  4. 로그를 볼 경로가 틀림(이전 컨테이너 로그 --previous 필요, sidecar에만 로그가 있음 등)

이제부터는 “어디까지 진행됐는지”를 확인하며 원인을 깎아나갑니다.

1단계: describe 이벤트로 “시작 전”인지 “시작 후”인지 가르기

가장 먼저 해야 할 일은 kubectl describe pod에서 Events를 읽는 겁니다. CrashLoopBackOff 자체는 결과일 뿐이고, 원인은 이벤트에 거의 남습니다.

NS=prod
POD=myapp-7b9c9d4c7f-abcde
kubectl -n $NS describe pod $POD

체크 포인트

  • Failed to pull image, ErrImagePull, ImagePullBackOff컨테이너 시작 전
  • CreateContainerConfigError환경변수/시크릿/볼륨/SA 문제
  • Back-off restarting failed container + Started container가 반복 → 컨테이너는 시작했다가 죽음
  • Unhealthy(Readiness/Liveness probe failed) → 프로브가 죽이는 중

이 단계에서 이미 FailedMount, Forbidden, no such file or directory 같은 메시지가 보이면, 로그 없이도 원인 70%는 끝납니다.

2단계: kubectl logs --previous로 “직전 크래시 로그”를 확보

CrashLoopBackOff는 재시작을 반복합니다. 기본 kubectl logs현재(또는 마지막으로 실행 중인) 컨테이너를 보는데, 이미 죽어버린 직전 컨테이너 로그는 --previous가 필요합니다.

NS=prod
POD=myapp-7b9c9d4c7f-abcde
CONTAINER=myapp

kubectl -n $NS logs $POD -c $CONTAINER --previous --tail=200

추가 팁

  • 멀티 컨테이너 Pod면 -c를 반드시 지정하세요.
  • sidecar(예: envoy, fluent-bit)만 로그가 찍히는 경우도 있으니 컨테이너 목록을 확인합니다.
kubectl -n $NS get pod $POD -o jsonpath='{.spec.containers[*].name}'

--previous에서도 완전히 비어 있다면, 앱이 stdout/stderr를 쓰기 전에 종료하거나, 컨테이너 생성 단계에서 실패했을 확률이 큽니다.

3단계: 종료 코드/Reason으로 “왜 죽었는지” 1차 분류

로그가 없을 때 가장 강력한 단서는 **종료 코드(exit code)**와 terminated reason입니다.

NS=prod
POD=myapp-7b9c9d4c7f-abcde

kubectl -n $NS get pod $POD -o jsonpath='{
  "phase": .status.phase,
  "containerStatuses": .status.containerStatuses
}' | jq

자주 보는 패턴

  • exitCode: 1 + reason: Error → 앱이 즉시 실패(설정 파싱/필수 env 누락/마이그레이션 실패)
  • exitCode: 127 → 엔트리포인트/명령 없음(이미지 CMD/ENTRYPOINT 잘못됨)
  • exitCode: 139 → 세그폴트(네이티브 라이브러리, glibc/musl 불일치 등)
  • reason: OOMKilled → 메모리 제한 초과
  • reason: ContainerCannotRun / StartError → 런타임/권한/파일 경로 문제

OOMKilled 빠른 확인

kubectl -n $NS describe pod $POD | sed -n '/State:/,/Last State:/p'

OOMKilled인데 로그가 없다면, 앱이 기동 중 메모리를 급격히 먹고 죽는 케이스가 많습니다(예: JVM 힙 자동 설정, Node/V8 메모리, 대형 캐시 로딩).

4단계: Probe가 “로그 남기기 전에” 죽이고 있는지 확인

livenessProbe가 너무 공격적이면 앱이 준비되기 전에 계속 재시작되어 로그가 비어 보일 수 있습니다.

NS=prod
DEPLOY=myapp
kubectl -n $NS get deploy $DEPLOY -o yaml | yq '.spec.template.spec.containers[] | {name, readinessProbe, livenessProbe, startupProbe}'

대표 원인

  • initialDelaySeconds가 0인데 앱 부팅이 20~60초 걸림
  • timeoutSeconds가 너무 짧아 TLS 핸드셰이크/DB 연결 때 타임아웃
  • startupProbe 없이 liveness가 먼저 작동

즉시 완화(임시)

문제 원인을 보기 위해 일단 liveness를 느슨하게 하거나 startupProbe를 추가합니다.

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  failureThreshold: 30
  periodSeconds: 2
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  timeoutSeconds: 2
  periodSeconds: 10

프로브 실패 이벤트는 describe podUnhealthy로 남습니다. 이게 보이면 애플리케이션 자체보다 헬스체크 설계/부팅 시간부터 의심하세요.

5단계: 이미지/엔트리포인트/권한 문제를 “컨테이너 내부 진입”으로 확인

컨테이너가 너무 빨리 죽어 exec가 안 되면, ephemeral debug container 또는 command override로 진입해 원인을 확인합니다.

(A) 디버그 컨테이너로 진입 (권장)

K8s 1.23+에서 지원합니다.

NS=prod
POD=myapp-7b9c9d4c7f-abcde
kubectl -n $NS debug -it $POD --image=busybox:1.36 --target=myapp -- sh

이후 아래를 확인합니다.

  • 실행 파일 존재 여부: ls -al /app
  • 권한/실행 비트: chmod +x 필요 여부
  • 동적 링크: (alpine/musl vs glibc)

(B) entrypoint가 잘못됐는지 빠르게 재현

command/args를 임시로 sleep로 바꿔 “컨테이너는 뜨는지” 확인합니다.

kubectl -n $NS patch deploy myapp --type='json' -p='[
  {"op":"add","path":"/spec/template/spec/containers/0/command","value":["/bin/sh","-c"]},
  {"op":"add","path":"/spec/template/spec/containers/0/args","value":["sleep 3600"]}
]'

컨테이너가 정상 Running이 되면, 원인은 거의 확실히 원래 entrypoint/args 또는 앱 부팅 로직입니다.

6단계: 노드/런타임/스토리지 이슈(로그가 안 남는 대표 원인)

Pod 수준 로그가 없는데 계속 죽는 경우, 실제로는 노드에서 컨테이너 생성이 실패하거나, 노드 리소스가 바닥나서 정상 실행이 불가능한 상황이 자주 있습니다.

(A) 노드 상태/디스크 압박

NS=prod
POD=myapp-7b9c9d4c7f-abcde
NODE=$(kubectl -n $NS get pod $POD -o jsonpath='{.spec.nodeName}')

kubectl describe node $NODE | sed -n '/Conditions:/,/Allocated resources:/p'
  • DiskPressure=True면 이미지 풀/로그 기록/emptyDir 등이 꼬이면서 연쇄 장애가 납니다.
  • 이 경우는 노드 디스크/이미지 GC/로그 로테이션을 함께 봐야 합니다.

관련해서 노드 디스크 압박으로 Evicted가 폭주하는 패턴은 별도 가이드로 정리해 둔 적이 있습니다: EKS 노드 디스크 부족 Evicted 폭주 해결 가이드

(B) containerd/kubelet 로그(노드에서 확인)

관리형 노드(EC2)라면 SSH로 들어가 확인합니다.

sudo journalctl -u kubelet -n 200 --no-pager
sudo journalctl -u containerd -n 200 --no-pager

여기서 failed to create shim task, OCI runtime create failed, no space left on device 같은 메시지가 나오면 Pod 로그가 없어도 설명이 됩니다.

7단계: AWS 연동(권한/네트워크/DNS) 때문에 “초기화 중 즉사”하는 케이스

EKS 앱은 시작하자마자 AWS API(STS, S3, ECR, CloudWatch 등)를 호출하는 경우가 많습니다. 이때 네트워크/DNS/NAT/IRSA 문제가 있으면 앱이 즉시 종료하지만, 로깅 초기화 전에 죽으면 로그가 비어 보일 수 있습니다.

(A) IRSA/STS 호출 타임아웃

IRSA를 쓰는 Pod가 STS에 못 붙으면 SDK가 부팅 단계에서 멈추거나 실패합니다. 특히 프라이빗 서브넷 + NAT/DNS 구성에서 자주 터집니다.

  • 증상: AssumeRoleWithWebIdentity 실패, 타임아웃, DNS NXDOMAIN
  • 조치: VPC 엔드포인트, NAT, CoreDNS, 보안그룹/네트워크 ACL 점검

이 케이스는 아래 글의 체크리스트가 그대로 적용됩니다: EKS STS 엔드포인트 타임아웃 - VPC·NAT·DNS 해결

(B) 시간 오차로 AWS 요청이 거부되는 경우

간헐적으로 노드 시간이 틀어져 S3 같은 요청이 RequestTimeTooSkewed로 실패하고 앱이 종료하는 경우도 있습니다(특히 부팅 직후/시간 동기화 이슈).

관련 증상과 해결은 이 글을 참고하세요: EKS Pod→S3 업로드 403 RequestTimeTooSkewed 해결

(C) 클러스터 내부에서 네트워크/DNS 자체가 불안정

가장 빠른 확인은 “문제 Pod와 동일한 네임스페이스/SA”로 디버그 Pod를 띄워서 DNS/HTTPS를 때려보는 겁니다.

NS=prod
kubectl -n $NS run net-debug --rm -it --image=public.ecr.aws/docker/library/alpine:3.19 -- sh

# inside
apk add --no-cache curl bind-tools
nslookup sts.amazonaws.com
curl -I https://sts.amazonaws.com

이게 실패하면 애플리케이션 문제가 아니라 클러스터 네트워크 레벨입니다.

마무리: 7단계 체크 순서(요약)

운영에서 시간을 아끼려면 아래 순서대로 “분기”만 잘 타도 됩니다.

  1. kubectl describe pod 이벤트로 실패 지점 확인
  2. kubectl logs --previous로 직전 크래시 로그 확인
  3. 종료 코드/Reason(OOMKilled, 127, 139 등)로 1차 분류
  4. probe 설정으로 조기 재시작 여부 확인(startupProbe 적극 활용)
  5. debug/command override로 entrypoint·권한·파일·라이브러리 확인
  6. 노드 DiskPressure/런타임(containerd/kubelet) 로그 확인
  7. IRSA/STS·DNS·NAT·시간오차 등 AWS 연동 초기 실패 점검

CrashLoopBackOff에서 “로그가 없다”는 건 단서가 없는 게 아니라, 로그 이전 단계에서 실패했다는 강한 단서입니다. 위 7단계를 그대로 따라가면, 대부분은 10~20분 안에 원인을 좁힐 수 있습니다.