- Published on
K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
CrashLoopBackOff는 Kubernetes가 장애를 감지해 컨테이너를 재시작하는 과정에서, 재시작 간격(back-off)이 점점 늘어나며 표시되는 상태입니다. 즉, CrashLoopBackOff 자체가 원인이 아니라 결과입니다.
이 글에서는 운영 환경에서 가장 자주 만나는 패턴을 기준으로:
- 어떤 순서로 로그와 이벤트를 수집해야 하는지
livenessProbe/readinessProbe/startupProbe가 어떻게 크래시를 유발하는지- 원인별로 YAML을 어떻게 수정해야 하는지
를 실전 중심으로 정리합니다.
1) 먼저 확인할 것: “왜 죽었는지”를 가장 빨리 찾는 순서
CrashLoopBackOff를 보면 바로 kubectl logs부터 치는 경우가 많지만, 이벤트와 종료 코드를 먼저 보면 시간을 크게 줄일 수 있습니다.
1-1. Pod 이벤트와 종료 코드 확인
kubectl -n <namespace> describe pod <pod-name>
describe에서 특히 다음을 봅니다.
State: Terminated의Reason/Exit Code/Finished시간Last State: Terminated(재시작 직전의 종료 정보)- 하단
Events섹션
자주 나오는 힌트:
OOMKilled또는Exit Code: 137이면 메모리/리소스 문제 가능성이 큼Error+Exit Code: 1이면 앱 프로세스가 스스로 종료(설정/의존성/권한 등)Liveness probe failed가 반복되면 “앱은 살아있지만 Probe가 죽이고 있는” 구조일 수 있음
OOMKilled 패턴은 내용이 길어 별도 글로 정리해두었습니다.
1-2. 직전 크래시 로그 보기(--previous)
CrashLoopBackOff 상황에서 가장 중요한 옵션은 --previous입니다. 현재 컨테이너가 이미 다시 뜬 상태라 “죽기 직전 로그”가 안 보일 수 있기 때문입니다.
kubectl -n <namespace> logs <pod-name> -c <container-name> --previous
만약 멀티 컨테이너 Pod라면 -c를 꼭 명시하세요. 사이드카는 멀쩡하고 앱 컨테이너만 죽는 경우가 흔합니다.
1-3. Deployment/ReplicaSet 이벤트도 같이 보기
Probe 실패나 이미지 풀, 스케줄링 문제는 Pod 단위보다 상위 리소스 이벤트가 더 명확할 때가 있습니다.
kubectl -n <namespace> describe deploy <deploy-name>
kubectl -n <namespace> get rs
kubectl -n <namespace> describe rs <rs-name>
2) 원인별 패턴: 로그·이벤트로 구분하는 법
CrashLoopBackOff의 원인은 크게 아래로 묶을 수 있습니다.
- 앱이 즉시 종료(설정 오류, 의존성 실패, 권한/자격증명 문제)
- 리소스 문제(OOMKilled, CPU throttling, ephemeral storage)
- Probe가 컨테이너를 죽임(특히 liveness)
- 신호 처리/Graceful shutdown 미흡으로 재시작 루프
- 노드/런타임/이미지 문제(드물지만 치명적)
이 중 이 글은 로그와 Probe 관점에서 자주 터지는 케이스를 집중적으로 다룹니다.
3) 앱이 즉시 종료하는 케이스: 설정/의존성/권한
3-1. 환경변수/ConfigMap/Secret 누락
증상:
Exit Code: 1또는Exit Code: 2- 로그에
missing env/config not found/failed to load류
대표적으로 Spring Boot, Node.js, Go 모두 “필수 환경변수 없으면 즉시 종료” 패턴이 많습니다.
확인:
kubectl -n <namespace> get pod <pod-name> -o yaml | sed -n '1,200p'
kubectl -n <namespace> get configmap
kubectl -n <namespace> get secret
해결:
- 필수 키를
ConfigMap/Secret에 추가 - 앱이 “필수 설정 누락”일 때는 CrashLoop로 들어가므로, 가능하면 앱에서 명확한 에러 메시지를 남기고 종료하도록 유지
- 운영에서는
optional: true를 남발하지 말고, 누락 시 조기에 실패하도록 설계하는 편이 디버깅에 유리
3-2. 외부 의존성(DB/Redis/Kafka) 연결 실패로 즉시 종료
증상:
- 로그에
connection refused,timeout,no such host - readiness/liveness 이전에 앱이 종료
이 경우 “앱이 의존성 연결 실패 시 즉시 종료하는 설정”인지 확인해야 합니다. 운영에서는 보통 다음 중 하나로 정리합니다.
- 앱은 살아있되 readiness만 실패(트래픽 차단)
- 또는 initContainer로 의존성 준비를 기다린 뒤 앱 시작
패턴 A: initContainer로 의존성 대기
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 2
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
initContainers:
- name: wait-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z -w 2 db 5432; do echo waiting db; sleep 2; done"]
containers:
- name: app
image: myrepo/app:1.0.0
ports:
- containerPort: 8080
- 장점: 앱이 “연결 실패 즉시 종료” 정책이어도 루프를 줄일 수 있음
- 단점: 의존성이 영구 장애면 Pod가 영구 Pending처럼 보일 수 있음(운영 정책에 따라 선택)
패턴 B: startupProbe로 “부팅 시간/의존성 준비”를 보호
아래 4장에서 자세히 다룹니다.
3-3. 클라우드 권한/자격증명 문제로 종료
EKS에서 특히 흔합니다.
- S3 접근 시
403또는 STS 토큰 문제 - AWS SDK가 자격증명을 못 찾고 종료
이런 경우 앱 로그는 보통 빠르게 찍히지만, 운영자가 놓치기 쉬운 건 “Probe가 죽인 게 아니라 앱이 먼저 종료했다”는 점입니다.
관련 케이스는 아래 글들도 참고하세요.
4) Probe가 CrashLoop를 만든다: liveness/readiness/startup의 오해
Probe는 “헬스체크”가 아니라 Kubelet이 컨테이너를 재시작/트래픽 제외하는 정책입니다.
readinessProbe실패: 트래픽만 제외(재시작은 하지 않음)livenessProbe실패: 컨테이너를 죽이고 재시작(= CrashLoop 유발 가능)startupProbe실패: 초기 부팅 구간에서만 실패를 허용/제어(부팅이 느린 앱 필수)
4-1. 가장 흔한 실수: 느린 부팅인데 liveness를 너무 일찍 때림
증상:
- 이벤트에
Liveness probe failed반복 - 앱 로그를 보면 아직 마이그레이션/캐시 워밍업 중
- 재시작 때문에 부팅이 끝나지 못하고 루프
해결의 핵심은 startupProbe 도입입니다.
잘못된 예시
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
부팅이 30초 걸리는데 5초부터 3번 실패하면 15초 내에 죽습니다.
권장 예시: startupProbe로 부팅 구간 보호
startupProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24 # 최대 120초까지 부팅 허용
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
포인트:
startupProbe가 성공하기 전까지는 liveness/readiness가 사실상 “잠시 무력화”되는 효과- readiness는
/ready처럼 “의존성 준비까지 포함한 진짜 준비 상태”를 보게 설계
4-2. readiness를 liveness로 쓰면 장애가 증폭된다
/health가 DB 연결까지 포함하고, DB가 일시적으로 느려지면 어떻게 될까요?
- readiness 실패면 트래픽만 제외되어 버틸 수 있음
- liveness 실패면 컨테이너가 재시작되며, 오히려 DB에 재연결 폭탄을 던짐
권장 분리:
- liveness: 프로세스가 “죽었는지/교착인지”만 확인(가벼워야 함)
- readiness: 외부 의존성(DB/캐시/메시지 브로커)까지 포함 가능
4-3. timeoutSeconds를 너무 빡세게 잡는 문제
특히 Java/Spring, Node, Python에서 GC, 이벤트 루프 지연, 콜드 캐시 등으로 헬스 엔드포인트가 간헐적으로 1초를 넘는 경우가 있습니다.
timeoutSeconds: 1은 운영에서 생각보다 공격적입니다.- 최소 2초, 보통 2초에서 5초 사이가 현실적입니다.
4-4. execProbe에서 셸 스크립트가 실패하는 케이스
exec probe는 디버깅이 어렵습니다. 스크립트가 set -e로 인해 예상치 못한 종료를 하거나, PATH 문제로 커맨드를 못 찾는 일이 잦습니다.
예시:
livenessProbe:
exec:
command: ["sh", "-c", "curl -sf http://127.0.0.1:8080/health"]
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
개선 팁:
- 가능하면
httpGet을 우선 고려 curl을 이미지에 포함하지 않는다면 probe는 항상 실패wget/curl의존성 자체가 장애 포인트가 될 수 있으니 최소화
5) 로그가 안 남는 크래시: 즉시 종료/시그널/표준출력 문제
5-1. 애플리케이션이 너무 빨리 죽어 로그가 잘리는 경우
kubectl logs --previous로도 빈 로그가 나올 수 있음- 프로세스가 시작하자마자 크래시하거나, stdout flush 전에 종료
대응:
- 앱 로거를 stdout로 내보내고, 버퍼링을 줄이기
- 시작 직후 핵심 설정을 한 줄이라도 출력(버전, 환경, 필수 설정 로딩 결과)
5-2. SIGTERM 처리 미흡으로 종료가 꼬이는 경우
Kubernetes는 종료 시 SIGTERM을 보내고 terminationGracePeriodSeconds 동안 기다립니다. 앱이 SIGTERM을 무시하거나, 종료 훅이 너무 오래 걸리면 다음 배포/스케일링에서 연쇄 문제가 생길 수 있습니다.
점검:
preStop훅이 과도하게 길지 않은지- graceful shutdown이 실제로 동작하는지
예시:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
preStop은 “정말 필요한 최소 시간”만 사용하세요. 불필요한 sleep은 장애 시 복구 시간을 늘립니다.
6) CrashLoopBackOff에서 자주 보이는 이벤트 메시지별 처방
6-1. Back-off restarting failed container
의미:
- 컨테이너가 계속 죽어서 Kubelet이 재시작 간격을 늘리는 중
처방:
describe pod에서 종료 코드와 reason 확인logs --previous로 직전 로그 확보- Probe 이벤트가 있다면 4장 방식으로 조정
6-2. Liveness probe failed
의미:
- 앱이 “죽지 않았을 수도 있는데” liveness가 죽이고 있음
처방:
startupProbe도입 또는initialDelaySeconds/failureThreshold완화- liveness 엔드포인트를 더 가볍게 분리
- timeout을 현실적으로 늘리기
6-3. Readiness probe failed
의미:
- 트래픽만 제외되는 중(크래시 원인은 아닐 수 있음)
처방:
- readiness가 외부 의존성을 포함한다면, 의존성 장애 시 서비스가 “정상적으로 제외”되는지 확인
- readiness 실패가 장기화되면 HPA/롤링업데이트에서 영향을 받을 수 있으니 원인(의존성, DNS, 네트워크 정책)을 추적
7) 실전 디버깅 체크리스트(명령어 모음)
아래 순서대로 보면 대부분의 케이스가 정리됩니다.
# 1) 현재 상태 요약
kubectl -n <namespace> get pod <pod-name> -o wide
# 2) 이벤트/종료 코드/Probe 실패 확인
kubectl -n <namespace> describe pod <pod-name>
# 3) 직전 크래시 로그 확보
kubectl -n <namespace> logs <pod-name> -c <container-name> --previous
# 4) 현재 컨테이너 로그(재시작 이후)
kubectl -n <namespace> logs <pod-name> -c <container-name>
# 5) 배포 리소스 이벤트 확인
kubectl -n <namespace> describe deploy <deploy-name>
# 6) (필요 시) 컨테이너 내부에서 헬스 엔드포인트 직접 확인
kubectl -n <namespace> exec -it <pod-name> -c <container-name> -- sh
# inside
# curl -v http://127.0.0.1:8080/health
exec가 안 될 정도로 컨테이너가 빨리 죽으면, 임시로 command를 바꿔 “살아있게” 만든 뒤 내부를 보는 방법도 있습니다.
containers:
- name: app
image: myrepo/app:1.0.0
command: ["sh", "-c", "sleep 3600"]
이후 exec로 들어가 설정 파일, 환경변수, DNS, 네트워크를 점검합니다. 원인 파악 후 원래 커맨드로 복구하세요.
8) 재발 방지 설계: Probe와 관측성을 같이 맞추기
CrashLoopBackOff를 “한 번 고치고 끝”내려면, Probe와 로그/메트릭이 함께 정리되어야 합니다.
- liveness는 가볍게, readiness는 의존성 포함 가능, 부팅은 startupProbe로 보호
- 헬스 엔드포인트는 실패 이유를 구분 가능하게(예: 캐시 미연결, DB 타임아웃)
- 애플리케이션 시작 시 필수 설정/버전을 한 줄로 출력
- 종료 코드/시그널을 로그에 남기기
특히 EKS 환경에서 네트워크/의존성 문제는 gRPC 같은 프로토콜에서 “간헐적 실패”로 보이다가, 잘못된 liveness로 인해 재시작 폭풍으로 번지는 경우가 있습니다. 관련 사례는 아래 글도 참고할 만합니다.
9) 마무리: CrashLoopBackOff는 ‘증상’이고, 답은 로그와 Probe에 있다
정리하면, CrashLoopBackOff 대응의 핵심은 아래 3줄입니다.
kubectl describe pod로 종료 코드와 이벤트를 먼저 본다kubectl logs --previous로 “죽기 직전 로그”를 확보한다- Probe는
startupProbe를 중심으로 부팅 구간을 보호하고, liveness를 가볍게 만든다
이 3가지만 습관화해도 “원인 파악에 몇 시간 쓰던” CrashLoopBackOff가 대부분 10분 내로 정리됩니다.