- Published on
K8s CrashLoopBackOff 원인별 빠른 진단·복구
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 아니라 Pod가 계속 재시작될 때, Kubernetes는 친절하게 CrashLoopBackOff라는 상태를 보여줍니다. 하지만 이 상태 자체가 원인은 아닙니다. 컨테이너가 비정상 종료(Exit Code != 0) 하거나, 프로브 실패로 강제 종료되거나, 시작조차 못 한 채 실패하는 등 다양한 실패가 누적되어 “재시도(back-off)”에 들어간 결과입니다.
이 글은 “왜 죽는지”를 빠르게 분류하고, 가장 짧은 경로로 복구하는 방법을 원인별로 정리합니다. 운영에서 중요한 건 정답 풀이가 아니라 시간을 줄이는 진단 루틴입니다.
CrashLoopBackOff 한 줄 정의와 관찰 포인트
CrashLoopBackOff: 컨테이너가 반복적으로 종료되어 kubelet이 재시작 간격을 점점 늘리는 상태- 핵심 지표
- Exit Code (예: 1, 137, 139)
- Reason (Error, OOMKilled, Completed 등)
- Events (ImagePullBackOff, Back-off restarting failed container, Unhealthy 등)
- Restart count (재현성/빈도)
아래 3가지만 먼저 확보하면 대부분 5분 내로 방향이 잡힙니다.
# 1) 현재 상태/리스타트 확인
kubectl get pod -n <ns> <pod> -o wide
# 2) 이벤트/종료 사유 확인 (가장 중요)
kubectl describe pod -n <ns> <pod>
# 3) 직전 크래시 로그 확인 (CrashLoopBackOff에서는 --previous가 핵심)
kubectl logs -n <ns> <pod> -c <container> --previous
> 팁: kubectl logs가 비어있다면 “애플리케이션이 로그를 남기기 전에 죽었거나”, “엔트리포인트가 실행되지 못했거나”, “stdout/stderr로 로그를 안 내보내는 구조”일 수 있습니다. 이때는 describe의 Last State / Terminated / Exit Code가 더 중요합니다.
0분 컷: 원인 분류를 위한 결정 트리
아래 질문 순서대로 보면, 원인의 70~80%는 빠르게 분류됩니다.
describe의 State: Waiting 인가?ImagePullBackOff,ErrImagePull→ 이미지/레지스트리CreateContainerConfigError→ env/secret/configmap/volumeCreateContainerError→ 런타임/권한/마운트
- State: Terminated 인가?
- Exit Code 137 / Reason OOMKilled → 메모리
- Exit Code 139 → segfault(네이티브/런타임)
- Exit Code 0인데 재시작? → 프로브/사이드카/정책
- 이벤트에
Unhealthy(Readiness/Liveness) 가 있는가?- 프로브 실패로 kill/restart
- 노드 이슈/스케줄링 이슈인가?
kubectl get events에FailedMount,FailedScheduling,NodeNotReady등
원인 1) 애플리케이션 즉시 종료(Exit Code 1 등)
가장 흔한 케이스입니다. 설정 누락, 잘못된 인자, DB 연결 실패 시 즉시 exit(1)로 떨어지며 CrashLoopBackOff로 이어집니다.
빠른 진단
kubectl logs -n <ns> <pod> -c <container> --previous --tail=200
kubectl describe pod -n <ns> <pod> | sed -n '/Last State:/,/Events:/p'
- 로그에
missing env,cannot connect,permission denied,config parse error같은 문구가 보이면 거의 확정 Last State: Terminated의Finished시간이 촘촘하면 “시작 직후 죽음”
즉시 복구
- 환경변수/시크릿/컨피그맵 누락이면 배포 스펙부터 수정
- 임시 우회: 앱이 “필수 의존성”이 준비될 때까지 기다리게 만들기 (initContainer, startupProbe)
예: DB가 준비되기 전 앱이 죽어버리는 경우, initContainer로 대기
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
initContainers:
- name: wait-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z db 5432; do echo waiting; sleep 2; done']
containers:
- name: api
image: myrepo/api:1.0.0
원인 2) OOMKilled (Exit Code 137)
메모리 부족으로 커널이 프로세스를 강제 종료하면 Kubernetes는 OOMKilled로 표시하고 재시작합니다.
빠른 진단
kubectl describe pod -n <ns> <pod> | grep -E 'OOMKilled|Exit Code|Reason' -n
# metrics-server가 있다면
kubectl top pod -n <ns> <pod>
Reason: OOMKilled,Exit Code: 137이면 거의 확정- JVM/Node/Python도 메모리 스파이크가 가능
즉시 복구
- requests/limits 조정 (limit이 너무 낮으면 즉시 OOM)
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
- 애플리케이션 레벨
- JVM:
-Xms/-Xmx를 limit보다 여유 있게 - Node:
--max-old-space-size - 대용량 캐시/버퍼 사용 여부 확인
- 재발 방지
- HPA/메모리 기반 스케일링
- 메모리 프로파일링/누수 점검
원인 3) Liveness/Readiness/Startup Probe 실패
앱이 살아있는데도 프로브가 실패하면 kubelet이 컨테이너를 죽이고 재시작합니다. 특히 livenessProbe가 너무 공격적이면 정상 부팅 중인 앱도 계속 죽습니다.
빠른 진단
describe의 Events에 이런 패턴이 보입니다.
Unhealthy+Liveness probe failedBack-off restarting failed container
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
즉시 복구
- 부팅 시간이 긴 앱은
startupProbe를 도입하고, 그 전까지 liveness를 유예 timeoutSeconds,failureThreshold,periodSeconds를 현실적으로 조정
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 60
periodSeconds: 2
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
> 운영 팁: “일단 살려야” 하면 livenessProbe를 잠시 제거하고 원인 분석 후 복구하는 것도 방법입니다(단, 장애 격리/자동복구는 약해짐).
원인 4) ImagePullBackOff / ErrImagePull (CrashLoopBackOff처럼 보이는 착시)
엄밀히는 CrashLoopBackOff가 아니라 이미지 풀 단계에서 막히는 경우가 많습니다. 하지만 현장에서는 “Pod가 안 뜬다”로 묶여 들어옵니다.
빠른 진단
kubectl describe pod -n <ns> <pod> | grep -E 'ErrImagePull|ImagePullBackOff|Failed to pull image' -n
즉시 복구
- 이미지 태그 오타/미푸시
- 레지스트리 인증(ImagePullSecret)
- 프라이빗 네트워크에서 레지스트리 접근 불가
spec:
imagePullSecrets:
- name: regcred
원인 5) CreateContainerConfigError (Secret/ConfigMap/Env/Volume)
컨테이너가 시작되기 전, kubelet이 설정을 구성하다 실패하는 케이스입니다.
빠른 진단
kubectl describe pod -n <ns> <pod>
# Events에
# - configmap "xxx" not found
# - secret "yyy" not found
# - couldn't find key ...
즉시 복구
- 참조하는 Secret/ConfigMap 이름/키 확인
- 배포 순서(먼저 secret 생성 후 deploy)
kubectl get secret -n <ns>
kubectl get configmap -n <ns>
원인 6) 권한/보안 컨텍스트 문제(파일/포트 바인딩/PSA)
- 1024 미만 포트 바인딩을 non-root로 시도
- 볼륨 마운트 경로에 쓰기 권한 없음
- Pod Security Admission(구 PSP) 정책에 의해 제한
빠른 진단
kubectl logs -n <ns> <pod> -c <container> --previous | tail -n 200
# permission denied, operation not permitted 등이 단서
즉시 복구
securityContext로 runAsUser/fsGroup 설정- 필요한 경우만 capability 추가(최소 권한)
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: web
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
원인 7) 노드/네트워크/iptables 계열(특히 CNI/kube-proxy)
애플리케이션이 아니라 클러스터 네트워킹이 깨지면, 의존성 호출 실패로 앱이 죽거나(예: DB/DNS 접근 실패), 시스템 파드가 직접 CrashLoopBackOff에 빠질 수 있습니다.
- DNS 실패:
Temporary failure in name resolution - 서비스 라우팅 문제: iptables/conntrack
EKS에서 kube-proxy가 iptables 오류로 CrashLoopBackOff 나는 케이스는 별도 패턴과 처방이 있습니다. 아래 글을 함께 보면 원인 좁히는 시간이 크게 줄어듭니다.
또한 “외부 API 호출이 502/게이트웨이로 터지며 앱이 종료” 같은 패턴이라면, 네트워크/프록시/엔드포인트 계층을 분리해 확인해야 합니다.
원인 8) 디스크/파일시스템 이슈(로그 폭증, ephemeral storage)
노드 디스크가 가득 차거나, 컨테이너가 파일을 쓰지 못하면 앱이 비정상 종료할 수 있습니다.
빠른 진단
- 이벤트에
evicted,DiskPressure,Failed to write등 - 노드에서 삭제된 파일이 열려 공간이 회수되지 않는 경우도 흔합니다(특히 로그).
이런 “디스크 100%인데 큰 파일이 안 보이는” 상황은 lsof로 추적하는 게 정석입니다.
즉시 복구
- 로그 로테이션/사이즈 제한
- ephemeral-storage requests/limits 설정
- 노드 정리(불필요한 이미지/컨테이너)
실전: 10분 내 복구를 위한 커맨드 세트
1) 한 번에 정보 모으기
NS=<ns>
POD=<pod>
kubectl get pod -n $NS $POD -o wide
kubectl describe pod -n $NS $POD
kubectl logs -n $NS $POD --all-containers --previous --tail=200
2) 특정 컨테이너만 계속 죽는지 확인
kubectl get pod -n <ns> <pod> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'
3) 재현을 위해 임시로 컨테이너 진입(디버그)
CrashLoop이면 컨테이너가 바로 죽어 exec가 안 될 수 있습니다. 이때는 ephemeral debug container 또는 동일 이미지로 별도 Pod를 띄워 확인합니다.
# ephemeral debug container (클러스터/버전에 따라 지원)
kubectl debug -n <ns> -it <pod> --image=busybox:1.36 --target=<container>
# 또는 동일 이미지로 임시 실행
kubectl run -n <ns> tmp-shell --rm -it --image=myrepo/api:1.0.0 --command -- sh
재발 방지 체크리스트(운영 관점)
- 프로브 설계: liveness는 “진짜로 죽었을 때만” 실패하도록, startupProbe로 부팅 보호
- 리소스 계약: requests/limits를 근거 기반으로 설정하고 OOM 이벤트를 알림화
- 의존성 준비: initContainer/재시도 로직/서킷브레이커로 일시 장애에 죽지 않게
- 배포 안전장치:
maxUnavailable,maxSurge, PDB로 롤아웃 중 전체 다운 방지 - 관측성: 종료 코드/이벤트를 로그로 남기고, 재시작 횟수 급증을 경보로
마무리: CrashLoopBackOff는 “증상”, 답은 describe에 있다
CrashLoopBackOff 대응에서 가장 흔한 실수는 로그만 보다가 시간을 쓰는 것입니다. 실제로는 kubectl describe pod의 Events + Last State(Exit Code/Reason) 만으로도 원인의 절반 이상이 갈립니다.
describe로 분류 → 2)--previous로그로 확정 → 3) 프로브/리소스/설정/권한/네트워크 중 해당 축을 즉시 수정
이 루틴을 팀 런북으로 고정해두면, “원인 찾기”가 아니라 “복구 시간”이 줄어듭니다.