- Published on
EKS Pod CrashLoopBackOff 로그 없을 때 7단계 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스가 아니라 EKS(쿠버네티스)에서 CrashLoopBackOff가 뜨는데 kubectl logs가 비어 있으면, 보통 프로세스가 stdout/stderr로 아무것도 출력하기 전에 종료했거나, 컨테이너가 아예 시작되지 못한 상태입니다. 이때는 “로그를 더 찾는” 접근보다, 컨테이너 생명주기(스케줄링 → 이미지 풀 → 컨테이너 생성 → 프로브 통과 → 앱 기동) 중 어디에서 끊겼는지 단계적으로 좁히는 게 가장 빠릅니다.
아래 7단계는 현장에서 재현 빈도가 높은 순서대로 정리했습니다. 각 단계는 확인 포인트 → 대표 원인 → 바로 실행할 명령 중심으로 구성했습니다.
0) 전제: 로그가 왜 없을까?
로그가 없는 케이스는 크게 4가지로 압축됩니다.
- 컨테이너가 생성되기 전(이미지 풀 실패, 권한, CNI 문제 등)
- 컨테이너는 생성됐지만 즉시 종료(entrypoint 오류, 동적 로더/라이브러리 missing, config parsing 실패 등)
- 프로브가 너무 빨라서 강제 종료(readiness/liveness가 앱 기동보다 빠름)
- 로그를 볼 경로가 틀림(이전 컨테이너 로그
--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 pod에 Unhealthy로 남습니다. 이게 보이면 애플리케이션 자체보다 헬스체크 설계/부팅 시간부터 의심하세요.
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단계 체크 순서(요약)
운영에서 시간을 아끼려면 아래 순서대로 “분기”만 잘 타도 됩니다.
kubectl describe pod이벤트로 실패 지점 확인kubectl logs --previous로 직전 크래시 로그 확인- 종료 코드/Reason(OOMKilled, 127, 139 등)로 1차 분류
- probe 설정으로 조기 재시작 여부 확인(
startupProbe적극 활용) - debug/command override로 entrypoint·권한·파일·라이브러리 확인
- 노드 DiskPressure/런타임(containerd/kubelet) 로그 확인
- IRSA/STS·DNS·NAT·시간오차 등 AWS 연동 초기 실패 점검
CrashLoopBackOff에서 “로그가 없다”는 건 단서가 없는 게 아니라, 로그 이전 단계에서 실패했다는 강한 단서입니다. 위 7단계를 그대로 따라가면, 대부분은 10~20분 안에 원인을 좁힐 수 있습니다.