Kubernetes 트러블슈팅 — CrashLoopBackOff부터 Pending까지, 에러별 대응법
Pod이 계속 빨간색으로 표시되는데 로그에는 아무것도 없고, describe를 봐도 뭐가 문제인지 모르겠다면 어디서부터 봐야 할까요?
Kubernetes를 운영하다 보면 다양한 에러를 만나게 됩니다. 중요한 건 에러 메시지를 보고 "이건 이런 원인이구나"를 빠르게 판단하는 것입니다. 자주 발생하는 에러별로 원인과 해결 방법을 체계적으로 정리합니다.
트러블슈팅 기본 흐름
어떤 에러든 다음 순서로 접근하면 대부분 원인을 찾을 수 있습니다.
# 1단계: 상태 확인
kubectl get pods -o wide
# 2단계: 상세 정보와 이벤트 확인
kubectl describe pod <pod-name>
# 3단계: 로그 확인
kubectl logs <pod-name>
kubectl logs <pod-name> --previous # 크래시된 이전 컨테이너 로그
# 4단계: 클러스터 이벤트 확인
kubectl get events --sort-by='.lastTimestamp'
# 5단계: 노드 상태 확인
kubectl describe node <node-name>
CrashLoopBackOff
증상: Pod이 Running → Error → CrashLoopBackOff를 반복
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app 0/1 CrashLoopBackOff 5 3m
원인 및 해결
| 원인 | 확인 방법 | 해결 |
|---|---|---|
| 애플리케이션 에러 | kubectl logs | 코드/설정 수정 |
| 잘못된 command/args | kubectl describe | 실행 명령어 확인 |
| 설정 파일 누락 | kubectl logs | ConfigMap/Secret 확인 |
| DB 연결 실패 | kubectl logs | 네트워크/인증 정보 확인 |
| OOMKilled | kubectl describe → Last State | 메모리 limits 증가 |
| livenessProbe 실패 | kubectl describe → Events | Probe 설정 조정 |
# 상세 디버깅
kubectl describe pod my-app | grep -A 10 "Last State"
# Last State: Terminated
# Reason: Error
# Exit Code: 1
kubectl logs my-app --previous
# Error: Cannot connect to database at mysql:3306
Exit Code 해석
| Exit Code | 의미 | 흔한 원인 |
|---|---|---|
| 0 | 정상 종료 | 잘못된 restartPolicy |
| 1 | 일반 에러 | 애플리케이션 예외 |
| 126 | 실행 권한 없음 | chmod 필요 |
| 127 | 명령어를 찾을 수 없음 | 잘못된 command |
| 137 | SIGKILL (128+9) | OOMKill 또는 외부 kill |
| 143 | SIGTERM (128+15) | 정상 종료 요청 |
ImagePullBackOff
증상: 이미지를 가져올 수 없음
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app 0/1 ImagePullBackOff 0 2m
원인 및 해결
kubectl describe pod my-app | grep -A 5 Events
# Failed to pull image "my-app:latest": rpc error: ...
| 원인 | 해결 |
|---|---|
| 이미지 이름/태그 오타 | 이미지 이름 확인, latest 대신 구체적 태그 |
| 프라이빗 레지스트리 인증 실패 | imagePullSecrets 설정 |
| 이미지가 존재하지 않음 | 레지스트리에서 이미지 확인 |
| 네트워크 문제 | 노드에서 레지스트리 접근 확인 |
# imagePullSecrets 설정
spec:
imagePullSecrets:
- name: registry-secret
# 레지스트리 Secret 생성
kubectl create secret docker-registry registry-secret \
--docker-server=myregistry.io \
--docker-username=user \
--docker-password=pass
OOMKilled
증상: 메모리 초과로 컨테이너가 강제 종료
kubectl describe pod my-app
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
해결
# 메모리 limits 증가
resources:
requests:
memory: 512Mi
limits:
memory: 1Gi # 애플리케이션 실제 사용량에 맞게 조정
# 실제 메모리 사용량 확인
kubectl top pod my-app
# NAME CPU(cores) MEMORY(bytes)
# my-app 100m 480Mi # limits(512Mi)에 근접!
Java 애플리케이션이라면 JVM 힙 설정도 확인하세요.
env:
- name: JAVA_OPTS
value: "-Xmx768m -Xms256m" # limits보다 작아야 함
Pending
증상: Pod이 스케줄링되지 못하고 대기 중
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app 0/1 Pending 0 5m
원인 진단
kubectl describe pod my-app | grep -A 10 Events
| 메시지 | 원인 | 해결 |
|---|---|---|
Insufficient cpu/memory | 노드 리소스 부족 | 노드 추가 또는 requests 줄이기 |
didn't match selector | nodeSelector 불일치 | 노드 레이블 확인 |
had taints | Taint 미허용 | Toleration 추가 |
pod has unbound PVC | PVC가 바인딩되지 않음 | PV/StorageClass 확인 |
didn't find available PV | 적합한 PV 없음 | PV 생성 또는 StorageClass 확인 |
# 노드 리소스 상황 확인
kubectl describe nodes | grep -A 5 "Allocated resources"
CreateContainerConfigError
증상: 컨테이너 설정 오류로 시작 불가
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app 0/1 CreateContainerConfigError 0 1m
주로 ConfigMap이나 Secret이 존재하지 않을 때 발생합니다.
# 원인 확인
kubectl describe pod my-app
# Warning Failed ... Error: configmap "app-config" not found
# 해결
kubectl get configmap app-config
kubectl get secret db-credentials
Evicted
증상: 노드 리소스 부족으로 Pod이 퇴출됨
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app-1 0/1 Evicted 0 30m
# 원인 확인
kubectl describe pod my-app-1
# Status: Failed
# Reason: Evicted
# Message: The node was low on resource: memory
# Evicted Pod 정리
kubectl get pods --field-selector=status.phase=Failed -o name | xargs kubectl delete
노드의 디스크나 메모리가 부족하면 kubelet이 Pod을 퇴출합니다. ResourceQuota와 LimitRange를 설정하여 예방하세요.
네트워크 관련 문제
Service에 접근 불가
# 1. Endpoints 확인
kubectl get endpoints my-service
# ENDPOINTS가 비어있으면 레이블 매칭 실패
# 2. 레이블 확인
kubectl get pods --show-labels
kubectl get svc my-service -o yaml | grep selector
# 3. Pod 내부에서 DNS 확인
kubectl exec my-pod -- nslookup my-service
# 4. Pod 내부에서 연결 테스트
kubectl exec my-pod -- curl -v http://my-service:8080
DNS 문제
# CoreDNS 상태 확인
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns
# DNS 테스트
kubectl run dnstest --image=busybox:1.36 --rm -it -- nslookup kubernetes.default
kubectl debug — 고급 디버깅
Kubernetes 1.25+에서 사용 가능한 강력한 디버깅 도구입니다.
# 실행 중인 Pod에 디버깅 컨테이너 추가
kubectl debug my-pod -it --image=busybox:1.36
# Pod 복사본을 만들어 디버깅 (원본에 영향 없음)
kubectl debug my-pod -it --image=busybox:1.36 --copy-to=debug-pod
# 노드 디버깅 (노드의 파일시스템에 접근)
kubectl debug node/node-1 -it --image=busybox:1.36
디버깅용 도구 이미지
# 네트워크 도구가 포함된 이미지
kubectl run netshoot --rm -it --image=nicolaka/netshoot -- bash
# 안에서 사용 가능한 도구들:
# curl, wget, nslookup, dig, ping, traceroute,
# tcpdump, iptables, ss, ip, netstat 등
자주 사용하는 디버깅 명령어 모음
# Pod 전체 상태 (넓은 출력)
kubectl get pods -o wide --all-namespaces
# 특정 상태의 Pod만 필터
kubectl get pods --field-selector=status.phase!=Running
# 리소스 사용량 상위 Pod
kubectl top pods --sort-by=memory
# 이벤트 (시간순)
kubectl get events --sort-by='.lastTimestamp' -n production
# Pod의 YAML 출력 (현재 상태 포함)
kubectl get pod my-pod -o yaml
# 강제 삭제 (stuck Pod)
kubectl delete pod my-pod --force --grace-period=0
체계적인 디버깅 체크리스트
1. kubectl get pods -o wide → 어떤 상태인가?
2. kubectl describe pod <name> → 이벤트에 원인이 있는가?
3. kubectl logs <name> [--previous] → 애플리케이션 에러가 있는가?
4. kubectl get events → 클러스터 수준 문제인가?
5. kubectl describe node <name> → 노드에 문제가 있는가?
6. kubectl debug <name> → 컨테이너 내부를 조사해야 하는가?
정리
Kubernetes 트러블슈팅의 핵심은 describe → logs → events 순서로 원인을 좁혀가는 것입니다. CrashLoopBackOff는 애플리케이션 에러나 OOMKill, Pending은 스케줄링 실패, ImagePullBackOff는 이미지 접근 문제가 대부분입니다. Exit Code를 해석하고, kubectl debug로 컨테이너 내부를 조사하는 방법까지 알아두면 대부분의 문제를 체계적으로 해결할 수 있습니다.