← 스터디 홈
10편 · 약 13분

운영 트러블슈팅 패턴

장애는 패턴이 있다

쿠버네티스 운영 중 마주치는 대부분의 문제는 몇 가지 패턴으로 수렴한다. 패턴을 알면 수십 분 걸릴 진단을 몇 분 안에 끝낼 수 있다. 이 글에서는 현장에서 가장 자주 보이는 Pod 실패 상태를 체계적으로 진단하고 해결하는 방법을 다룬다.

진단의 첫 번째 도구 — describe와 events

kubectl get pod <pod-name> -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl get events --sort-by='.lastTimestamp' -n <namespace>

describe 출력의 Events: 섹션이 핵심이다. Scheduler가 왜 Pod를 배치하지 못했는지, kubelet이 어떤 이유로 컨테이너를 종료했는지가 여기에 기록된다. Events는 기본 1시간 후 etcd에서 사라지므로 장애 직후 빠르게 확인해야 한다.

Pod 상태별 트러블슈팅

Pending — 스케줄러가 노드를 찾지 못했다

kubectl describe pod <pod-name> | grep -A 10 "Events:"

describe 이벤트에 0/3 nodes are available 같은 메시지가 뜬다.

원인확인 명령해결 방향
클러스터 리소스 부족kubectl top nodes노드 추가 또는 리소스 request 낮추기
PVC Pendingkubectl get pvcStorageClass 확인, PV 생성 여부 확인
nodeSelector/affinity 불일치kubectl describe pod Events레이블 확인, affinity 규칙 재검토
Taint/Toleration 미설정kubectl describe node TaintsToleration 추가

nodeSelector vs affinity: nodeSelector는 단순한 레이블 매칭이고, affinity는 preferredDuringScheduling(소프트 규칙)과 requiredDuringScheduling(하드 규칙)을 구분한다. requiredDuringScheduling은 조건을 만족하는 노드가 없으면 Pod를 영구 Pending 상태로 만든다.

CrashLoopBackOff — 컨테이너가 계속 죽는다

쿠버네티스가 컨테이너를 재시작하되 지수 백오프(10s → 20s → 40s → ... → 최대 5분)를 적용하는 상태다.

# 현재 로그
kubectl logs <pod-name> -n <namespace>
# 이전(죽기 전) 인스턴스의 로그 — 핵심
kubectl logs <pod-name> -n <namespace> --previous
# 재시작 횟수 확인
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].restartCount}'

--previous 플래그 없이는 방금 시작된 컨테이너의 로그만 보이므로 실제 크래시 원인을 놓치기 쉽다.

흔한 원인:

  • 앱 코드 예외 또는 설정 파일 오류
  • 없는 ConfigMap/Secret을 마운트 시도
  • 커맨드/인수 오류 (명령어가 즉시 exit 0 반환)
  • Liveness Probe가 너무 엄격해 초기화 중 Pod를 재시작

OOMKilled — 메모리 제한을 초과했다

컨테이너가 limits.memory를 초과하면 커널이 SIGKILL(종료 코드 137)을 보낸다.

kubectl describe pod <pod-name> | grep -A 5 "Last State"
# 출력: Reason: OOMKilled, Exit Code: 137
대응언제
limits.memory 값 상향실제로 더 많은 메모리가 필요한 경우
VPA Off 모드로 권장값 수집적정값을 모르는 경우
메모리 누수 조사request/limit을 올려도 계속 증가하는 경우

container_memory_working_set_bytes 메트릭이 limits 값에 근접하는 트렌드를 Grafana에서 미리 확인하면 OOMKill 전에 예방할 수 있다.

ImagePullBackOff / ErrImagePull — 이미지를 내려받지 못했다

kubectl describe pod <pod-name> | grep -A 5 "Events:"
# Failed to pull image "...", repository does not exist or may require 'docker login'
원인해결
이미지 이름 / 태그 오타image: 필드 재확인
프라이빗 레지스트리 인증 없음imagePullSecrets 추가
레지스트리 네트워크 단절노드에서 curl <registry> 테스트
latest 태그 캐시 불일치구체적인 다이제스트 또는 버전 태그 사용
# imagePullSecrets 예시
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - image: private.registry.io/myapp:1.2.3

Terminating — Pod가 삭제되지 않는다

kubectl delete pod <pod-name> --grace-period=0 --force

Pod가 Terminating 상태에서 멈추는 경우는 주로 두 가지다. Finalizer가 설정돼 있어 컨트롤러가 응답하지 않거나, 컨테이너가 SIGTERM을 무시하고 grace period(기본 30초)가 만료되기를 기다리는 경우다. --force --grace-period=0은 etcd 레코드를 강제 삭제하므로 신중하게 사용한다.

