- Published on
K8s CrashLoopBackOff 8가지 원인, 로그로 끝내기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
CrashLoopBackOff는 Kubernetes가 애플리케이션을 “살리려고” 반복 재시작하는 상태입니다. 즉, 원인은 거의 항상 컨테이너 프로세스가 종료(Exit) 하거나, 프로브(liveness/readiness)가 실패 하거나, 노드/리소스 문제로 강제 종료(OOMKill 등) 되는 데 있습니다.
중요한 포인트는 하나입니다.
- CrashLoopBackOff는 결과(증상)이고, 원인은 이전 컨테이너의 종료 원인(Exit Code / Reason) + 그 직전 로그에 있다.
이 글은 “8가지 대표 원인”을 로그/이벤트로 어떻게 확정하는지에 초점을 맞춥니다. (EKS든 온프레든 동일하게 적용됩니다.)
0) 먼저: 가장 빠른 3단계 수집 루틴
원인 8가지를 보기 전에, 매번 공통으로 쓰는 수집 루틴부터 고정해두면 속도가 압도적으로 빨라집니다.
1) Pod 이벤트/상태 확인
kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod>
describe하단의 Events에 이미지 풀 실패, 프로브 실패, OOMKilled 등의 단서가 뜹니다.
2) “이전 컨테이너” 로그가 핵심
CrashLoop은 컨테이너가 이미 죽었다가 다시 뜨는 패턴이므로 --previous가 핵심입니다.
kubectl -n <ns> logs <pod> -c <container> --previous
kubectl -n <ns> logs <pod> -c <container> --tail=200
3) 종료 코드/Reason을 바로 뽑기
kubectl -n <ns> get pod <pod> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\t"}{.lastState.terminated.finishedAt}{"\n"}{end}'
reason=OOMKilled,exitCode=137같은 패턴이 바로 나오면 절반은 끝난 겁니다.
1) 애플리케이션 즉시 종료(Exit 1/2): 설정/인자/환경변수 문제
가장 흔한 유형입니다. 컨테이너는 정상 실행되어야 하는데, 앱이 부팅 단계에서 예외로 종료됩니다.
로그/증상
exitCode=1또는exitCode=2가 많음kubectl logs --previous에서 스택트레이스, 설정 파일 누락, 파라미터 오류
예시 로그 패턴:
Error: missing required env VAR_XCannot find module ...java.lang.IllegalArgumentException: ...
확인 포인트
kubectl -n <ns> describe pod <pod> | sed -n '/Environment:/,/Mounts:/p'
- Secret/ConfigMap 키 이름 오타
command/args가 이미지의 엔트리포인트와 충돌
빠른 해결
- 앱이 “필수 환경변수 누락 시 즉시 종료”하도록 되어 있다면, 기동 전 검증 로직을 넣되 메시지를 명확히 남기기
command/args를 최소화하고 이미지 기본 엔트리포인트를 신뢰
2) CrashLoopBackOff의 단골: livenessProbe 실패로 강제 재시작
앱은 살아있는데, livenessProbe가 계속 실패하면 kubelet이 컨테이너를 죽이고 재기동합니다. 이때 로그는 “앱이 죽어서”가 아니라 “죽임당해서” 끊길 수 있습니다.
로그/이벤트
kubectl describe pod Events에서 자주 보이는 문구:
Liveness probe failed: ...Back-off restarting failed container
확인 명령
kubectl -n <ns> describe pod <pod> | sed -n '/Liveness:/,/Readiness:/p'
실전 팁
- 초기 부팅이 느린 앱(스프링/Node 대형 번들)은
startupProbe를 반드시 고려 timeoutSeconds/failureThreshold가 너무 공격적이면 “정상 앱”도 죽습니다
관련해서 프로브로 1분마다 재시작되는 케이스를 더 깊게 다룬 글도 참고하세요: EKS Pod 1분마다 재시작? livenessProbe 실패 해결
3) OOMKilled (Exit 137): 메모리 제한 초과로 커널이 강제 종료
CrashLoopBackOff의 “가장 확정적인” 원인 중 하나입니다. 앱이 뻗은 게 아니라 리소스 제한이 앱을 죽입니다.
증상
reason=OOMKilled,exitCode=137- 이벤트에
OOMKilled또는Container killed due to memory usage류
확인
kubectl -n <ns> describe pod <pod> | sed -n '/Last State:/,/Ready/p'
kubectl -n <ns> top pod <pod>
해결 방향
resources.limits.memory상향 또는 JVM/Node 메모리 옵션 조정- Java:
-XX:MaxRAMPercentage=... - Node:
--max-old-space-size=...
- Java:
- 메모리 릭이면, 재시작 주기와 메모리 사용량 증가 곡선을 같이 봐야 합니다
4) CPU 제한/쓰로틀링으로 타임아웃 → 프로브 실패 → CrashLoop
CPU가 부족하면 앱이 느려지고, 결국 프로브 타임아웃으로 재시작될 수 있습니다. 이때 종료 코드는 꼭 137이 아닐 수 있고, 앱 로그도 “단순히 느림”으로 보일 수 있어 헷갈립니다.
단서
- Events에
Liveness probe failed: timeout형태 kubectl top pod에서 CPU가 limit 근처에서 고정
확인/재현 팁
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.containers[0].resources}'
kubectl -n <ns> top pod <pod>
해결
- CPU
requests/limits재조정 - 프로브
timeoutSeconds/periodSeconds완화 - 앱의 cold start 구간에
startupProbe도입
5) 이미지/엔트리포인트 문제: 실행 파일 없음, 권한 없음, 아키텍처 불일치
컨테이너가 “시작” 자체를 못하면 CrashLoop로 보이기도 하지만, 실제로는 시작 직후 즉시 종료하거나(권한/실행파일), 혹은 ImagePullBackOff로 가기도 합니다.
로그/이벤트 패턴
exec /app: no such file or directorypermission deniedstandard_init_linux.go:... exec format error(amd64/arm64 불일치)
확인
kubectl -n <ns> describe pod <pod> | sed -n '/Events:/,$p'
해결
- Dockerfile에서
ENTRYPOINT/CMD확인 - 바이너리 권한:
RUN chmod +x /app - 멀티아키 빌드:
docker buildx build --platform linux/amd64,linux/arm64 ...
6) Secret/ConfigMap 마운트 실패 또는 파일 경로 불일치
앱은 특정 경로의 설정 파일을 전제로 하는데, 마운트가 실패했거나 경로가 달라서 부팅 중 종료하는 케이스입니다.
단서
- Events에
MountVolume.SetUp failed/not found/permission denied - 앱 로그에
cannot open /etc/...No such file등
확인
kubectl -n <ns> describe pod <pod> | sed -n '/Volumes:/,/QoS Class:/p'
kubectl -n <ns> get cm,secret | grep -i <name>
해결
volumeMounts.mountPath와 앱 설정의 경로 일치- Secret 키 이름/파일명 매핑(
items) 점검
7) 의존 서비스 장애( DB/DNS/egress )로 부팅 실패
앱이 “DB 연결 성공”을 기동 조건으로 두면, 외부 의존성이 잠깐만 흔들려도 CrashLoop로 이어집니다. 특히 EKS에서 보안그룹/라우팅/NAT/DNS 이슈가 섞이면 로그만 보고는 감이 안 올 때가 많습니다.
로그 패턴
Connection refused,ECONNRESET,timeout,no route to hostcould not translate host name(PostgreSQL DNS)
네트워크/DNS 확인(임시 디버그 Pod)
kubectl -n <ns> run net-debug --rm -it --image=ghcr.io/nicolaka/netshoot -- bash
# DNS
nslookup <db-host>
# TCP 연결
nc -vz <db-host> 5432
curl -v http://<dependency>
해결 방향
- 앱은 의존성 실패 시 즉시 종료 대신 재시도(backoff) 하도록 설계(특히 DB)
- EKS라면 egress 경로(NAT/라우팅/SG)와 DNS(CoreDNS)까지 함께 점검
EKS에서 egress만 막히는 전형적인 점검 루트는 여기 정리되어 있습니다: EKS에서 Pod는 정상인데 egress만 막힐 때 점검
8) 노드/런타임/시스템 컴포넌트 문제(kube-proxy, CNI, iptables 등)
애플리케이션만 보고 있으면 끝까지 못 잡는 유형입니다. 특정 노드에만 재현되거나, 클러스터 네트워크 구성요소가 꼬이면 “앱이 문제처럼” 보이는 CrashLoop이 발생할 수 있습니다.
단서
- 특정 노드로 스케줄될 때만 CrashLoop
- 같은 이미지/설정인데 어떤 노드에서는 정상
- 노드 이벤트에 네트워크/iptables 관련 에러
확인
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.nodeName}{"\n"}'
kubectl describe node <node>
# 시스템 파드 상태(클러스터에 따라 네임스페이스는 다를 수 있음)
kubectl -n kube-system get pods -o wide
특히 EKS에서 kube-proxy가 iptables 오류로 CrashLoop 나는 케이스는 앱 장애처럼 보이기도 합니다. 해당 유형은 다음 글이 직접적입니다: EKS kube-proxy CrashLoopBackOff iptables 오류 해결
로그로 “원인 확정”하는 체크리스트(요약)
아래 4가지만 정리해도 8가지 원인의 80%는 분류가 됩니다.
describe podEvents에 무엇이 찍히는가?logs --previous에서 마지막 30줄에 무엇이 있는가?lastState.terminated.reason/exitCode는 무엇인가?- 특정 노드에서만 발생하는가? (nodeName 고정)
실전 예시: 한 번에 필요한 정보만 뽑는 스니펫
여러 Pod가 동시에 CrashLoop일 때는 아래처럼 “종료 사유”를 한 번에 뽑아보면 분류가 빨라집니다.
kubectl -n <ns> get pod \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .status.containerStatuses[*]}{.name}{":"}{.lastState.terminated.reason}{"/"}{.lastState.terminated.exitCode}{"\t"}{end}{"\n"}{end}' \
| column -t
OOMKilled/137이 줄줄이 나오면 리소스 문제Error/1이 많으면 앱 부팅 실패(설정/의존성)ContainerCannotRun/exec format error면 이미지/아키텍처
결론
CrashLoopBackOff를 빨리 끝내는 핵심은 “추측”이 아니라 이전 컨테이너의 종료 원인 + 이벤트 + 직전 로그를 묶어서 보는 것입니다. 이 글의 8가지 원인을 기준으로 분류하면, 대부분은 10분 내에 ‘어디를 고쳐야 하는지’가 확정됩니다.
다음 액션은 항상 동일합니다.
- 종료 코드로 큰 갈래(앱 종료 vs 강제 종료 vs 프로브)부터 나누고
- Events로 쿠버네티스가 무엇을 관측했는지 확인한 뒤
--previous로그로 “마지막 한 줄”을 잡아 원인을 못 박으세요.