Published on

Kubernetes Pod Pending 원인 - PV·노드·Quota 7단계

Authors

Pod가 Pending 상태로 오래 멈춰 있으면 대부분은 스케줄러가 배치할 수 없는 이유가 명확히 존재합니다. 문제는 그 이유가 kubectl get pod만으로는 잘 안 보인다는 점입니다. 이 글은 현장에서 자주 만나는 원인을 PV·노드·Quota 축으로 묶어, “어디부터 확인해야 하는지”를 7단계로 정리합니다.

진단은 원칙적으로 다음 순서가 가장 빠릅니다.

  1. Pod 이벤트 확인 → 2) 스케줄링 제약 확인 → 3) 노드 리소스/상태 확인 → 4) PV/PVC 바인딩 확인 → 5) 이미지/시크릿 등 런타임 의존성 확인 → 6) 네트워크(CNI IP) 확인 → 7) 네임스페이스 Quota/LimitRange 확인

관련해서 EKS 환경에서 0/XX nodes available 메시지를 더 깊게 다룬 글은 아래도 참고할 수 있습니다.


0단계: “Pending”의 의미를 정확히 잡기

Pending은 대개 다음 중 하나입니다.

  • 스케줄링 자체가 안 됨: 노드 선택 실패, 리소스 부족, taint/toleration 불일치, PV 조건 불일치 등
  • 스케줄링은 됐지만 컨테이너가 못 뜸: 이 경우는 보통 ContainerCreating이나 ImagePullBackOff로 더 진행되지만, 이벤트를 보면 Pending처럼 보일 수 있습니다.

가장 먼저 해야 할 것은 “스케줄링 실패인지, 런타임 준비 실패인지”를 이벤트로 가르는 것입니다.

kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod>

# 이벤트만 빠르게 필터링
kubectl -n <ns> get events --sort-by=.metadata.creationTimestamp \
  | tail -n 50

describeEvents: 섹션에 FailedScheduling이 보이면 스케줄러 단계 문제입니다. FailedMount, FailedAttachVolume, FailedCreatePodSandBox 등이 보이면 각각 스토리지/CSI, 볼륨, CNI 계열로 좁혀집니다.


1단계: FailedScheduling 메시지에서 1차 분류

가장 흔한 패턴은 아래와 같습니다.

  • 0/NN nodes are available: NN Insufficient cpu 또는 Insufficient memory
  • node(s) had taint {key: value}, that the pod didn't tolerate
  • node(s) didn't match Pod's node affinity/selector
  • pod has unbound immediate PersistentVolumeClaims
  • No preemption victims found 또는 Preemption is not helpful

여기서 이미 원인의 70%는 결정됩니다. 다음 단계들은 이 메시지 유형별로 더 파고드는 체크리스트입니다.


2단계: 노드 선택 제약 (nodeSelector, affinity) 확인

의도치 않게 nodeSelectornodeAffinity가 너무 빡세면 “리소스는 남는데도” 스케줄이 안 됩니다.

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.nodeSelector}'
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.affinity}'

노드 라벨과 비교합니다.

kubectl get nodes --show-labels
kubectl get node <node> -o jsonpath='{.metadata.labels}'

자주 하는 실수

  • 라벨 키 오타: node-role.kubernetes.io/worker vs node-role.kubernetes.io/workers
  • 환경별 라벨 불일치: 운영에서는 topology.kubernetes.io/zone이 다른 값
  • requiredDuringSchedulingIgnoredDuringExecution을 남발해 후보 노드가 0이 됨

해결은 라벨/셀렉터를 맞추거나, preferred 조건으로 완화하는 것입니다.


3단계: taint/toleration 불일치 확인

노드에 taint가 걸려 있으면 toleration이 없는 Pod는 배치되지 않습니다.

kubectl describe node <node> | sed -n '/Taints:/,/Conditions:/p'

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.tolerations}'