심층 진단 — exec와 debug

컨테이너가 실행 중이라면 직접 들어갈 수 있다.

# 실행 중인 컨테이너 내부 쉘
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh

# 멀티컨테이너 Pod의 특정 컨테이너
kubectl exec -it <pod-name> -c <container-name> -- /bin/bash

컨테이너에 쉘이 없거나 시작 즉시 크래시되는 경우엔 kubectl debug를 사용한다.

# 동일 Pod의 복사본에 디버그 컨테이너 추가 (원본 유지)
kubectl debug -it <pod-name> --image=busybox --copy-to=debug-pod -- sh

# 노드에 직접 접근 (노드 파일시스템, 프로세스 확인)
kubectl debug node/<node-name> -it --image=ubuntu

서비스 연결 트러블슈팅

Pod 자체는 정상인데 요청이 도달하지 않는다면 네트워킹 레이어를 확인한다.

# Service의 Endpoints가 채워져 있는지 확인 (비어 있으면 selector 불일치)
kubectl get endpoints <service-name>

# DNS 해석 테스트 (임시 Pod 활용)
kubectl run tmp --image=busybox --rm -it -- nslookup <service-name>.<namespace>.svc.cluster.local

# Service 포트 → Pod 포트 포워딩으로 앱 직접 확인
kubectl port-forward pod/<pod-name> 8080:8080

가장 흔한 원인은 Service의 selector와 Pod의 labels가 일치하지 않는 것이다. kubectl get endpoints가 비어 있다면 레이블을 먼저 확인한다.

트러블슈팅 결정 트리

kubectl get pod Pod STATUS는? Pending describe 이벤트 확인 노드 리소스 / taint affinity / PVC 확인 CrashLoopBackOff logs --previous ConfigMap/Secret 존재 여부 Liveness Probe 점검 OOMKilled (137) limits.memory 상향 메모리 누수 조사 VPA 권장값 수집 ImagePullBackOff 이미지명·태그 확인 imagePullSecrets 추가 레지스트리 접근 확인 Terminating Finalizer 확인 --grace-period=0 --force (신중하게) 원인 파악 후 수정 → 재배포 kubectl rollout status / get pod Pod 정상인데 연결 안 됨 → kubectl get endpoints → labels/selector 불일치 확인
Pod 장애 진단 결정 트리

노드 레벨 트러블슈팅

Pod가 아닌 노드 자체 문제일 수 있다.

# 노드 상태 확인
kubectl get nodes
kubectl describe node <node-name>

# NotReady 노드의 원인 (kubelet, 네트워크 플러그인, 디스크 압박 등)
kubectl get node <node-name> -o jsonpath='{.status.conditions}' | jq .

# 노드의 이벤트
kubectl get events --field-selector involvedObject.kind=Node

노드가 NotReady가 되면 해당 노드의 Pod는 Unknown 상태로 전환되고, 기본 5분 후 다른 노드로 재스케줄링된다. 디스크 압박(DiskPressure), 메모리 압박(MemoryPressure), PID 부족(PIDPressure) Condition을 확인해야 한다.

안티패턴 정리

실수결과올바른 접근
--force --grace-period=0을 습관적으로 사용데이터 손실, Finalizer 꼬임원인 파악 후 정상 삭제 우선
kubectl logs 만 보고 --previous 안 씀크래시 직전 로그를 놓침반드시 --previous 병행 확인
resource request 없이 배포노드 Pending, OOMKill 예측 불가모든 컨테이너에 request 설정
Liveness Probe에 DB 연결 확인 포함외부 장애 시 Pod 재시작 폭풍Liveness는 앱 자체만 확인
latest 태그 사용의도치 않은 버전 배포, 캐시 불일치구체적인 버전 태그 또는 digest 사용
Events 만료 후 조사 시작이벤트 소멸(기본 1시간)로 컨텍스트 손실장애 즉시 이벤트 스냅샷 저장

References

  • https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/
  • https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/
  • https://kx.cloudingenium.com/kubernetes-pod-troubleshooting-guide/
  • https://www.netdata.cloud/academy/kubernetes-crash-loop-backoff/
  • https://www.sysdig.com/blog/debug-kubernetes-crashloopbackoff
  • https://komodor.com/learn/how-to-fix-crashloopbackoff-kubernetes-error/
  • https://www.groundcover.com/kubernetes-troubleshooting/crashloopbackoff
  • https://oneuptime.com/blog/post/2026-02-09-kubectl-describe-pod-events/view
  • https://oneuptime.com/blog/post/2026-01-22-kubectl-logs-debug-containers/view
  • https://krishnendubhowmick.medium.com/this-kubernetes-troubleshooting-method-will-make-you-the-hero-on-call-e6bb568479b3