Published on

K8s Pod가 Pending? 스케줄링 실패 12가지

Authors

서버리스처럼 보이지만, 쿠버네티스의 Pod는 결국 어떤 Node에 올라가야만 실행됩니다. Pod가 Pending에 오래 머무른다는 건 대개 컨테이너 이미지 풀이나 앱 문제 이전에, 스케줄러가 배치 대상 노드를 찾지 못했다는 뜻입니다.

이 글은 “왜 Pending인가?”를 빠르게 좁히기 위한 실전 체크리스트입니다. 특히 kubectl describe pod의 Events에 찍히는 문구(예: 0/3 nodes are available)를 기준으로 12가지 대표 원인을 분류했습니다.

0. 먼저: Pending이 ‘스케줄링’ 문제인지 확인

Pending은 크게 두 갈래입니다.

  • 스케줄링 실패: 노드가 아예 지정되지 않음 (PodScheduled=False)
  • 스케줄링은 됐는데 실행 불가: 노드 지정은 됐지만 이미지 풀/볼륨 마운트 등에서 대기

아래 명령으로 먼저 갈라냅니다.

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

# 조건만 빠르게 보고 싶으면
kubectl get pod -n <ns> <pod> -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason} {.message}{"\n"}{end}'

describe의 Events에 아래 같은 문구가 있으면 이 글의 범위(스케줄링 실패)입니다.

  • FailedScheduling
  • 0/N nodes are available: ...

1) CPU/메모리 요청량(Requests) 과다

증상

Events에 자주 보이는 형태:

  • Insufficient cpu
  • Insufficient memory

원인

resources.requests가 노드의 allocatable을 초과하거나, 클러스터 전체에 여유가 없는 상태입니다. 특히 HPA/VPA, 기본 템플릿 복붙으로 요청량이 과해지는 경우가 많습니다.

해결

  • 요청량을 현실적으로 조정
  • 노드 스케일 아웃(Cluster Autoscaler 등)
  • QoS 정책 재검토
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1"
    memory: "1Gi"

2) Pod 개수(Pod capacity) 한도 초과

증상

  • Too many pods 또는 CNI/노드 설정에 따른 pod 수 제한으로 스케줄 불가

원인

노드당 Pod 수는 kubelet 설정 및 CNI(예: AWS VPC CNI의 IP 할당) 제약을 받습니다. 노드에 자원이 남아도 IP/Pod 슬롯이 없어서 Pending이 됩니다.

해결

  • 노드 타입/ENI/IP 풀 확장
  • 노드 수를 늘려 분산
  • DaemonSet 과다 여부 점검

EKS에서 네트워크 제약이 얽히는 케이스는 아래 글의 점검 흐름도 함께 참고하면 좋습니다.

3) nodeSelector 불일치

증상

  • 0/N nodes are available: N node(s) didn't match node selector.

원인

Pod가 요구하는 라벨이 실제 노드에 없거나 오타가 있습니다.

해결

  • 노드 라벨 확인/수정
  • selector 키/값 오타 제거
kubectl get nodes --show-labels
kubectl label node <node> workload=api
spec:
  nodeSelector:
    workload: api

4) Node Affinity(필수) 조건 불일치

증상

  • didn't match Pod's node affinity/selector

원인

requiredDuringSchedulingIgnoredDuringExecution의 조건이 너무 빡세거나, 노드 라벨 변경으로 조건이 깨졌습니다.

해결

  • required를 preferred로 완화
  • 라벨 체계 정리(환경/가용영역/워크로드 라벨)
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: topology.kubernetes.io/zone
          operator: In
          values: ["ap-northeast-2a"]

5) Taints/Tolerations 미스매치

증상

  • node(s) had taint {key: value}, that the pod didn't tolerate

원인

노드가 taint로 보호되고 있는데 Pod가 toleration을 갖고 있지 않습니다. 운영에서 흔한 패턴:

  • 전용 노드풀(예: dedicated=ml:NoSchedule)
  • 장애/디스크 이슈 노드의 NoSchedule

해결

  • 필요한 Pod에만 toleration 부여
  • taint 제거는 신중히(노드풀 격리 무너짐)
kubectl describe node <node> | sed -n '/Taints/,$p'
tolerations:
- key: "dedicated"
  operator: "Equal"
  value: "ml"
  effect: "NoSchedule"

6) Pod Anti-Affinity/TopologySpreadConstraints로 인해 배치 불가

증상

  • didn't match pod anti-affinity rules
  • constraints violated 류 메시지

원인

고가용성을 위해 같은 노드/존에 같이 못 올라가게 막아뒀는데, 실제 노드 수/존 수가 부족해 조건을 만족할 수 없습니다.

해결

  • anti-affinity를 preferred로 완화
  • spread constraint의 whenUnsatisfiableScheduleAnyway로 조정(의도 확인 필요)
  • 노드/존 확장
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchLabels:
          app: api
      topologyKey: kubernetes.io/hostname

7) PVC(스토리지) 바인딩 대기(WaitForFirstConsumer)

증상

  • pod has unbound immediate PersistentVolumeClaims
  • PVC가 Pending이며 PV가 안 잡힘

원인

StorageClass가 WaitForFirstConsumer인 경우, 스케줄링과 볼륨 프로비저닝이 엮입니다. 또한 AZ 제약(EBS 등) 때문에 특정 존에만 볼륨을 만들 수 있는데, Pod 조건이 그 존과 충돌하면 계속 Pending이 됩니다.

해결

  • PVC/PV/StorageClass 상태 확인
  • topology 제약(존) 정합성 맞추기
  • 잘못된 accessMode/size 요청 수정
kubectl get pvc -n <ns>
kubectl describe pvc -n <ns> <pvc>
kubectl get storageclass