예를 들어 노드에 dedicated=ml:NoSchedule taint가 있으면 Pod에 아래 toleration이 필요합니다.

apiVersion: v1
kind: Pod
metadata:
  name: demo
spec:
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "ml"
      effect: "NoSchedule"
  containers:
    - name: app
      image: nginx:1.27

주의할 점은 “toleration을 추가하면 스케줄은 되지만, 원래 격리 의도(dedicated 노드)가 깨질 수 있다”는 것입니다. 운영에서는 toleration 추가 전, 해당 taint의 목적을 먼저 확인하세요.


4단계: 노드 리소스 부족 (CPU/메모리/디스크) vs 요청량 과다

Insufficient cpuInsufficient memory는 단순히 노드가 꽉 찼거나, Pod의 requests가 과도할 때 발생합니다.

4-1. 노드/파드 리소스 현황

kubectl top node
kubectl top pod -n <ns>

# 스케줄러 관점의 allocatable/requests 확인
kubectl describe node <node> | sed -n '/Allocatable:/,/Events:/p'

4-2. requests/limits 점검

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.containers[*].resources}'

requests를 과하게 잡아놓으면 실제 사용량이 낮아도 스케줄이 막힙니다. 특히 자바/노드 런타임에서 “메모리 넉넉히” 잡아두는 관행이 클러스터에서는 곧바로 Pending으로 이어집니다.

4-3. ephemeral-storage도 확인

이미지 레이어, 로그, emptyDir 등으로 ephemeral-storage가 부족해도 스케줄 실패가 납니다.

kubectl describe node <node> | grep -n "ephemeral-storage" -n

5단계: PV/PVC 바인딩 문제 (가장 흔한 스토리지 원인)

pod has unbound immediate PersistentVolumeClaims 또는 FailedMount, FailedAttachVolume는 PV/PVC 계열입니다.

5-1. PVC 상태부터 확인

kubectl -n <ns> get pvc
kubectl -n <ns> describe pvc <pvc>

PVC가 Pending이면 대개 아래 중 하나입니다.

  • StorageClass가 없거나 이름이 틀림
  • 동적 프로비저너(CSI)가 없거나 장애
  • PV가 있어도 accessModes, storageClassName, volumeMode, capacity가 불일치
  • volumeBindingModeWaitForFirstConsumer인데, 노드/존 제약과 꼬임

5-2. PV와 StorageClass 확인

kubectl get sc
kubectl describe sc <sc>

kubectl get pv
kubectl describe pv <pv>

특히 멀티 AZ 환경에서는 PV가 특정 존에 묶이는데, Pod는 다른 존 노드로만 스케줄 가능해서 교착이 생깁니다. 이때 WaitForFirstConsumer가 도움이 되기도 하지만, 반대로 노드 선택 제약이 강하면 더 악화될 수 있습니다.

5-3. “즉시 바인딩(Immediate)”의 함정

volumeBindingMode: Immediate인 StorageClass는 PVC 생성 시점에 PV를 먼저 잡습니다. 이후 Pod가 특정 존/노드로만 가야 하면 스케줄이 막힐 수 있습니다. 가능한 경우 WaitForFirstConsumer를 검토하세요.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-wffc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: gp3

6단계: CNI IP 고갈 및 Pod sandbox 생성 실패

이벤트에 FailedCreatePodSandBox 또는 IP 할당 실패가 보이면 CNI 계열입니다. 특히 EKS의 AWS VPC CNI에서는 서브넷 IP가 부족하면 Pod가 Pending/ContainerCreating에서 멈춥니다.

kubectl -n <ns> describe pod <pod> | sed -n '/Events:/,$p'

# CNI/노드 에이전트 로그 확인(환경에 따라 데몬셋 이름이 다를 수 있음)
kubectl -n kube-system get pods -o wide | grep -E 'cni|aws-node|calico|cilium'

