Published on

K8s CrashLoopBackOff 8가지 원인, 로그로 끝내기

Authors

서론

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_X
  • Cannot 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=...
  • 메모리 릭이면, 재시작 주기메모리 사용량 증가 곡선을 같이 봐야 합니다

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 directory
  • permission denied
  • standard_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 host
  • could 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%는 분류가 됩니다.

  1. describe pod Events에 무엇이 찍히는가?
  2. logs --previous에서 마지막 30줄에 무엇이 있는가?
  3. lastState.terminated.reason/exitCode는 무엇인가?
  4. 특정 노드에서만 발생하는가? (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 로그로 “마지막 한 줄”을 잡아 원인을 못 박으세요.