8) HostPort/NodePort 충돌(포트 바인딩 불가)

증상

  • didn't have free ports for the requested pod ports

원인

hostPort를 쓰는 Pod가 이미 같은 노드에서 해당 포트를 점유 중입니다. DaemonSet + hostPort 조합에서 특히 자주 터집니다.

해결

  • hostPort 사용 최소화(가능하면 Service로)
  • 포트 변경 또는 노드 수 확장
containers:
- name: agent
  image: example/agent:1
  ports:
  - containerPort: 8125
    hostPort: 8125
    protocol: UDP

9) 우선순위/선점(Preemption) 실패

증상

  • preemption: ... No preemption victims found
  • preemption is not helpful for scheduling

원인

클러스터가 꽉 찼을 때 높은 우선순위 Pod가 낮은 우선순위 Pod를 밀어내며 들어올 수 있습니다. 하지만

  • 낮은 우선순위 Pod가 없거나
  • 밀어내도 자원이 충분히 안 생기거나
  • PDB(중단 예산) 때문에 축출 불가

이면 선점이 실패합니다.

해결

  • PriorityClass 설계 재검토
  • PDB 완화(가용성 요구와 균형)
  • 노드 스케일 아웃
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: critical
value: 100000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Critical workloads"

10) 리소스 쿼터(ResourceQuota) 또는 LimitRange 제약

증상

FailedScheduling보다는 생성 단계에서 막히는 경우도 있지만, 네임스페이스 정책이 강하면 Pending/생성 실패가 섞여 보일 수 있습니다.

  • exceeded quota
  • 기본 requests/limits 강제 부여로 결과적으로 Insufficient 발생

원인

네임스페이스에 CPU/메모리/Pod 수 쿼터가 걸려 있거나, LimitRange가 최소 requests를 강제해 노드에 못 올라가는 크기가 됩니다.

해결

  • kubectl describe quota, kubectl describe limitrange로 정책 확인
  • 쿼터 상향 또는 배포 스펙 조정
kubectl get resourcequota -n <ns>
kubectl describe resourcequota -n <ns>
kubectl get limitrange -n <ns>
kubectl describe limitrange -n <ns>

11) 노드가 NotReady / Cordon / Drain 상태

증상

  • node(s) were not ready
  • node(s) were unschedulable

원인

노드가 NotReady이거나 운영자가 cordon 해둔 상태입니다. 장애/업그레이드/드레인 작업 중 흔합니다.

해결

  • 노드 상태 확인 후 원인(CNI, kubelet, 디스크 압박 등) 제거
  • 의도된 cordon이면 노드풀 증설 또는 해제
kubectl get nodes
kubectl describe node <node>

# 스케줄링 재개
kubectl uncordon <node>

12) 스케줄러/클러스터 정책(Admission, PSP 대체, SCC 등) 간접 영향

증상

겉으로는 Pending인데, 실제로는 정책이 스펙을 변형/거부하거나 특정 노드로만 몰리게 만들어 스케줄링이 막힙니다. 예:

  • 특정 런타임 클래스/보안 정책으로 인해 일부 노드만 후보가 됨
  • sidecar 주입(istio 등)으로 requests가 커져 Insufficient로 변함

원인

Admission Controller, 보안 정책(PSA, OPA/Gatekeeper, Kyverno), RuntimeClass, 서비스 메시 주입 등이 Pod 스펙을 바꾸거나 노드 후보군을 줄입니다.

해결

  • 실제 적용된 Pod 스펙을 확인(배포 YAML vs 생성된 Pod)
  • kubectl get pod -o yaml로 변형 여부 확인
  • 정책 로그/리포트(OPA/Kyverno) 확인
# 생성된 Pod 스펙이 어떻게 바뀌었는지 확인
kubectl get pod -n <ns> <pod> -o yaml | less

# Mutating/ValidatingWebhook 확인
kubectl get mutatingwebhookconfigurations
kubectl get validatingwebhookconfigurations

빠른 진단 루틴(5분 컷)

운영에서 가장 효율이 좋았던 순서입니다.

  1. kubectl describe pod Events에서 정확한 문구를 읽는다.
  2. 0/N nodes are available 뒤의 이유를 분류한다.
  3. 아래 3가지는 동시에 확인한다.
    • 노드 상태: kubectl get nodes
    • 자원 여유: kubectl top nodes(metrics-server 필요)
    • 제약 조건: nodeSelector/affinity/taints/PVC
kubectl describe pod -n <ns> <pod> | sed -n '/Events/,$p'

kubectl get nodes
kubectl top nodes || true

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

Pending 이후(노드 지정은 됐는데)도 자주 헷갈리는 포인트

이번 글은 스케줄링 실패 중심이지만, 현장에서는 “Pending인 줄 알았는데 사실 ContainerCreating에서 멈춤” 같은 케이스도 많습니다. 그때는 이미지 풀, CNI, 볼륨 마운트, DNS 등을 봐야 합니다. 네트워크/엣지 진단을 빠르게 끝내는 접근은 아래 글의 ‘로그로 30분 진단’ 방식이 사고 흐름을 잡는 데 도움이 됩니다.

마무리: Events는 답을 알고 있다

Pod Pending은 막연히 “클러스터가 이상하다”가 아니라, 스케줄러가 왜 배치할 수 없는지를 Events에 거의 항상 남깁니다. 핵심은 그 메시지를 12가지 범주(자원/라벨·어피니티/테인트/스토리지/포트/우선순위/정책/노드상태) 중 어디에 넣을지 빠르게 익히는 것입니다.

다음에 Pending을 만나면, 먼저 kubectl describe podFailedScheduling 한 줄을 복사해 두고 이 체크리스트에 대입해 보세요. 대부분은 5~10분 안에 원인이 좁혀집니다.