- Published on
K8s CrashLoopBackOff 원인 10분 추적법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
CrashLoopBackOff는 증상이 아니라 상태입니다. 쿠버네티스가 컨테이너를 재시작하려고 시도했는데, 짧은 시간 안에 계속 종료되어 백오프(backoff)로 재시작 간격을 늘리는 상황을 말합니다. 중요한 포인트는 두 가지입니다.
- 무엇이 컨테이너를 종료시켰는가(Exit Code, Signal, OOMKill)
- 왜 쿠버네티스가 “살아있지 않다”고 판단했는가(프로브, 준비 상태, 의존성)
이 글은 운영 중인 클러스터에서 10분 안에 원인을 좁히는 “추적 루틴”을 제공합니다. 원인 목록을 더 넓게 보고 싶다면 함께 읽을 만한 글로 K8s CrashLoopBackOff 원인 10가지·즉시 진단법도 참고하세요.
0분~1분: 대상 Pod와 컨테이너를 확정하기
먼저 “어떤 Pod의 어떤 컨테이너”가 죽는지 확정해야 합니다. 사이드카가 죽는 경우도 흔합니다.
# 네임스페이스 포함해서 빠르게 확인
kubectl get pod -n my-ns -o wide | grep -i crash
# 특정 파드 자세히
kubectl describe pod -n my-ns my-pod
describe에서 다음을 눈으로 먼저 잡습니다.
Containers:아래State: Waiting과Reason: CrashLoopBackOffLast State: Terminated의Exit Code,Reason,FinishedEvents:에 반복되는 경고(프로브 실패, 이미지 풀 실패, OOM 등)
여기서 이미 절반은 끝납니다. Exit Code가 137이면 OOMKill 가능성이 크고, 1이면 앱이 자체적으로 실패 종료했을 확률이 큽니다.
1분~3분: “직전 로그”부터 본다 (현재 로그 말고)
CrashLoop 상황에서 가장 흔한 실수는 “현재 실행 중인 컨테이너 로그”만 보는 것입니다. 재시작 직후라 로그가 비어 있을 수 있습니다. 반드시 --previous를 먼저 봅니다.
# 직전 크래시 로그
kubectl logs -n my-ns my-pod -c my-container --previous
# 현재 실행 중 로그(살아있을 때)
kubectl logs -n my-ns my-pod -c my-container -f
로그에서 찾는 키워드 패턴은 다음과 같습니다.
- 설정/환경:
missing env,config not found,permission denied,no such file - 네트워크/의존성:
connection refused,timeout,dns,x509,certificate - 마이그레이션/스키마:
migration failed,relation does not exist - 런타임:
panic,segmentation fault,SIGILL,SIGSEGV
로그가 전혀 없다면 “프로세스가 시작도 못 했거나, stdout으로 아무것도 못 찍고 죽었거나”입니다. 이때는 describe의 Last State와 이벤트, 그리고 command/args를 확인하는 쪽으로 이동합니다.
3분~5분: 종료 원인(Exit Code, Signal, OOMKill)로 분기
여기부터는 “분기”가 핵심입니다. kubectl describe pod의 Last State를 기준으로 아래처럼 갈라집니다.
케이스 A: Reason: OOMKilled 또는 Exit Code: 137
대부분 메모리 상한(resources.limits.memory)에 부딪혀 커널 OOM Killer가 프로세스를 죽인 것입니다.
# 리소스 설정 확인
kubectl get pod -n my-ns my-pod -o jsonpath='{.spec.containers[*].resources}'
# 메트릭 서버가 있다면 현재 사용량 확인
kubectl top pod -n my-ns my-pod
kubectl top pod -n my-ns --containers | grep my-pod
즉시 취할 액션:
limits.memory를 올리거나, 메모리 누수/캐시 설정을 점검- JVM/Node/Go 런타임이면 힙 상한을 명시적으로 조정
- 대용량 초기 로딩(예: 모델, 사전, 캐시)을 지연 로딩으로 변경
주의: 메모리 requests만 높이고 limits가 낮으면, 스케줄은 되지만 실행 중 죽습니다. 반대로 limits만 높고 requests가 너무 낮으면 노드 압박으로 다른 프로세스와 경쟁하다가 불안정해질 수 있습니다.
케이스 B: Exit Code: 1 (일반 오류 종료)
앱이 “정상적으로 시작하지 못했다”고 선언하고 종료한 케이스입니다. 로그가 가장 중요합니다.
바로 확인할 것:
- 잘못된 환경변수/시크릿
- 마이그레이션 실패
- 외부 의존성(DB, Kafka, Redis) 연결 실패
# 환경변수/시크릿 참조 확인(값 자체를 찍지 말고 참조가 유효한지)
kubectl get deploy -n my-ns my-deploy -o yaml | sed -n '1,200p'
# 시크릿/컨피그맵 존재 여부
kubectl get secret -n my-ns | grep my-secret
kubectl get configmap -n my-ns | grep my-config
시크릿 키 이름이 바뀌었거나, envFrom의 ConfigMap이 누락되면 “시작 즉시 종료”로 이어집니다.
케이스 C: Exit Code: 139 또는 시그널 관련(세그폴트)
네이티브 라이브러리, 아키텍처 불일치, 런타임 버그, 이미지/빌드 문제 가능성이 큽니다.
# 이미지와 실행 커맨드 확인
kubectl get pod -n my-ns my-pod -o jsonpath='{.spec.containers[0].image}{"\n"}{.spec.containers[0].command}{"\n"}{.spec.containers[0].args}{"\n"}'
특히 amd64 노드에 arm64 이미지를 올리거나(반대도 포함), distroless 이미지에서 필요한 라이브러리가 빠진 경우가 많습니다.
5분~7분: 프로브(liveness/readiness/startup)로 인한 “강제 재시작” 확인
애플리케이션이 사실은 살아 있는데, 프로브 설정이 공격적이어서 쿠버네티스가 죽인 뒤 재시작시키는 경우가 있습니다. Events에 아래가 반복되면 거의 확정입니다.
Liveness probe failedReadiness probe failedBack-off restarting failed container
# 프로브 설정만 빠르게 보기
kubectl get pod -n my-ns my-pod -o jsonpath='{.spec.containers[0].livenessProbe}{"\n"}{.spec.containers[0].readinessProbe}{"\n"}{.spec.containers[0].startupProbe}{"\n"}'
자주 발생하는 함정:
- 초기 부팅이 느린데
startupProbe없이livenessProbe만 둔 경우 initialDelaySeconds가 너무 짧고,timeoutSeconds가 너무 짧은 경우- 앱은
0.0.0.0이 아니라127.0.0.1에만 바인딩하는데, 프로브는 Pod IP로 접근하는 경우 - HTTP 프로브 경로가 리다이렉트/인증으로 바뀐 경우(예:
/health가 로그인 필요)
프로브 튜닝의 기본 방향:
- “부팅 완료 전”은
startupProbe로 보호 - “일시적 지연”은
failureThreshold와periodSeconds로 흡수 - “정말 죽었을 때만”
livenessProbe가 재시작을 유도
7분~9분: 스케줄/볼륨/권한 문제로 시작 자체가 꼬이는지 확인
CrashLoop로 보이지만 실제로는 컨테이너가 정상 실행을 못 하고 준비 과정에서 실패하는 경우가 있습니다.
볼륨 마운트/권한
describe의 이벤트에 다음이 있으면 볼륨/권한 쪽입니다.
MountVolume.SetUp failedpermission denied
kubectl describe pod -n my-ns my-pod | sed -n '/Events:/,$p'
조치 포인트:
securityContext.runAsUser,fsGroup미설정으로 파일 접근 실패readOnlyRootFilesystem: true인데 앱이/tmp나 작업 디렉터리에 쓰려는 경우- PVC 바인딩/스토리지 클래스 문제
이미지 풀/레지스트리 인증
이건 보통 ImagePullBackOff지만, 재시작 과정에서 섞여 보일 때도 있습니다.
Failed to pull imageErrImagePull
레지스트리 토큰 만료, imagePullSecrets 누락을 확인합니다.
9분~10분: 재현 가능한 “단발 실행”으로 원인 고정하기
원인을 거의 좁혔다면, 동일 이미지로 “프로브 없이” 단발 실행을 만들어 로그/행동을 고정시키는 게 빠릅니다. 운영 리소스를 직접 건드리기 어렵다면 임시 Pod로 재현합니다.
# 같은 이미지로 임시 디버그 파드 실행(명령은 상황에 맞게)
kubectl run -n my-ns crash-debug \
--image=my-registry/my-image:tag \
--restart=Never \
--command -- sh -c 'env; ./app --version; ./app'
# 로그 확인
kubectl logs -n my-ns crash-debug
# 필요 시 파드 내부 진입(이미지에 sh가 있어야 함)
kubectl exec -n my-ns -it crash-debug -- sh
distroless처럼 셸이 없는 이미지라면, 별도 디버그 이미지(예: busybox, alpine)를 같은 네임스페이스에서 띄우고 네트워크/ DNS/ TLS만 확인하는 방식이 현실적입니다.
실전 체크리스트: 10분 루틴을 한 장으로 요약
아래 순서대로만 하면 “감”이 아니라 “증거”로 좁힙니다.
kubectl describe pod로Last State,Exit Code,Events확보kubectl logs --previous로 직전 크래시 로그 확보Exit Code 137이면 메모리/OOM 분기,Exit Code 1이면 설정/의존성 분기Events에 프로브 실패가 반복되면 프로브 튜닝/경로 확인- 볼륨/권한/시크릿 누락은
Events와spec에서 바로 드러남 - 임시 Pod로 단발 실행해 재현하고 원인을 고정
자주 나오는 원인별 “증거”와 즉시 처방
DB 의존성(연결 실패)로 즉시 종료
- 증거: 로그에
connection refused,timeout,password authentication failed - 처방: 네트워크 정책/서비스 DNS/시크릿/커넥션 스트링 확인, 앱 시작 시 재시도(backoff) 추가
마이그레이션을 앱 시작에 묶어둔 경우
- 증거:
migration failed후 종료 - 처방: 마이그레이션을
Job으로 분리하거나, 실패 시 롤백/재시도 전략 수립
DB 락/교착이 의심되면 애플리케이션 크래시로만 보지 말고 데이터베이스 관점에서 확인이 필요합니다. 이 경우 PostgreSQL 데드락(40P01) 원인·해결 9단계가 원인 좁히기에 도움이 됩니다.
설정 스크립트에서 set -e로 조기 종료
엔트리포인트 셸 스크립트가 작은 실패에도 즉시 종료하면서 CrashLoop가 나는 경우가 많습니다.
- 증거: 로그가 짧고, 특정 커맨드 실패 직후 종료
- 처방: 안전한 예외 처리, 명시적 에러 메시지, 필수/선택 단계 분리
관련해서 셸 스크립트의 실패 처리 패턴은 bash set -euo pipefail 함정과 안전한 예외처리를 참고하면 시행착오를 줄일 수 있습니다.
결론: CrashLoopBackOff는 “원인 수렴 속도”가 전부다
CrashLoopBackOff를 해결하는 가장 빠른 방법은 복잡한 추측이 아니라, describe의 Last State와 Events, 그리고 --previous 로그로 증거를 모아 분기하는 것입니다. 10분 루틴으로도 대부분의 케이스는 OOM, 프로브, 설정/시크릿, 의존성 연결, 권한/볼륨 중 하나로 수렴합니다.
다음 장애에서 시간을 더 줄이려면, 평소에 아래를 준비해 두면 좋습니다.
- 애플리케이션 시작 로그에 “환경/의존성 체크 결과”를 명확히 남기기
- 프로브를 서비스 특성에 맞게 튜닝하고, 초기 부팅은
startupProbe로 보호 - 리소스
requests/limits를 근거 있는 값으로 관리하고 OOM 시그널을 관측 가능하게 만들기
이 정도만 갖춰도 CrashLoopBackOff는 ‘공포의 상태’가 아니라 ‘빠르게 추적 가능한 이벤트’가 됩니다.