- Published on
Kubernetes CrashLoopBackOff 원인별 로그·해결 9가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
CrashLoopBackOff는 쿠버네티스 에러라기보다 증상입니다. 핵심은 컨테이너가 Running으로 유지되지 못하고 종료되며, kubelet이 재시작을 반복하다가 백오프(backoff)로 재시작 간격을 늘리는 상태라는 점입니다.
이 글은 다음 순서로 문제를 빠르게 좁힙니다.
- 먼저 어떤 로그를 어디서 봐야 하는지
- 그 다음 대표 원인 9가지를 “증상
->확인 로그->해결”로 정리
또한 CrashLoopBackOff를 Image pull 문제와 혼동하는 경우가 많습니다. 이미지 다운로드 단계에서 막히는 케이스는 아래 글이 더 직접적입니다.
CrashLoopBackOff에서 가장 먼저 보는 로그 3종
1) 이벤트: kubectl describe pod
CrashLoopBackOff의 1차 단서는 대부분 이벤트에 있습니다.
kubectl -n <ns> describe pod <pod>
아래 항목을 집중해서 봅니다.
Last State: Terminated의Reason,Exit CodeEvents의Back-off restarting failed container,Killing,Unhealthy등
2) 현재 로그: kubectl logs
컨테이너가 짧게라도 실행되었다면 애플리케이션 로그가 남습니다.
kubectl -n <ns> logs <pod> -c <container>
3) 직전 크래시 로그: kubectl logs --previous
재시작이 이미 한 번 이상 발생했다면, 직전 실행의 로그가 핵심입니다.
kubectl -n <ns> logs <pod> -c <container> --previous
추가로, 프로세스가 OOMKill로 죽는지 확인하려면 종료 사유를 봅니다.
kubectl -n <ns> get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
원인 1) 엔트리포인트·커맨드 오류로 즉시 종료
전형적 증상
- 로그가 거의 없거나
exec: "...": executable file not found in $PATH Exit Code: 127또는Exit Code: 1
확인 포인트
spec.containers[].command,args- 이미지 내부에 해당 바이너리/스크립트가 존재하는지
kubectl -n <ns> get pod <pod> -o yaml | sed -n '1,160p'
해결
command/args를 이미지의 실제 엔트리포인트와 일치시키기- 스크립트 실행 시
chmod +x, shebang(#!/bin/sh) 확인 - distroless 이미지 사용 시 셸이 없을 수 있으니
sh -c가정 금지
예시: 잘못된 커맨드를 올바르게 수정
containers:
- name: api
image: myrepo/api:1.2.3
command: ["/app/server"]
args: ["--port", "8080"]
원인 2) 애플리케이션 설정/환경변수 누락으로 부팅 실패
전형적 증상
- 로그에
Missing env var,KeyError,NullPointerException같은 초기화 실패 - ConfigMap/Secret 참조 오류
확인 로그
kubectl logs --previous에서 애플리케이션이 시작 직후 예외로 종료describe pod에서CreateContainerConfigError까지는 아니고, 컨테이너가 실행되었다가 종료
해결
- 필수 환경변수에 기본값 제공 또는
required검증을 더 친절하게 - ConfigMap/Secret 키 존재 여부 확인
kubectl -n <ns> get cm <cm-name> -o yaml
kubectl -n <ns> get secret <secret-name> -o yaml
예시: Secret 키를 env로 주입
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: database_url
원인 3) 포트 바인딩 실패 또는 권한 문제
전형적 증상
listen tcp :80: bind: permission deniedEACCES또는Permission denied
왜 발생하나
- 리눅스에서 1024 미만 포트는 일반 사용자로 바인딩 불가
- 파일/디렉터리 권한(로그 디렉터리, 소켓 파일 등)
해결
- 컨테이너 포트를 8080 등으로 올리고 Service에서 80으로 매핑
securityContext를 정리하고, 필요한 디렉터리에 쓰기 권한 부여
예시: 비루트 실행 + 안전한 권한
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
원인 4) liveness/readiness probe 오설정으로 kubelet이 계속 죽임
전형적 증상
- 애플리케이션은 실제로 뜨지만, kubelet이
Unhealthy로 판단해Killing반복 describe pod이벤트에Liveness probe failed가 연속으로 찍힘
확인 로그
kubectl -n <ns> describe pod <pod>
Events 예시(인라인 코드로만 표기): Liveness probe failed: HTTP probe failed with statuscode: 500
해결
- 초기 기동이 느리면
startupProbe를 추가하거나initialDelaySeconds를 늘리기 - readiness와 liveness의 목적을 분리
- readiness: 트래픽 받을 준비
- liveness: 프로세스가 비정상 상태인지
예시: startupProbe로 부팅 구간 보호
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
원인 5) OOMKilled: 메모리 제한으로 강제 종료
전형적 증상
Last State: Terminated의Reason: OOMKilled- 애플리케이션 로그가 중간에 끊기거나 GC 로그 후 종료
확인 포인트
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> top pod <pod>
해결
resources.limits.memory를 현실적으로 올리거나,requests와 함께 재조정- JVM이라면
-Xmx가 cgroup 메모리보다 크지 않게 - 메모리 누수/캐시 폭증을 의심
Spring/Java에서 OOM 분석이 필요하면 힙 덤프 기반 접근이 가장 빠릅니다.
예시: 리소스 가드레일 설정
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
원인 6) Crash on start: 외부 의존성(DB, Kafka, Redis) 연결 실패
전형적 증상
- 로그에
Connection refused,timeout,UnknownHostException - 컨테이너가 재시작되며 백오프
확인 포인트
- DNS 해석 실패인지, 네트워크 정책 차단인지, 인증 실패인지 분리
- 동일 네임스페이스에서 디버그 파드로 확인
kubectl -n <ns> run net-debug --image=busybox:1.36 --restart=Never -it -- sh
# 내부에서
nslookup <service-name>
wget -qO- http://<service-name>:<port>/health
해결
- 부팅 시 외부 의존성에 강하게 결합되어 있으면 재시작 루프가 심해집니다.
- 재시도(backoff) 로직을 애플리케이션에 넣고, 일정 시간은 살아있게 유지
- readiness로 트래픽만 차단하고 프로세스는 유지
- Service/Endpoint 존재 여부 확인
kubectl -n <ns> get svc,endpoints
원인 7) 볼륨 마운트/파일 경로 문제로 초기화 실패
전형적 증상
No such file or directory,Read-only file system- 인증서/설정 파일을 특정 경로에서 읽는데 마운트가 안 됨
확인 로그
- 애플리케이션 로그에서 파일 접근 예외
kubectl describe pod에서 볼륨 관련 이벤트도 함께 확인
해결
volumeMounts.mountPath와 애플리케이션이 참조하는 경로를 일치subPath사용 시 파일/디렉터리 타입 불일치 주의readOnlyRootFilesystem: true사용 시 쓰기 디렉터리는emptyDir로 분리
예시: 쓰기 경로를 emptyDir로 제공
volumes:
- name: tmp
emptyDir: {}
containers:
- name: api
volumeMounts:
- name: tmp
mountPath: /tmp
원인 8) SIGTERM 처리 미흡으로 롤링 업데이트 중 반복 크래시
전형적 증상
- 배포/스케일링 시점에만 CrashLoopBackOff가 튀거나, 종료 직후 재기동이 꼬임
- 종료 처리 중 예외 발생, 워커가 강제 종료되며 상태가 망가짐
확인 포인트
terminationGracePeriodSeconds가 너무 짧은지- 애플리케이션이 SIGTERM을 받아 정상 종료하는지
해결
- 프레임워크별 graceful shutdown 활성화
preStop훅으로 드레이닝 시간을 확보
예시: preStop으로 종료 전 대기
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 30
/bin/sh가 없는 이미지라면 위 커맨드는 실패합니다. 이 경우 앱 내 graceful shutdown에 집중하거나, 셸이 포함된 베이스 이미지로 바꾸는 편이 낫습니다.
원인 9) 노드 시간 불일치/인증서·서명 검증 실패로 즉시 종료
전형적 증상
- TLS 오류:
x509: certificate has expired or is not yet valid - AWS 서명 오류:
RequestTimeTooSkewed - JWT 검증 실패:
clock skew관련 메시지
이 케이스는 애플리케이션이 “보안상 즉시 종료”하도록 구현되어 있으면 CrashLoopBackOff로 이어집니다.
확인 포인트
- 로그에서 시간/서명/인증서 관련 키워드
- 노드의 NTP 동기화 상태, 컨테이너의 시간대 설정
해결
노드 시간 동기화(chrony, systemd-timesyncd) 점검
클러스터에서 특정 노드로만 스케줄될 때 발생하면 해당 노드의 시간 드리프트 의심
AWS API 호출에서
RequestTimeTooSkewed가 난다면 아래 케이스를 함께 점검
재현과 분리: 디버깅을 빠르게 만드는 운영 팁
종료 코드로 1차 분류하기
Exit Code: 137은 OOMKill 가능성이 큼Exit Code: 126은 실행 권한/실행 불가Exit Code: 127은 커맨드/바이너리 없음
kubectl -n <ns> get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
재시작 루프를 잠시 멈추고 내부를 보고 싶을 때
컨테이너가 너무 빨리 죽어 exec가 안 되면, 동일 이미지로 sleep 파드를 띄워 환경을 확인합니다.
kubectl -n <ns> run debug-sleep \
--image=<image> \
--restart=Never \
--command -- sh -c "sleep 3600"
sh가 없는 이미지라면 sleep 바이너리가 있는지부터 확인이 필요합니다. distroless라면 별도 디버그 이미지(busybox 등)로 네트워크/볼륨만 점검하고, 앱 내부는 로그로 접근하는 전략이 안전합니다.
이벤트를 시간순으로 보기
kubectl -n <ns> get events --sort-by=.metadata.creationTimestamp
체크리스트: CrashLoopBackOff를 10분 안에 좁히는 순서
describe pod에서Reason,Exit Code,Events확인logs --previous로 직전 크래시 로그 확보- probe 실패 여부 확인(특히
Unhealthy,Killing) - OOMKilled 여부 확인
- env/config/secret 누락 확인
- 외부 의존성 연결 실패인지 DNS부터 확인
- 볼륨 마운트 경로/권한 확인
- 엔트리포인트/커맨드와 이미지 타입(distroless 여부) 확인
- 시간 드리프트/TLS/JWT 서명 검증 오류 확인
CrashLoopBackOff는 대부분 “컨테이너가 왜 종료되었는가”로 환원됩니다. 종료 코드와 직전 로그(--previous)만 제대로 잡아도, 원인의 80%는 위 9가지 중 하나로 빠르게 분류됩니다.