- Published on
K8s Pod CrashLoopBackOff 원인 7가지와 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 죽었다가 다시 뜨는 현상은 익숙하지만, Kubernetes의 CrashLoopBackOff는 훨씬 더 빠르고(초 단위), 원인도 훨씬 다양합니다. 특히 애플리케이션 자체 오류뿐 아니라 프로브(liveness/readiness), 리소스 제한, 볼륨/권한, 네트워크/외부 의존성 등 “플랫폼 레이어” 이슈가 함께 얽혀서 진단이 어려워집니다.
이 글에서는 CrashLoopBackOff를 7가지 대표 원인으로 분류하고, 각 원인별로 어떤 증거를 먼저 봐야 하는지, 어떻게 고치는지, 재발 방지 체크리스트까지 정리합니다.
CrashLoopBackOff 빠른 이해: 무엇이 ‘BackOff’인가
- 컨테이너 프로세스가 종료되면 kubelet이 재시작합니다.
- 일정 시간 내 반복 종료가 발생하면 kubelet은 재시작 간격을 점점 늘립니다(BackOff).
- 즉, CrashLoopBackOff는 “원인이 해결되지 않은 채 재시작만 반복”되는 상태입니다.
아래 커맨드로 기본 증거를 확보합니다.
# 상태/재시작 횟수 확인
kubectl get pod -n <ns> <pod> -o wide
# 이벤트(BackOff, OOMKilled, probe fail 등) 확인
kubectl describe pod -n <ns> <pod>
# 현재 컨테이너 로그
kubectl logs -n <ns> <pod> -c <container>
# 직전(이전) 크래시 로그: CrashLoopBackOff에서 특히 중요
kubectl logs -n <ns> <pod> -c <container> --previous
> 팁: describe의 Events 섹션과 --previous 로그만으로도 절반은 결론이 납니다.
원인 1) 애플리케이션 프로세스 즉시 종료(Exit Code 1/2/…)
대표 증상
kubectl logs --previous에 스택트레이스/설정 파싱 오류/포트 바인딩 실패 등이 보임describe에Last State: Terminated+Exit Code: 1등
진단 포인트
- 애플리케이션이 “정상적으로 오래 떠 있는 프로세스”인지 확인
- 예: 웹 서버는 foreground로 실행되어야 함
- 잘못된 엔트리포인트로 스크립트가 실행 후 종료되는 경우 많음
kubectl get pod -n <ns> <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
해결 방법
command/args또는 DockerfileENTRYPOINT/CMD수정- 설정 파일/환경변수 누락 수정
- 포트 충돌/권한 문제 해결(예: 1024 미만 포트 바인딩)
예: 잘못된 command로 프로세스가 즉시 끝나는 케이스 수정
containers:
- name: app
image: myapp:1.0
# 잘못된 예: init 스크립트만 실행하고 종료
# command: ["/bin/sh", "-c", "./init.sh"]
# 올바른 예: 서버 프로세스를 foreground로 실행
command: ["/bin/sh", "-c", "./init.sh && exec ./server --port=8080"]
원인 2) livenessProbe 실패로 강제 재시작(‘정상인데 죽는’ 케이스)
CrashLoopBackOff의 고전적인 함정은 앱은 살아있지만 livenessProbe가 실패해서 kubelet이 죽이는 상황입니다.
대표 증상
describe이벤트에Liveness probe failed가 반복- 재시작 주기가 일정(예: 30초~1분)하게 반복
kubectl describe pod -n <ns> <pod> | sed -n '/Events:/,$p'
흔한 원인
- 앱이 기동에 오래 걸리는데 초기부터 liveness가 때림
/healthz가 외부 의존성(DB 등)까지 포함해 “가끔” 실패timeoutSeconds가 너무 짧음
해결 방법
- 기동 시간이 길면
startupProbe를 도입 - liveness는 “프로세스 생존”에 집중, readiness는 “트래픽 수신 가능”에 집중
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 2
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
timeoutSeconds: 2
periodSeconds: 5
이 주제는 원인/패턴이 많아 별도 글로 정리해두었습니다: EKS Pod 1분마다 재시작? livenessProbe 실패 해결
원인 3) OOMKilled: 메모리 제한 초과로 커널이 종료
대표 증상
describe에Reason: OOMKilledExit Code: 137(SIGKILL)로 끝나는 경우가 많음
kubectl describe pod -n <ns> <pod> | grep -E 'OOMKilled|Exit Code|Reason' -n
진단 포인트
- requests/limits가 실제 사용량 대비 너무 낮은지
- JVM/Node/Python 등 런타임의 기본 메모리 설정이 컨테이너 제한과 불일치하는지
메트릭 서버가 있다면:
kubectl top pod -n <ns> <pod>
해결 방법
resources.limits.memory상향 또는 메모리 누수 수정- JVM이라면
-XX:MaxRAMPercentage등 컨테이너 인지 설정 - Node.js라면
--max-old-space-size조정
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1"
memory: "1Gi"
> 팁: OOM은 “앱 버그”일 수도 있지만, 운영에서 더 흔한 건 limit를 너무 타이트하게 잡은 설정 문제입니다.
원인 4) Image/권한/파일시스템 문제: 읽기 전용, 실행 권한, 루트 권한 필요
CrashLoopBackOff는 이미지 자체는 잘 받아왔는데 컨테이너 시작 직후 권한 문제로 종료되는 형태로도 자주 나타납니다.
대표 증상
- 로그에
permission denied,exec format error,no such file or directory readOnlyRootFilesystem: true인데 앱이/tmp나/var에 쓰기 시도
진단 포인트
- 보안 컨텍스트(
runAsNonRoot,fsGroup,readOnlyRootFilesystem) 확인 - 실행 파일 권한/라인엔딩/아키텍처(arm64 vs amd64) 확인
해결 방법
- 쓰기 필요한 경로를
emptyDir로 마운트 securityContext에서 필요한 권한만 최소로 부여
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
원인 5) ConfigMap/Secret/환경변수 누락 또는 잘못된 값
구성 오류는 “배포 직후 즉시 크래시” 패턴을 만듭니다.
대표 증상
- 로그에
missing env,failed to load config,invalid yaml/json,connection string parse error describe에CreateContainerConfigError가 섞여 보이기도 함(완전 크래시는 아니지만 자주 동반)
진단 포인트
envFrom,valueFrom.secretKeyRef키 이름 오타- Secret이 base64 인코딩은 맞지만 내용이 잘못됨(개행 포함 등)
kubectl get secret -n <ns> <secret> -o yaml
kubectl get configmap -n <ns> <cm> -o yaml
해결 방법
- 필수 환경변수는 앱 시작 시점에 명확한 에러 메시지로 검증
- Helm/Kustomize 템플릿에서 키 이름을 상수화/검증
예: 필수 키 누락을 빠르게 드러내는 패턴(애플리케이션 레벨)
# entrypoint.sh
set -euo pipefail
: "${DATABASE_URL:?DATABASE_URL is required}"
exec ./server
원인 6) 볼륨 마운트/스토리지 문제로 앱이 시작 실패(PVC/권한/경로)
엄밀히는 PVC가 Pending이면 Pod가 Running까지 못 가지만, 마운트는 되었는데 앱이 원하는 경로/권한/파일이 없어 즉시 종료하면 CrashLoopBackOff로 보입니다.
대표 증상
- 로그에
No such file or directory(데이터 디렉터리),permission denied(볼륨 소유권) - initContainer 없이 앱이 “필수 디렉터리 생성”을 못 하고 종료
진단 포인트
- 마운트 경로가 앱 설정과 일치하는지
- EBS/EFS 등 CSI 드라이버 환경에서 소유권/퍼미션 이슈가 있는지
해결 방법
initContainer로 디렉터리/권한 준비fsGroup설정으로 볼륨 권한 정리
securityContext:
fsGroup: 2000
initContainers:
- name: init-perms
image: busybox:1.36
command: ["sh", "-c", "mkdir -p /data && chown -R 2000:2000 /data"]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
PVC 자체가 Pending/프로비저닝 실패라면 아래 글에서 체크리스트를 참고하세요: Kubernetes PVC Pending - EBS CSI 동적 프로비저닝 실패 해결
원인 7) 노드 리소스/디스크 압박 및 Eviction 여파(간접적 CrashLoop)
노드가 DiskPressure/MemoryPressure 상태면 이미지 레이어/로그/emptyDir 등이 꼬이면서 앱이 비정상 종료하거나 Evicted→재스케줄→재시작 루프로 보일 수 있습니다.
대표 증상
- 이벤트에
Evicted,node had condition: DiskPressure - 이미지 풀/컨테이너 생성이 불안정하게 실패
kubectl describe node <node> | grep -E 'DiskPressure|MemoryPressure|PIDPressure|Evict' -n
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 30
해결 방법
- 노드 디스크 확장, 로그/이미지 정리, ephemeral storage requests/limits 설정
- DaemonSet 로그 폭주/캐시 폭주 원인 제거
이 케이스는 운영에서 빈도가 높아 별도 가이드가 있습니다: EKS 노드 디스크 부족 Evicted 폭주 해결 가이드
실전 트러블슈팅 플로우(10분 컷)
아래 순서대로 보면 대부분의 CrashLoopBackOff는 빠르게 좁혀집니다.
kubectl describe pod의 Events에서probe failed,OOMKilled,Evicted키워드 확인kubectl logs --previous로 “직전 죽기 전 로그” 확보- 종료 코드 확인(1: 앱 에러, 137: OOM 가능성, 143: SIGTERM 등)
- 프로브가 있다면 일시적으로 완화/비활성화 후 정상 기동 여부 확인
- 리소스 제한/노드 압박 확인(
kubectl top, node conditions) - ConfigMap/Secret 키/값 검증
- 파일시스템/볼륨/권한(특히
readOnlyRootFilesystem,fsGroup) 확인
재발 방지 체크리스트
startupProbe로 “기동 구간”과 “운영 구간”을 분리- liveness는 최소 조건(프로세스 생존), readiness는 트래픽 수신 조건
--previous로그가 남도록 로깅/종료 메시지 정리(구성 검증 에러를 친절히)- 메모리 limit는 런타임 특성(JVM/Node 등) 반영해 여유 있게
readOnlyRootFilesystem사용 시/tmp등 쓰기 경로를emptyDir로 제공- 스토리지는 initContainer로 권한/디렉터리 준비
- 노드 디스크/로그/이미지 관리(특히 DaemonSet 로그 폭주 주의)
CrashLoopBackOff는 “원인”이 아니라 “현상”입니다. 중요한 건 이벤트(Events) + 이전 로그(--previous) + 종료 코드 3종 세트를 먼저 확보하고, 그 다음에 프로브/리소스/권한/스토리지/노드 상태로 분류해 들어가는 것입니다. 이 루틴만 팀 내 표준으로 만들어도, 장애 대응 시간이 눈에 띄게 줄어듭니다.