- Published on
Kubernetes CrashLoopBackOff 원인 7가지·즉시복구
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
운영 중인 파드가 CrashLoopBackOff 로 떨어지면 대부분 “컨테이너가 계속 죽고, kubelet이 재시작을 반복하지만 점점 재시작 간격(백오프)이 늘어나는 상태”입니다. 문제는 원인이 애플리케이션 버그일 수도, 설정 실수일 수도, 리소스/스토리지/프로브 같은 쿠버네티스 레이어일 수도 있다는 점입니다.
이 글은 즉시 복구(서비스 영향 최소화) 를 먼저 하고, 이후 근본 원인(RCA) 를 빠르게 좁히는 순서로 정리합니다.
관련해서 클러스터에서 kubectl logs/exec 자체가 막히는 상황이라면 먼저 아래 글을 참고해 접근성부터 복구하는 게 좋습니다.
CrashLoopBackOff를 “정확히” 이해하기
CrashLoopBackOff는 에러 원인이 아니라 증상입니다.- 컨테이너가 종료 코드와 함께 종료되고, kubelet이 재시작 정책에 따라 재기동합니다.
- 재시작이 반복되면
BackOff로 재시작 간격이 점점 늘어납니다.
따라서 핵심은 두 가지입니다.
- 왜 종료되는가 (exit code, signal, OOM, 설정 오류 등)
- 왜 kubelet이 죽었다고 판단했는가 (liveness probe, startup probe, 프로세스 종료 등)
즉시 복구 우선순위(장애 대응 플레이북)
아래 순서대로 보면 “지금 당장 트래픽을 살릴 수 있는지”와 “원인을 가장 빨리 드러내는지”를 동시에 만족합니다.
1) 이벤트로 1차 분류: describe 가 가장 빠르다
kubectl -n <namespace> describe pod <pod-name>
인라인에서 <namespace> 와 <pod-name> 은 반드시 백틱으로 감싼 형태로 사용하세요.
Events 에서 다음 키워드를 먼저 찾습니다.
Back-off restarting failed containerLiveness probe failedReadiness probe failedOOMKilledError: ImagePullBackOff(이건 CrashLoopBackOff 이전 단계일 때도 많음)MountVolume.SetUp failed
2) 로그 확보: “현재 로그”와 “이전 로그”를 둘 다 본다
CrashLoop는 프로세스가 빨리 죽기 때문에 이전 컨테이너 로그가 결정적입니다.
kubectl -n <namespace> logs <pod-name> -c <container-name>
kubectl -n <namespace> logs <pod-name> -c <container-name> --previous
3) 롤아웃 즉시 되돌리기(가장 안전한 즉시복구)
최근 배포 이후부터 발생했다면 원인 분석보다 먼저 롤백으로 서비스부터 복구하는 편이 낫습니다.
kubectl -n <namespace> rollout history deploy/<deploy-name>
kubectl -n <namespace> rollout undo deploy/<deploy-name>
4) 프로브로 인해 죽는다면 “일시 완화”로 숨통 틔우기
Liveness probe failed 로 계속 재시작한다면, 짧은 시간이라도 프로브를 완화해 원인 분석 시간을 벌 수 있습니다.
startupProbe를 추가하거나livenessProbe.initialDelaySeconds를 늘리거나failureThreshold를 늘립니다.
kubectl -n <namespace> patch deploy/<deploy-name> --type='json' -p='[
{"op":"replace","path":"/spec/template/spec/containers/0/livenessProbe/initialDelaySeconds","value":60},
{"op":"replace","path":"/spec/template/spec/containers/0/livenessProbe/failureThreshold","value":10}
]'
주의: 이 조치는 “근본 해결”이 아니라 재시작 루프를 잠깐 멈추는 응급처치입니다.
5) 리소스 이슈가 의심되면 즉시 상향(특히 메모리)
OOMKilled 가 보이면 “원인 분석” 전에 메모리를 올려 서비스부터 살릴 수 있습니다.
kubectl -n <namespace> set resources deploy/<deploy-name> \
--containers=<container-name> \
--limits=memory=1Gi,cpu=500m \
--requests=memory=512Mi,cpu=200m
CrashLoopBackOff 원인 7가지(진단 포인트와 해결)
1) 애플리케이션이 즉시 종료됨(설정/환경변수/엔트리포인트 오류)
가장 흔한 케이스입니다.
- 잘못된
ENTRYPOINT또는CMD - 필수 환경변수 누락
- 설정 파일 경로 불일치
- 포트 바인딩 실패
진단
kubectl -n <namespace> logs <pod-name> --previous
kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
해결 체크리스트
envFrom의ConfigMap/Secret키가 실제로 존재하는지- 애플리케이션이 “포그라운드”로 실행되는지(데몬화되면 컨테이너가 종료될 수 있음)
- Dockerfile에서
CMD가 셸 확장에 의존하지 않는지
예: 필수 환경변수 미설정 방지(헬스체크 전에 빠르게 실패시키기)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: myapp:1.0.0
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secret
key: databaseUrl
2) OOMKilled(메모리 부족) 또는 과도한 GC/캐시
describe 의 컨테이너 상태에 OOMKilled 가 찍히면 거의 확정입니다.
진단
kubectl -n <namespace> describe pod <pod-name>
kubectl -n <namespace> top pod <pod-name>
즉시복구
- 메모리 limit 상향
- 워커 수/동시성 하향
- 캐시/버퍼 크기 축소
근본해결
- 언어 런타임 옵션 조정(예: Node.js
--max-old-space-size) - 메모리 릭 분석
- HPA 기준을 CPU만 보지 말고 메모리도 고려
Java/Spring 계열에서 DB 커넥션/스레드가 누수나 대기 폭증을 유발하면 간접적으로 메모리 압박이 커질 수 있습니다. 관련 진단 패턴은 아래 글도 참고할 만합니다.
3) Liveness/Startup/Readiness 프로브 설정 실패
프로브 실패는 “프로세스는 살아있는데 kubelet이 죽였을” 가능성이 큽니다.
전형적인 실수
- 앱 부팅이 느린데
startupProbe없이livenessProbe만 둠 timeoutSeconds가 너무 짧음- HTTP 헬스 엔드포인트가 의존성(DB, 외부 API)까지 강하게 결합되어 일시 장애에 취약
진단
kubectl -n <namespace> describe pod <pod-name>
# Events에서 probe failed 메시지 확인
권장 설정 예시(느린 부팅 앱)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
포인트
startupProbe는 “부팅 완료 전까지 liveness를 유예”하는 용도readinessProbe는 “트래픽 투입 여부”만 제어하고, 보통 재시작과는 직접 연결되지 않음
4) 이미지/아키텍처/런타임 불일치(실행 파일이 안 뜸)
exec format error(예: ARM 노드에 AMD64 바이너리)- 라이브러리 누락(동적 링크 실패)
- 컨테이너가 시작하자마자 크래시
진단
kubectl -n <namespace> logs <pod-name> --previous
해결
- 멀티 아키텍처 이미지 빌드(
linux/amd64,linux/arm64) - distroless 사용 시 필요한 동적 라이브러리 포함 여부 확인
command/args로 엔트리포인트가 덮어써지지 않았는지 점검
5) ConfigMap/Secret/볼륨 마운트 문제로 프로세스가 실패
파일 기반 설정을 읽는 앱에서 흔합니다.
- 키가 없어서 파일이 비어 있음
- 마운트 경로가 앱 기대와 다름
- 권한 문제로 읽기 실패
진단
kubectl -n <namespace> describe pod <pod-name>
kubectl -n <namespace> get cm <configmap-name> -o yaml
kubectl -n <namespace> get secret <secret-name> -o yaml
즉시복구 팁
- 최근에 ConfigMap만 바꿨다면 “Deployment 재시작”으로 반영
kubectl -n <namespace> rollout restart deploy/<deploy-name>
근본해결
- 설정 파일 경로를 코드/차트에서 상수화
- 필수 키 존재 여부를 CI에서 검증
6) 의존성(DB, Kafka, 외부 API) 연결 실패를 “치명적 종료”로 처리
앱이 부팅 중 DB 연결 실패를 만나면 그대로 exit(1) 하는 패턴이 있습니다. 그러면 외부 의존성의 일시 장애가 곧바로 CrashLoop로 번집니다.
진단
- 로그에
connection refused,timeout,authentication failed같은 메시지 확인
즉시복구
- 의존성 복구가 먼저(네트워크, SG, DNS, 인증서)
- 앱이 “재시도(backoff) 후 계속 실행”하도록 동작 변경
근본해결
- 부팅 시 의존성 체크를
readinessProbe로 옮기고, 프로세스는 살아 있게 유지 - 서킷 브레이커/재시도 정책 도입
Ingress/ALB 레벨 타임아웃이 겹치면 장애가 더 커 보일 수 있습니다. 트래픽 계층의 타임아웃 진단은 아래 글도 함께 보면 원인 분리가 빨라집니다.
7) 노드/런타임/권한(SecurityContext) 문제
앱이 특정 권한을 요구하거나(바인딩, 파일 쓰기), 런타임 제약이 강할 때 발생합니다.
runAsNonRoot인데 이미지가 root 전제readOnlyRootFilesystem: true인데 앱이/tmp외 경로에 쓰기fsGroup미설정으로 볼륨 접근 불가
진단
kubectl -n <namespace> describe pod <pod-name>
# permission denied, operation not permitted 같은 로그/이벤트 확인
해결 예시
securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
그리고 쓰기가 필요하면 emptyDir 를 /tmp 등에 마운트합니다.
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
재현/분석이 어려울 때: 디버그 컨테이너로 “살아있는 쉘” 확보
CrashLoop는 컨테이너가 너무 빨리 죽어 exec 가 어려울 수 있습니다. 이때는 kubectl debug 로 임시 디버그 컨테이너를 붙여 네트워크/DNS/볼륨을 확인합니다.
kubectl -n <namespace> debug -it pod/<pod-name> --image=busybox:1.36 --target=<container-name>
확인할 것
- DNS:
nslookup <service-name> - 라우팅:
wget -S -O - http://<service-name>:<port>/health - 파일: 설정 마운트 경로 존재 여부
운영에서 바로 쓰는 “10분 내 결론” 체크리스트
kubectl describe pod이벤트에서OOMKilled또는probe failed를 먼저 찾는다kubectl logs --previous로 직전 크래시 원인을 확보한다- 최근 배포 직후면
rollout undo로 즉시 롤백한다 - 프로브가 원인이면
startupProbe도입 또는 임시 완화로 재시작 루프를 끊는다 - 메모리면 limit 상향으로 먼저 살리고, 이후 릭/동시성/GC를 분석한다
- 의존성 실패를 부팅 치명 종료로 처리하지 않도록 readiness 중심으로 재설계한다
마무리: “응급처치”와 “근본해결”을 분리하자
CrashLoopBackOff 대응에서 가장 중요한 건, 원인을 찾기 전에 서비스를 살릴 수 있는 조치(롤백, 리소스 상향, 프로브 완화)로 가용성을 먼저 확보하고, 그 다음 로그/이벤트/exit code 기반으로 원인을 좁혀 재발 방지로 이어가는 것입니다.
특히 프로브와 리소스는 애플리케이션 코드가 멀쩡해도 운영에서 흔히 깨지는 지점이므로, 배포 파이프라인에 describe 이벤트 점검과 프로브/리소스 정책 리뷰를 정례화하면 CrashLoopBackOff의 상당수를 예방할 수 있습니다.