이 주제는 원인과 해결책(서브넷 확장, ENI/Prefix Delegation 설정, maxPods 조정, 노드그룹 확장 등)이 길어 별도 글로 정리해두었습니다.


7단계: ResourceQuota / LimitRange로 인한 “보이지 않는” Pending

네임스페이스에 Quota가 걸려 있으면, 스케줄러 이전에 어드미션 단계에서 막히거나(생성 실패), 혹은 특정 리소스가 부족해 배치가 계속 지연되는 것처럼 보일 수 있습니다. 또한 LimitRange가 requests/limits를 강제로 주입하면서 결과적으로 Insufficient를 유발하기도 합니다.

7-1. Quota 확인

kubectl -n <ns> get resourcequota
kubectl -n <ns> describe resourcequota <rq>

여기서 usedhard에 근접/도달했는지 봅니다. 자주 막히는 항목은 아래입니다.

  • requests.cpu, requests.memory
  • limits.cpu, limits.memory
  • count/pods
  • requests.storage (PVC 생성 자체가 막힐 수 있음)

7-2. LimitRange 확인

kubectl -n <ns> get limitrange
kubectl -n <ns> describe limitrange <lr>

LimitRange가 기본 requests/limits를 주입하면, 개발자가 의도한 값보다 커져서 Pending이 날 수 있습니다. kubectl get pod -o yaml로 실제 주입된 값을 확인하세요.


실전 체크리스트: 3분 안에 결론 내는 루틴

아래 순서대로 보면 “감”이 아니라 “증거”로 결론이 납니다.

  1. kubectl -n <ns> describe pod <pod>에서 Events 확인
  2. FailedScheduling이면
    • nodeSelector/affinity 확인
    • taint/toleration 확인
    • Insufficient면 requests/allocatable 비교
  3. unbound immediate PersistentVolumeClaims
    • PVC Pending 원인(스토리지클래스/CSI/존) 확인
  4. FailedCreatePodSandBox
    • CNI IP 고갈/네트워크 플러그인 로그 확인
  5. 마지막으로 Quota/LimitRange 확인

트러블슈팅 예시: 이벤트 메시지별 처방전

예시 A: 0/6 nodes are available: 6 Insufficient memory

  • Pod의 requests.memory가 과도한지 먼저 확인
  • 노드가 실제로 꽉 찼다면 HPA가 아니라 Cluster Autoscaler나 노드 증설이 필요
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.containers[*].resources.requests.memory}'
kubectl describe node <node> | sed -n '/Allocated resources:/,/Events:/p'

예시 B: pod has unbound immediate PersistentVolumeClaims

  • PVC가 Pending인지 확인하고 StorageClass/CSI부터 점검
kubectl -n <ns> get pvc
kubectl -n <ns> describe pvc <pvc>

예시 C: node(s) had taint ... that the pod didn't tolerate

  • toleration을 추가하기 전에 taint의 의도(전용 노드, 시스템 노드, 장애 격리)를 확인
kubectl describe node <node> | sed -n '/Taints:/,/Conditions:/p'

마무리

Pod Pending은 “쿠버네티스가 느려서”가 아니라, 거의 항상 스케줄러/스토리지/네트워크/정책 중 하나가 논리적으로 불가능한 상태를 의미합니다. 핵심은 kubectl describe pod의 이벤트를 출발점으로 삼고, PV·노드·Quota를 축으로 7단계로 좁혀 들어가는 것입니다.

운영에서 특히 많이 만나는 조합은 다음입니다.

  • 노드 리소스는 남는데 affinity가 후보를 0으로 만듦
  • PVC가 특정 존에 묶여 스케줄이 교착
  • CNI IP가 고갈되어 sandbox 생성이 실패
  • LimitRange가 requests를 키워 Insufficient를 유발

이 루틴을 팀 런북으로 고정해두면, Pending 대응 시간이 체감상 크게 줄어듭니다.