Kubernetes CrashLoopBackOff 원인 7가지 — 5분 종료코드 진단법
K8s_Troubleshooting_Guide 13편
kubectl get pods를 쳤는데 STATUS에 CrashLoopBackOff가 떠 있고 RESTARTS 숫자가 1초마다 올라가고 있나요? 지금 장애 한복판에서 이 글을 검색해서 들어왔다면, 일단 한 가지만 기억하세요. CrashLoopBackOff는 그 자체로 에러 메시지가 아닙니다.
"back-off restarting failed container"가 뜬다고 당황하지 말자
CrashLoopBackOff는 정확히 말하면 이런 상태입니다.
컨테이너가 시작 → 종료 → 시작 → 종료를 반복하니까, kubelet이 재시작 간격을 10초 → 20초 → 40초 → ... 최대 5분까지 점점 늘리며(back-off) 기다리는 상태.
즉 "컨테이너가 자꾸 죽는다"는 결과일 뿐, 왜 죽는지는 따로 캐내야 합니다. 그래서 kubectl describe에 찍히는 Back-off restarting failed container라는 메시지만 봐서는 절대 원인을 알 수 없습니다. 진짜 단서는 종료코드(Exit Code) 와 죽기 직전 로그에 있습니다. 지금부터 위에서부터 순서대로 따라오면 5분 안에 원인이 좁혀집니다.
1단계: 종료코드부터 읽는다 (describe + logs --previous)
가장 먼저 할 일은 단 두 줄입니다.
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous # 죽은 직전 컨테이너의 로그--previous(또는 -p)가 핵심입니다. 컨테이너가 이미 재시작됐기 때문에 그냥 logs를 치면 새로 뜬(아직 안 죽은) 컨테이너를 보거나 비어 있습니다. 죽기 직전 컨테이너의 마지막 비명을 들으려면 반드시 --previous를 붙여야 합니다.
describe 출력에서 봐야 할 곳은 Last State 블록입니다.
Last State: Terminated
Reason: Error
Exit Code: 1 # ← 여기!
Started: Tue, 17 Jun 2026 09:12:01 +0900
Finished: Tue, 17 Jun 2026 09:12:01 +0900 # 시작=종료 → 즉시 죽음
Restart Count: 6Reason과 Exit Code, 그리고 Started/Finished 간격을 보세요. 시작과 종료 시각이 거의 같으면 "부팅도 못 하고 즉시 죽는" 패턴입니다.
종료코드 매핑 표
| Exit Code | Reason | 가리키는 원인 | 분기 |
|---|---|---|---|
0 | Completed | 정상 종료했는데 재시작됨 (메인 프로세스가 안 떠있거나 백그라운드로 빠짐) | 원인 ① |
1 (또는 2) | Error | 앱 내부 예외/설정 오류로 죽음 | 원인 ②③④⑦ |
137 | OOMKilled / Error | SIGKILL — 메모리 limit 초과 또는 강제 종료 | 원인 ⑤⑥ |
139 | Error | Segfault(SIGSEGV) — 네이티브 크래시·아키텍처 불일치 | 원인 ⑦ |
종료코드가 신호등입니다. 0이면 "안 죽었는데 끝났다", 1이면 "앱/설정이 틀렸다", 137이면 "K8s가 죽였다" 로 머릿속에서 갈래를 나누세요.
2단계: 7대 원인별 진단·복구 (복붙용)
① 메인 프로세스가 즉시 종료 (Exit 0인데 재시작)
- 이렇게 보이면: Exit Code
0, ReasonCompleted, 로그도 깨끗함. - 이 명령:
kubectl logs <pod> -p— 정상 로그 후 바로 종료됐는지 확인. - 이렇게 고친다: 컨테이너의 메인 프로세스(PID 1)가 foreground로 떠 있어야 합니다.
nginx -g 'daemon off;',node server.js처럼 포그라운드로 실행되게 CMD를 바꾸세요. 데몬을 백그라운드로 띄우면 PID 1이 즉시 끝나며 컨테이너가 종료됩니다.
② 잘못된 command/args (엔트리포인트 오류)
- 이렇게 보이면:
exec: "xxx": executable file not found또는no such file or directory. - 이 명령:
Bash
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].command}' kubectl logs <pod> -p - 이렇게 고친다: 매니페스트의
command/args오타, 절대경로 누락을 확인합니다. 이미지에 셸이 없는 distroless라면["/bin/sh","-c", ...]가 안 먹힙니다.
③ 환경변수·ConfigMap/Secret 누락
- 이렇게 보이면: 로그에
KeyError,nil pointer,panic: required env DATABASE_URL not set. - 이 명령:
Bash
kubectl get configmap,secret -n <ns> kubectl describe pod <pod> | grep -A20 Environment - 이렇게 고친다: 참조하는 ConfigMap/Secret 이름과 key가 실제로 존재하는지 확인합니다. GitOps(ArgoCD)로 배포할 때 가장 흔한 함정이 바로 이겁니다 — Deployment는 새 ConfigMap을 바라보는데 ConfigMap이 동기화 순서상 아직 반영 안 됐거나, 값만 바꾸고 Pod를 롤링하지 않아 옛 값이 그대로 남는 경우죠.
kubectl rollout restart deploy/<name>으로 강제 반영하세요.
④ 의존 서비스(DB·Redis) 미준비로 부팅 실패
- 이렇게 보이면:
connection refused,could not connect to db:5432. - 이 명령:
kubectl get svc,endpoints -n <ns>— 대상 Service의 ENDPOINTS가 비어 있는지 확인. - 이렇게 고친다: 앱이 부팅 시점에 DB가 없으면 죽도록 짜였다면, initContainer로 의존성을 대기시키는 게 2026년 권장 패턴입니다.
근본적으로는 앱에 재시도 로직을 넣고YAML
initContainers: - name: wait-for-db image: busybox:1.36 command: ['sh','-c','until nc -z db 5432; do echo waiting; sleep 2; done']restartPolicy: Always로 K8s 재시작에 맡기는 것도 방법입니다.
⑤ 메모리 limit 부족 → OOMKilled (Exit 137)
- 이렇게 보이면: Reason
OOMKilled, Exit Code137. - 이 글의 주역은 아닙니다. 부팅 직후가 아니라 한참 돌다가 137로 죽는 패턴이라면 메모리 문제이니, 👉 OOMKilled 진단 편(9편) 을 참고하세요. 여기서는 "137 = K8s가 죽였다" 분기만 기억하면 됩니다.
⑥ liveness probe 실패로 강제 재시작
- 이렇게 보이면: events에
Liveness probe failed,Killing container. - 앱은 멀쩡한데 헬스체크 설정 때문에 K8s가 계속 죽이는 경우입니다. 👉 Probe 실패 진단 편 을 참고하세요. 여기서도 분기만 잡고 넘어갑니다.
⑦ 이미지/엔트리포인트 오류·아키텍처 불일치
- 이렇게 보이면:
exec format error, Exit Code139(segfault). - 이 명령:
kubectl describe pod <pod> | grep Image로 태그 확인. - 이렇게 고친다:
exec format error는 십중팔구 아키텍처 불일치입니다. Apple Silicon(arm64)에서 빌드한 이미지를 amd64 노드에 올렸을 때 단골로 터집니다.docker buildx build --platform linux/amd64로 멀티아치 빌드하거나 노드 아키텍처에 맞춰 다시 빌드하세요. GitOps 환경에서는latest태그 캐시로 옛 이미지가 도는 이미지 태그 불일치도 의심하세요.
3단계: 5분 진단 플로우차트 + 디버깅 팁
flowchart TD
A[CrashLoopBackOff] --> B[kubectl describe + logs -p]
B --> C{Exit Code?}
C -->|0| D[메인 프로세스 foreground화 ①]
C -->|1/2| E{로그 메시지?}
E -->|env/config 키 없음| F[ConfigMap·Secret 확인 ③]
E -->|connection refused| G[의존서비스·initContainer ④]
E -->|not found/exec오류| H[command·args 점검 ②⑦]
C -->|137| I[OOMKilled 편 / Probe 편 ⑤⑥]
C -->|139| J[아키텍처·이미지 재빌드 ⑦]로그가 안 남을 때: sleep으로 덮어쓰고 직접 들어가기
가장 답답한 케이스가 로그조차 안 남고 즉사하는 경우입니다. 이럴 땐 컨테이너가 죽지 않게 만들어 놓고 안에서 직접 명령을 돌려봅니다.
# 임시로 entrypoint를 덮어써서 컨테이너를 살려둔다
spec:
containers:
- name: app
image: myapp:1.2.3
command: ["sleep", "3600"]kubectl exec -it <pod> -- sh
# 컨테이너 안에서 원래 실행 명령을 직접 돌려본다
/app/start.sh # 진짜 에러 메시지를 눈으로 확인
env | grep DATABASE # 환경변수 주입 여부 확인
nc -z db 5432 # 의존 서비스 연결 테스트원래 죽던 명령을 셸에서 직접 실행하면, K8s가 삼켜버린 진짜 에러 로그가 그대로 화면에 찍힙니다. 이 한 방으로 풀리는 경우가 정말 많습니다.
events 타임라인으로 전체 그림 보기
kubectl get events --sort-by=.lastTimestamp -n <ns>LAST TYPE REASON OBJECT MESSAGE
30s Warning BackOff pod/app-xxx Back-off restarting failed container
45s Normal Pulled pod/app-xxx Successfully pulled image "myapp:1.2.3"
50s Warning Failed pod/app-xxx Error: secret "db-cred" not foundevents를 시간순으로 보면 "이미지는 잘 받았는데 secret이 없어서 터졌다"처럼 인과관계가 한눈에 보입니다.
실무 한마디 — 제 경험상 1~3년차 엔지니어가 CrashLoopBackOff로 가장 많이 헤매는 지점은
--previous를 안 붙이고 "로그가 비어 있다"며 막히는 것, 그리고 ArgoCD로 배포하면서 ConfigMap만 바꾸고 Pod를 롤링 안 해서 옛 설정으로 도는 케이스입니다. 이 두 가지만 의식해도 디버깅 시간이 절반으로 줄어듭니다.
결론: 3분류로 기억하자
복잡해 보여도 CrashLoopBackOff의 원인은 결국 세 갈래입니다.
- 앱이 죽었나? → Exit 0/1, 로그에 예외 (원인 ①②⑦)
- 설정이 틀렸나? → env·ConfigMap·Secret·의존서비스 (원인 ③④)
- K8s가 죽였나? → 137 OOM, Probe 실패 (원인 ⑤⑥)
describe로 종료코드 읽고 → 표에서 갈래 잡고 → 7대 원인 블록을 복붙. 이 순서면 5분 안에 범위가 좁혀집니다.
다음 14편에서는 CrashLoopBackOff와 자주 엮이는 ImagePullBackOff와 프라이빗 레지스트리 인증 오류를 다룹니다.
자주 묻는 질문 (FAQ)
Q. kubectl logs를 쳤는데 아무것도 안 나옵니다.
A. 컨테이너가 이미 재시작돼서 새 컨테이너 로그를 보고 있을 가능성이 큽니다. kubectl logs <pod> --previous로 죽은 직전 컨테이너의 로그를 확인하세요. 그래도 비어 있으면 부팅 전에 즉사한 것이니, entrypoint를 sleep 3600으로 덮고 kubectl exec로 직접 들어가 실행해 보세요.
Q. Exit Code가 0인데 왜 CrashLoopBackOff인가요?
A. 정상 종료(0)했더라도 restartPolicy: Always라면 K8s는 다시 띄웁니다. 메인 프로세스가 foreground로 계속 떠 있어야 하는데 백그라운드 데몬으로 빠지거나 작업 후 바로 끝나버린 경우입니다. 프로세스를 포그라운드로 유지하세요.
Q. CrashLoopBackOff와 ImagePullBackOff는 다른가요? A. 네. ImagePullBackOff는 이미지를 못 받아서 컨테이너가 아예 시작도 못 한 상태이고, CrashLoopBackOff는 시작은 했는데 반복해서 죽는 상태입니다. 이 글은 후자를 다룹니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...