CrashLoopBackOff를 만났을 때
이 책 전체에서 가장 많이 쓰게 될 장은 아마 이 장일 겁니다. 앞의 열 개 장에서 배운 개념들이 실제로 어떻게 어긋나는지, 그리고 그 어긋남을 어떤 순서로 좁혀가는지를 다룹니다. 쿠버네티스 에러 메시지는 처음 보면 암호문 같지만, 사실 종류가 그렇게 많지 않습니다.
디버깅은 항상 같은 순서로 시작한다
Pod에 문제가 생겼을 때 되는대로 이것저것 찔러보기보다, 아래 순서를 기계적으로 따라가는 게 훨씬 빠릅니다.
flowchart TD
A["kubectl get pods"] --> B{"Pod 상태가?"}
B -->|Pending| C["kubectl describe pod → Events 확인"]
B -->|ImagePullBackOff| D["이미지 이름·태그·레지스트리 인증 확인"]
B -->|CrashLoopBackOff| E["kubectl logs --previous"]
B -->|Running인데 응답 없음| F["kubectl exec로 컨테이너 내부 진입"]
C --> G["자원 부족 / 스케줄링 실패 원인 확인"]
E --> H{"직전 종료 코드가?"}
H -->|0이 아님| I["애플리케이션 자체의 에러"]
H -->|137| J["OOMKilled — 10장의 리소스 limits 확인"]Pending — 애초에 노드에 배정조차 안 됐다
Pod이 계속 Pending이라면 스케줄러가 이 Pod을 어느 노드에도 두지 못하고 있다는 뜻입니다. 원인은 거의 항상 kubectl describe pod의 Events 섹션 맨 아래에 적혀 있습니다.
kubectl describe pod my-api-xxx
# Events:
# Warning FailedScheduling 0/3 nodes are available:
# 3 Insufficient memory.이 경우는 10장에서 설정한 resources.requests를 감당할 수 있는 노드가 없다는 뜻입니다. 8장에서 본 Multi-Attach error도 같은 Pending 상태로 나타나지만 원인은 전혀 다르니, 반드시 Events 메시지 원문을 읽어야 합니다. 상태 이름만 보고 원인을 짐작하는 게 가장 흔한 시간 낭비입니다.
ImagePullBackOff — 이미지를 못 받아왔다
kubectl describe pod my-api-xxx
# Events:
# Warning Failed Failed to pull image "my-api:1.0.0":
# rpc error: code = NotFound원인은 거의 셋 중 하나입니다. 이미지 이름이나 태그를 오타냈거나, private 레지스트리인데 클러스터에 인증 정보(imagePullSecrets)를 안 넣었거나, 로컬에서만 빌드하고 레지스트리에 push를 안 했거나. 특히 마지막 경우는 로컬 Docker에는 이미지가 멀쩡히 있으니 "내 컴퓨터에선 되는데요"라는 흔한 착각으로 이어집니다. kind 클러스터는 로컬 Docker와 별개의 이미지 저장소를 쓰기 때문에, kind load docker-image my-api:1.0.0 같은 명령으로 직접 이미지를 넣어주지 않는 한 로컬에서 빌드한 이미지를 그냥 볼 수 없습니다.
CrashLoopBackOff — 뜨긴 뜨는데 계속 죽는다
가장 흔하면서 가장 정보가 많이 남는 상태입니다. 여기서 초보자가 자주 하는 실수는 kubectl logs만 보고 로그가 비어 있으면 포기하는 것입니다. 컨테이너가 재시작된 직후라면 kubectl logs는 방금 새로 시작한 컨테이너의 로그를 보여주는데, 이건 아직 아무 일도 안 했으니 당연히 비어 있습니다. 원인이 남아 있는 건 죽기 직전 로그, 즉 --previous 플래그입니다.
kubectl logs my-api-xxx --previous종료 코드도 단서가 됩니다.
kubectl describe pod my-api-xxx
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137137은 SIGKILL로 강제 종료됐다는 뜻이고, Reason: OOMKilled가 붙어 있다면 10장에서 다룬 메모리 limits를 초과했다는 뜻입니다. 반면 종료 코드가 1이나 2처럼 애플리케이션이 스스로 선택한 코드라면, 그건 쿠버네티스 문제가 아니라 애플리케이션 코드나 설정(예: 필수 환경 변수 누락) 문제일 가능성이 큽니다. 이 지점부터는 7장의 ConfigMap·Secret이 제대로 주입됐는지를 확인하는 게 다음 순서입니다.
kubectl exec -it my-api-xxx -- env | grep DB_그 밖에 알아두면 아끼는 시간
# 네임스페이스 전체에서 최근 일어난 이벤트를 시간순으로
kubectl get events --sort-by='.lastTimestamp'
# 리소스를 얼마나 쓰고 있는지 (metrics-server 필요)
kubectl top podskubectl get events는 특정 Pod 하나가 아니라 네임스페이스 전체에서 최근 무슨 일이 있었는지 시간순으로 보여줘서, "이게 언제부터 이상해지기 시작했는지" 감을 잡을 때 유용합니다. 다만 기본 설정에서 이벤트는 1시간 정도만 보관되기 때문에, 어제 있었던 장애를 오늘 이 명령으로 되짚어보려 하면 이미 사라지고 없습니다. 장기적인 장애 분석이 필요하다면 로그와 이벤트를 클러스터 밖(Elasticsearch, Loki 등)으로 계속 내보내는 구성이 따로 필요한데, 그건 이 책의 범위를 넘어섭니다.
여기까지가 이 책에서 다루는 쿠버네티스의 뼈대입니다. Helm으로 매니페스트를 패키징하는 법, 오토스케일링, 커스텀 리소스처럼 더 나아간 주제들이 남아 있지만, 그건 지금까지 다룬 Pod·Deployment·Service·Ingress를 손에 익힌 다음이라야 제대로 이해가 됩니다. 급하게 다음 걸 찾기보다, 여기서 배운 명령들로 여러분의 실제 애플리케이션 하나를 직접 클러스터에 올려보는 걸 권합니다.