- Published on
K8s CrashLoopBackOff 원인별 진단·해결 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/VM 장애와 달리 Kubernetes의 CrashLoopBackOff는 **"프로세스가 정상적으로 떠 있지 못한다"**는 신호를 가장 직접적으로 드러냅니다. 하지만 원인은 애플리케이션 버그부터 이미지/권한/리소스/프로브/스토리지까지 광범위합니다. 이 글은 현장에서 가장 많이 만나는 패턴을 원인별로 분류하고, kubectl 명령 중심으로 진단 → 증거 수집 → 해결 순서로 정리합니다.
참고로 “로그가 아예 안 나오는” 케이스는 접근법이 약간 다른데, 그 경우는 별도 글인 EKS Pod CrashLoopBackOff 로그 없을 때 7단계 진단도 함께 보면 좋습니다.
CrashLoopBackOff의 의미와 재시작 메커니즘
CrashLoopBackOff는 Kubernetes 이벤트/상태에서 보이는 백오프(Backoff) 재시작 상태입니다.- 컨테이너가 종료되면 kubelet이 재시작을 시도하고, 반복 실패 시 재시작 간격을 점점 늘립니다.
- 핵심은 “왜 종료되는가”이며, 종료 원인은 크게 아래로 나뉩니다.
- 애플리케이션이 즉시 종료(예: 설정 누락으로
exit 1) - OOMKilled 등 커널에 의해 강제 종료
- liveness/startup probe 실패로 kubelet이 kill
- 볼륨 마운트/권한/시크릿 등 런타임 의존성이 충족되지 않음
- 엔트리포인트/명령/아키텍처 불일치
0단계: “지금 무엇이 죽는지” 60초 안에 확인
아래 3가지는 거의 모든 케이스에서 첫 화면으로 봅니다.
1) Pod 상태와 재시작 횟수
kubectl get pod -n <ns> <pod> -o wide
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[*].restartCount}'
RESTARTS가 급격히 증가하면 CrashLoop 패턴입니다.- 노드 변경이 잦다면 스케줄링/노드 리소스 문제 가능성도 올라갑니다.
2) describe로 종료 이유/이벤트 확인
kubectl describe pod -n <ns> <pod>
여기서 특히 봐야 할 곳:
Containers:→Last State: Terminated→Reason,Exit Code,FinishedEvents:→Back-off restarting failed container,Killing container,Unhealthy등
3) 이전(직전) 컨테이너 로그 확인
kubectl logs -n <ns> <pod> -c <container> --previous
- CrashLoop일 때는 현재 컨테이너가 이미 죽어있어 로그가 비어 보일 수 있으므로
--previous가 중요합니다.
1) Exit Code로 원인 빠르게 분류하기
종료 코드/Reason은 원인 분류의 지름길입니다.
Exit Code 1/2/…: 앱이 스스로 종료
- 설정 파일/환경변수 누락
- 외부 의존성(DB, Redis, API) 연결 실패 시 즉시 종료
- 마이그레이션 실패, 포트 바인딩 실패
진단 체크:
kubectl logs -n <ns> <pod> -c <container> --previous
kubectl exec -n <ns> -it <pod> -c <container> -- env | sort
해결 포인트:
ConfigMap/Secret키 이름 오타, 마운트 경로 불일치- 앱이 의존성 실패 시 즉시 종료하지 말고 재시도/지수 백오프 적용
- readiness로 트래픽 유입을 막고, liveness는 너무 공격적으로 두지 않기
Exit Code 137 / Reason=OOMKilled: 메모리 부족
Exit Code 137은 SIGKILL(대개 OOM) 가능성이 큽니다.describe에서Reason: OOMKilled면 거의 확정.
진단 체크:
kubectl describe pod -n <ns> <pod> | sed -n '/Last State:/,/Events:/p'
kubectl top pod -n <ns> <pod>
해결 포인트:
resources.limits.memory상향 또는 메모리 사용량 감소- JVM/Node/Python 등 런타임 힙 설정을 limit에 맞추기
- 캐시/버퍼가 큰 라이브러리(예: 대용량 모델 로딩, 이미지 처리)라면 초기 로딩 메모리도 고려
예시(YAML):
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1"
memory: "512Mi"
Exit Code 139: Segmentation fault
- 네이티브 라이브러리, glibc/musl 호환, 잘못된 바이너리, 메모리 접근 버그
진단 체크:
kubectl logs -n <ns> <pod> -c <container> --previous
kubectl describe pod -n <ns> <pod> | grep -E 'Exit Code|Reason|Message' -n
해결 포인트:
- 이미지 베이스(alpine vs debian)와 네이티브 의존성 호환성 확인
- CPU 아키텍처(amd64/arm64) 불일치 여부 확인
2) 프로브(liveness/readiness/startup) 설정이 과격한 경우
CrashLoopBackOff의 “범인”이 앱이 아니라 프로브인 경우가 생각보다 많습니다.
전형적 증상
- 앱은 느리게 뜨는데 liveness가 먼저 실패 → kubelet이 kill → 무한 반복
- 로그에는 큰 에러가 없고,
Events에Unhealthy/Killing container가 반복
진단 체크:
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[0].livenessProbe}'
해결 전략(권장 패턴):
- startupProbe로 “부팅 시간”을 별도로 보호
- readiness는 트래픽 차단용, liveness는 “정말로 죽었을 때만”
예시:
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
팁:
- 부팅이 느린 앱(마이그레이션, 모델 로딩, 캐시 워밍업)은 startupProbe 없이 liveness만 두면 거의 사고가 납니다.
3) 이미지/엔트리포인트/명령 문제
전형적 증상
exec format error(아키텍처 불일치)no such file or directory(ENTRYPOINT 경로/권한/라인엔딩)permission denied(실행 비트, runAsUser)
진단 체크:
kubectl logs -n <ns> <pod> -c <container> --previous
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[0].image}'
해결 포인트:
- 멀티 아키텍처 이미지라면
linux/amd64,linux/arm64매니페스트 확인 - 셸 스크립트가 CRLF로 들어가면
/bin/sh^M: bad interpreter형태로 터질 수 있음 - distroless 이미지에서
/bin/sh가 없는데command: ["sh", "-c", ...]를 쓰는 실수
예시(명령 오버라이드):
containers:
- name: app
image: myrepo/app:1.2.3
command: ["/app/server"]
args: ["--port=8080"]
4) ConfigMap/Secret/환경변수/볼륨 마운트 문제
앱이 “필수 설정이 없으면 즉시 종료”하는 경우 CrashLoop이 됩니다.
전형적 증상
- 로그에
missing env/cannot read config/file not found describe에MountVolume.SetUp failed이벤트
진단 체크:
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
kubectl get cm -n <ns>
kubectl get secret -n <ns>
해결 포인트:
- 키 이름/경로 오타,
subPath사용 시 파일 교체 이슈 - Secret이 다른 네임스페이스에 있는지(기본적으로 불가)
- CSI 드라이버/외부 시크릿 연동(예: External Secrets) 동기화 지연
예시(Secret env 주입):
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-db
key: password
5) 권한/보안 컨텍스트(runAsUser, fsGroup)로 인한 즉시 종료
컨테이너는 떠도, 앱이 파일/포트에 접근 못해 죽는 케이스입니다.
전형적 증상
EACCES: permission denied,Operation not permitted- 1024 미만 포트 바인딩 실패(비루트)
진단 체크:
kubectl logs -n <ns> <pod> -c <container> --previous
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.securityContext}'
kubectl get pod -n <ns> <pod> -o jsonpath='{.spec.containers[0].securityContext}'
해결 포인트:
- 쓰기 디렉터리에는
emptyDir를 마운트하고 권한 맞추기 - PV 사용 시
fsGroup설정으로 그룹 권한 부여
예시:
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
6) 리소스/노드 이슈: OOM 말고도 CPU 스로틀링, 디스크 압박
OOMKilled가 아니어도 CPU 스로틀링으로 타임아웃이 나고, 그 결과 프로브 실패 → 재시작 루프로 이어질 수 있습니다. 또한 노드 디스크 압박(DiskPressure)은 이미지 풀/로그 쓰기 실패를 유발합니다.
진단 체크:
kubectl top pod -n <ns>
kubectl describe node <node>
해결 포인트:
requests를 너무 낮게 잡으면 CPU 부족으로 부팅이 길어져 startupProbe가 필요- 노드 디스크/이미지 GC 정책 점검
7) 의존성(DB/외부 API) 장애로 ‘즉시 종료’하는 설계
K8s에서 가장 흔한 안티패턴 중 하나가 “DB 연결 실패하면 프로세스 종료”입니다. 일시 장애에도 CrashLoop이 되어 오히려 복구를 더 늦춥니다.
권장 설계
- 프로세스는 살아있되 readiness를
false로 만들어 트래픽만 차단 - 재시도/서킷브레이커/타임아웃을 명시적으로 둠
예시(간단한 Node.js 의존성 재시도 의사코드):
import pRetry from 'p-retry';
async function connectDB() {
return pRetry(async () => {
// 실제 DB 연결
}, { retries: 10, minTimeout: 500, maxTimeout: 5000 });
}
(async () => {
await connectDB();
// 서버 시작
})();
DB 타임아웃/커넥션 풀 고갈로 장애가 확대되는 패턴은 애플리케이션 레벨에서도 자주 보이므로, 원인 분석 시에는 앱 내부 리소스(풀, 타임아웃)도 함께 확인하는 게 좋습니다. (관련 주제: Spring Boot HikariCP 풀 고갈·DB 타임아웃 10분 진단)
8) 실전 “원인별 체크리스트” 요약
아래는 현장에서 빠르게 체크하는 순서입니다.
A. 상태/이벤트
kubectl describe pod에서Last State/Exit Code/ReasonEvents에Unhealthy,Killing,Back-off패턴 확인
B. 로그
kubectl logs --previous필수- 로그가 없다면 엔트리포인트/권한/프로브/즉시 크래시를 의심
C. 대표 원인 매핑
OOMKilled/137→ 메모리 limit, 런타임 힙, 초기 로딩Unhealthy이벤트 반복 → startupProbe/liveness 튜닝MountVolume실패 → Secret/ConfigMap/PV/CSIpermission denied→ securityContext, fsGroup, 쓰기 경로exec format error→ 아키텍처/이미지 빌드
9) 트러블슈팅을 “재현 가능”하게 만드는 팁
CrashLoopBackOff는 재시작이 너무 빨라서 관찰이 어렵습니다. 아래 방법으로 관찰 시간을 확보합니다.
1) 임시로 command를 sleep으로 바꿔 내부 확인
kubectl -n <ns> patch deploy <deploy> --type='json' \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","sleep 3600"]}]'
- distroless면
sh가 없으니 이미지에 맞게 조정해야 합니다.
2) Ephemeral container로 디버깅(가능한 클러스터에서)
kubectl debug -n <ns> -it <pod> --image=busybox --target=<container>
- 네트워크/DNS/파일 마운트 상태를 빠르게 확인할 수 있습니다.
마무리: “죽는 이유”를 Exit Code와 Events로 고정하라
CrashLoopBackOff를 빠르게 끝내는 핵심은 추측이 아니라 **증거(Exit Code/Reason/Events/previous logs)**로 원인을 고정하는 것입니다. 특히 프로브 설정과 리소스 제한은 “정상 앱도 죽일 수 있는” 인프라 레벨 요인이므로, 앱 로그만 보다가 시간을 낭비하지 않도록 describe의 이벤트와 종료 사유를 항상 먼저 확인하세요.
추가로, 로그가 아예 남지 않는 까다로운 케이스는 EKS Pod CrashLoopBackOff 로그 없을 때 7단계 진단에서 더 깊게 다룹니다.