- Published on
K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
CrashLoopBackOff는 Kubernetes가 컨테이너를 시작했지만 곧바로 종료(exit)되어 재시작을 반복할 때 나타나는 상태입니다. 중요한 점은 CrashLoopBackOff가 ‘원인’이 아니라 증상이라는 것입니다. 같은 CrashLoopBackOff라도 실제 원인은 다음처럼 전혀 다를 수 있습니다.
- 애플리케이션이 즉시 종료(설정 누락, 예외, 마이그레이션 실패)
livenessProbe가 과격해 정상 프로세스를 죽임- OOMKilled(메모리 부족)로 커널이 종료
- 이미지/엔트리포인트/권한 문제로 프로세스가 뜨지 못함
- 의존 서비스(DB/Redis/EFS 등) 연결 타임아웃으로 스타트업 실패
이 글은 로그/이벤트/프로브 중심의 진단 루틴을 먼저 제시하고, 흔한 원인별로 “무엇을 보면 확정할 수 있는지”와 “어떻게 고치면 재발을 막는지”를 정리합니다.
1) 가장 먼저 보는 것: 상태, 이벤트, 이전 로그
CrashLoopBackOff는 재시작이 반복되므로 현재 로그(kubectl logs)만 보면 원인 로그가 이미 날아간 경우가 많습니다. 그래서 아래 순서가 안전합니다.
1-1. Pod 상태/컨테이너 종료 사유 확인
kubectl get pod -n <ns> <pod> -o wide
kubectl describe pod -n <ns> <pod>
describe에서 특히 아래를 봅니다.
Last State: Terminated의Reason,Exit Code,FinishedEvents섹션의Back-off restarting failed containerOOMKilled,Error,Completed여부
1-2. “이전(previous)” 로그가 핵심
컨테이너가 재시작된 상황이면 이전 인스턴스 로그를 봐야 합니다.
# 현재 실행 중인 컨테이너 로그
kubectl logs -n <ns> <pod> -c <container>
# 직전 종료된 컨테이너 로그(가장 중요)
kubectl logs -n <ns> <pod> -c <container> --previous
1-3. 재시작 횟수/패턴으로 원인 좁히기
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[0].restartCount}'
- 수 초 단위로 바로 죽으면: 엔트리포인트/설정/권한/즉시 예외 가능성
- 30~60초 등 일정 지연 후 죽으면: 프로브(liveness) 또는 스타트업 타임아웃 가능성
- 부하가 걸린 뒤 죽으면: OOM/스레드 폭증/외부 의존성 타임아웃 가능성
2) 원인 A: 애플리케이션이 즉시 종료(exit 1/2/…)
가장 흔한 유형입니다. 컨테이너는 정상 실행되지만 앱이 내부 오류로 종료합니다.
2-1. 확인 포인트
kubectl logs --previous에 스택트레이스/환경변수 누락/설정 파일 미존재Exit Code: 1(일반 오류),Exit Code: 2(잘못된 인자) 등
2-2. 자주 나오는 케이스
- 환경변수(예:
DATABASE_URL) 미설정 - ConfigMap/Secret 키 이름 오타
- 마이그레이션 실패로 프로세스 종료
- 앱이 “foreground”가 아니라 즉시 종료되는 방식으로 실행됨
2-3. 해결 패턴
(1) ConfigMap/Secret 키를 명시적으로 검증
앱 시작 시 필수 설정을 검사하고, 누락 시 명확한 로그를 남기도록 합니다.
(2) 엔트리포인트가 즉시 종료되지 않는지 확인
예: Dockerfile에서 CMD ["sh", "-c", "python init.py"]처럼 init만 하고 끝나면 컨테이너도 종료됩니다. 메인 프로세스를 실행해야 합니다.
3) 원인 B: OOMKilled (메모리 부족)
메모리 부족은 CrashLoopBackOff의 대표 원인입니다. 특히 JVM/Node/Python에서 초기 로딩, 캐시 워밍, 대용량 파일 처리 시 잘 터집니다.
3-1. 확인 포인트
kubectl describe pod에서:
Last State: Terminated→Reason: OOMKilled- 이벤트에
Killed관련 메시지
또는 아래로도 빠르게 확인 가능합니다.
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
3-2. 해결 패턴
(1) requests/limits 재설정
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
requests는 스케줄링 기준,limits는 강제 상한입니다.- OOM은 대개
limits.memory가 너무 낮거나, 앱 메모리 튜닝이 부족해서 발생합니다.
(2) 런타임별 메모리 상한 튜닝
- JVM:
-XX:MaxRAMPercentage,-Xms/-Xmx - Node.js:
--max-old-space-size - Python: 워커 수/동시성 제한, 대용량 처리 스트리밍
4) 원인 C: livenessProbe가 정상 프로세스를 죽인다
livenessProbe는 “죽은 프로세스를 재시작”하는 장치인데, 설정이 공격적이면 살아있는 프로세스도 죽여서 CrashLoopBackOff를 만듭니다.
4-1. 확인 포인트
- 이벤트에
Liveness probe failed반복 - 재시작 주기가
periodSeconds와 유사 - 앱 로그에는 큰 오류가 없는데도 재시작됨
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
4-2. 가장 흔한 실수
- 앱 기동이 느린데
initialDelaySeconds가 너무 짧음 timeoutSeconds가 너무 짧아 순간 지연에 실패- DB 의존 엔드포인트를 liveness로 체크(외부 장애가 곧 프로세스 kill로 이어짐)
4-3. 해결: startupProbe를 먼저 도입
기동 시간이 긴 앱은 startupProbe로 “초기 부팅 구간”을 보호하고, 이후에만 liveness를 적용하는 패턴이 안정적입니다.
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
startupProbe가 성공하기 전에는livenessProbe가 동작하지 않습니다.readinessProbe는 트래픽 유입 여부만 제어하므로, 외부 의존성(DB 등)을 포함해도 상대적으로 안전합니다.
5) 원인 D: readinessProbe 실패를 Crash로 오해하는 경우
엄밀히 말해 readiness 실패는 CrashLoopBackOff를 만들지 않습니다. 하지만 다음 상황에서는 “계속 재시작되는 것처럼” 보일 수 있습니다.
- 상위 오케스트레이션(Argo CD/HPA/배포 스크립트)이 readiness 실패를 장애로 보고 롤백/재배포
- 서비스가 트래픽을 못 받아서 외부에서 헬스체크 실패 → 자동 재시작 정책이 개입
GitOps 환경이라면 배포/동기화 상태도 함께 확인하는 게 좋습니다. 동기화/헬스가 꼬인 케이스는 Argo CD Sync 실패 - OutOfSync·Degraded 해결법도 같이 참고하면 진단 속도가 빨라집니다.
6) 원인 E: 의존 서비스(네트워크/스토리지) 타임아웃으로 스타트업 실패
앱이 시작 과정에서 DB/Redis/EFS 등을 붙다가 실패하면 exit → CrashLoopBackOff로 이어집니다.
6-1. 확인 포인트
- 로그에
Connection timed out,ECONNREFUSED,DNS실패 - 특정 시간(예: 10분) 이후에 죽는다면 라이브러리 기본 타임아웃 가능성
예를 들어 EKS에서 Redis 연결이 특정 패턴으로 오래 걸릴 때는 네트워크 경로/보안그룹/NACL/DNS/클라이언트 타임아웃을 같이 봐야 합니다. 비슷한 진단 흐름은 EKS Pod→ElastiCache Redis 10분 타임아웃 진단법에 정리해 두었습니다.
스토리지 마운트가 원인인 경우도 많습니다. Pod는 Running으로 보이는데 컨테이너 내부는 마운트 대기/타임아웃으로 죽는 형태가 나오기도 합니다. EFS 계열은 EKS에서 Pod는 뜨는데 EFS Mount 타임아웃 해결처럼 VPC/DNS/보안그룹/마운트 타깃을 함께 점검해야 합니다.
6-2. 해결 패턴
- 앱 스타트업에서 외부 의존성을 “필수”로 묶지 말고 재시도/backoff 적용
- readiness에 의존성 체크를 넣고, liveness는 프로세스 생존만 판단
- DNS 이슈 의심 시
nslookup,dig를 디버그 파드로 수행
디버그용 임시 파드 예시:
kubectl run -n <ns> net-debug --rm -it --image=busybox:1.36 -- sh
# inside
nslookup redis.my-namespace.svc.cluster.local
wget -S -O- http://my-service:8080/healthz
7) 원인 F: 잘못된 command/args, 권한, 파일 경로 문제
컨테이너가 뜨자마자 죽는데 로그도 거의 없으면 엔트리포인트/권한 문제를 의심합니다.
7-1. 확인 포인트
kubectl describe에Error: failed to start container류 메시지exec format error(아키텍처 불일치),permission denied,no such file or directory
7-2. 해결 패턴
command/args를 이미지 기준으로 다시 확인WORKDIR/상대경로 사용 시 실제 경로 존재 여부 확인runAsNonRoot사용 시 실행 파일 권한/소유자 점검
보안 컨텍스트 예시:
securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
readOnlyRootFilesystem: true를 켠 상태에서 앱이 /tmp나 루트에 파일을 쓰면 즉시 죽을 수 있습니다. 이 경우 emptyDir로 쓰기 가능 경로를 제공해야 합니다.
8) 프로브 설계 체크리스트(재발 방지 핵심)
CrashLoopBackOff를 “고쳤다”가 아니라 “다시 안 터지게 했다”로 만들려면 프로브 설계가 중요합니다.
8-1. 역할 분리
startupProbe: 기동 완료까지 보호(느린 부팅/마이그레이션/캐시 워밍)readinessProbe: 트래픽 받을 준비(외부 의존성 포함 가능)livenessProbe: 프로세스가 살아있는지(외부 의존성 제외 권장)
8-2. HTTP 엔드포인트 추천 형태
/live: 단순 200(프로세스 루프/스레드 상태 등 최소)/ready: DB ping, 큐 연결, 필수 리소스 준비 여부 확인/healthz: 종합 상태(관측용)
8-3. 타임아웃/임계치 가이드
timeoutSeconds: 최소 2~5초(네트워크/GC 고려)failureThreshold: 일시적 스파이크를 흡수할 정도로periodSeconds: 너무 촘촘하면 장애 시 증폭
9) 실전 트러블슈팅 루틴(요약)
아래 순서로 보면 대부분 10분 내에 원인 범주를 좁힐 수 있습니다.
kubectl describe pod로Last State,Exit Code,Events확인kubectl logs --previous로 “죽기 직전” 로그 확보OOMKilled면 리소스/런타임 튜닝부터Liveness probe failed면 startupProbe 도입 + liveness 완화- 외부 의존성 오류면 readiness로 격리 + 재시도/backoff + 네트워크/DNS 점검
- 엔트리포인트/권한 문제면
command/args, 아키텍처, 파일 권한 재검증
결론
CrashLoopBackOff는 Kubernetes가 친절하게 “계속 죽어서 다시 띄우고 있다”라고 알려주는 신호일 뿐, 해결의 열쇠는 **이전 로그(--previous)와 이벤트(특히 probe 실패/리소스 킬)**에 있습니다.
- 로그로 애플리케이션 종료 원인을 확정하고
- 이벤트로 kubelet이 왜 재시작을 유도했는지(프로브/OOM)를 확인하며
startup/readiness/liveness의 역할을 분리해 재발을 막으면
CrashLoopBackOff는 대부분 “한 번의 패치”가 아니라 운영 가능한 헬스체크/리소스 설계로 끝낼 수 있습니다.