Published on

EKS Pod Pending 0/XX nodes available 원인별 해결

Authors

서버리스처럼 보이는 쿠버네티스도 결국 스케줄러가 올릴 수 있는 노드가 있어야 Pod가 뜹니다. EKS에서 흔히 보는 에러가 바로 Pod Pending + 0/XX nodes available인데, 이 문구 자체는 “모든 노드를 검사했지만 조건을 만족하는 노드가 0개”라는 뜻일 뿐, 진짜 원인은 이벤트 메시지에 숨어 있습니다.

이 글은 kubectl describe pod의 이벤트를 기준으로 원인을 빠르게 분류하고, EKS(Managed Node Group/Cluster Autoscaler/Karpenter, EBS CSI 등) 환경에서 자주 터지는 케이스를 재현 가능한 커맨드와 함께 정리합니다.

> 참고로 IP 고갈로 Pending이 나는 케이스는 증상이 비슷하지만 해결책이 꽤 다릅니다. CNI/서브넷 IP 관점의 진단은 별도 글인 Kubernetes CNI IP 부족으로 Pod Pending 해결 가이드를 함께 보시면 빠릅니다.

1) 가장 먼저: 이벤트 한 줄로 원인 분류하기

Pod가 Pending이면, “노드가 없나?”부터 보기보다 이벤트에서 스케줄러가 거절한 이유를 확인하는 게 가장 빠릅니다.

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

# 이벤트만 보고 싶으면
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp | tail -n 50

describe의 Events에서 아래처럼 나옵니다.

  • 0/10 nodes are available: 10 Insufficient cpu.
  • 0/10 nodes are available: 10 Insufficient memory.
  • 0/10 nodes are available: 10 node(s) had taint {dedicated=..., NoSchedule}.
  • 0/10 nodes are available: 10 node(s) didn't match node selector.
  • 0/10 nodes are available: 10 node(s) didn't match Pod's node affinity.
  • 0/10 nodes are available: 10 node(s) had volume node affinity conflict.
  • pod has unbound immediate PersistentVolumeClaims

이제부터는 이 문구별로 해결합니다.

2) Insufficient cpu/memory/ephemeral-storage: 리소스 요청이 너무 큼

증상

이벤트에 보통 이렇게 찍힙니다.

  • Insufficient cpu
  • Insufficient memory
  • Insufficient ephemeral-storage

EKS에서 특히 자주 하는 실수는 limits만 올리고 requests를 과하게 잡는 것입니다. 스케줄링은 기본적으로 requests 기준으로 이루어집니다.

2-1) 현재 노드의 allocatable/사용량 확인

kubectl get nodes
kubectl describe node <node> | egrep -A5 "Allocatable|Capacity"

# metrics-server가 있다면
kubectl top nodes
kubectl top pod -A | head

kubectl top이 0이거나 비정상이라면 HPA/스케줄링 진단도 꼬일 수 있습니다. 메트릭이 0으로 나오는 문제는 Kubernetes HPA가 안 늘 때 metrics-server 0값 해결도 참고하세요.

2-2) requests/limits 재조정 (예시)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: myrepo/api:1.0
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "512Mi"
  • requests는 현실적인 최소치로 잡아 스케줄 가능성을 높이고
  • limits는 과도한 폭주를 막는 상한으로 둡니다.

2-3) 노드 증설(오토스케일러) 확인

리소스가 정말 부족한 상황이라면 노드가 늘어야 합니다.

  • Managed Node Group + Cluster Autoscaler 사용 시
    • ASG 최대치(maxSize)가 낮아서 늘지 않는 경우가 흔함
  • Karpenter 사용 시
    • Provisioner/NodePool 제약(인스턴스 타입, AZ, 용량 타입) 때문에 안 늘 수 있음

확인 포인트:

# cluster-autoscaler 로그
kubectl -n kube-system logs deploy/cluster-autoscaler | tail -n 200

# karpenter 로그(설치 방식에 따라 이름 다름)
kubectl -n karpenter logs deploy/karpenter | tail -n 200

3) node(s) had taint ... NoSchedule: 태인트/톨러레이션 불일치

증상

이벤트:

  • node(s) had taint {dedicated=xxx: NoSchedule}

노드가 특정 워크로드 전용으로 태인트되어 있는데, Pod가 이를 허용하는 톨러레이션이 없으면 스케줄 불가입니다.

3-1) 노드 태인트 확인

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

# 전체 노드 태인트를 간단히
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

3-2) 해결: 톨러레이션 추가 또는 태인트 제거

톨러레이션을 Pod에 추가:

spec:
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "batch"
      effect: "NoSchedule"

혹은 노드에서 태인트 제거(운영 정책에 맞을 때만):

kubectl taint nodes <node> dedicated=batch:NoSchedule-

4) didn't match node selector / node affinity: 라벨/어피니티 조건 불일치

증상

이벤트:

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

EKS에서는 노드그룹별로 라벨을 달아 워크로드를 분리하는데, 라벨이 없거나 오타가 있으면 전체 노드가 후보에서 제외됩니다.

4-1) Pod의 스케줄 제약 확인

kubectl get pod -n <ns> <pod> -o yaml | yq '.spec.nodeSelector, .spec.affinity'

4-2) 노드 라벨 확인

kubectl get nodes --show-labels

# 특정 키만 보고 싶으면
kubectl get nodes -L nodegroup,topology.kubernetes.io/zone,karpenter.sh/capacity-type

4-3) 해결

  • Pod의 nodeSelector/nodeAffinity를 실제 라벨에 맞게 수정
  • 또는 노드에 라벨 추가
kubectl label node <node> workload=api

5) pod has unbound immediate PersistentVolumeClaims: PVC 바인딩 실패

증상

이벤트:

  • pod has unbound immediate PersistentVolumeClaims

EKS에서는 보통 EBS CSI Driver로 동적 프로비저닝을 하는데, StorageClass/권한/AZ/볼륨 제약 때문에 PV가 안 만들어지면 Pod는 계속 Pending입니다.

5-1) PVC 상태 확인

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

kubectl get sc
kubectl describe sc <storageclass>

5-2) 자주 터지는 원인

  • StorageClass에 volumeBindingMode: Immediate인데, 노드 AZ와 볼륨 AZ가 꼬이는 설계
    • 멀티 AZ 노드풀에서 특히 빈번
  • EBS CSI Driver 미설치/비정상
  • CSI가 AWS API를 못 호출(권한 문제)
    • IRSA/OIDC/TrustPolicy 문제로 AccessDenied가 나면 CSI가 볼륨을 못 만듭니다.

IRSA 점검은 EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검을 참고하면, “왜 권한이 있는데도 안 되지?”를 빠르게 정리할 수 있습니다.

5-3) 해결 방향

  • 가능한 경우 WaitForFirstConsumer를 사용해 스케줄러가 노드를 고른 뒤 그 AZ에 볼륨을 만들게 함
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3-wffc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3

6) volume node affinity conflict: PV/노드 AZ 불일치(특히 EBS)

증상

이벤트:

  • had volume node affinity conflict

이미 존재하는 PV가 특정 AZ에 묶여 있는데(예: EBS), 스케줄러가 그 AZ의 노드에 Pod를 배치할 수 없으면 발생합니다.

체크

kubectl describe pv <pv>
# Node Affinity 섹션에서 topology.kubernetes.io/zone 확인

kubectl get nodes -L topology.kubernetes.io/zone

해결

  • 해당 AZ에 노드가 존재/증설 가능하도록 노드그룹/카펜터 제약 수정
  • 또는 스토리지를 AZ 종속이 덜한 형태(EFS 등)로 재검토
  • Stateful workload라면 “데이터 위치가 곧 스케줄 제약”임을 전제로 설계

7) Too many pods / Insufficient pods: 노드의 Pod 수 한도(ENI/IP) 도달

증상

이벤트가 아래처럼 나올 수 있습니다.

  • Too many pods
  • 또는 간접적으로 0/XX nodes available만 보이고, 노드에는 여유 CPU/메모리가 있는데도 안 올라감

EKS의 VPC CNI는 인스턴스 타입별로 ENI/IP 기반 Pod 최대치가 사실상 정해집니다.

확인

kubectl describe node <node> | egrep -A3 "Non-terminated Pods|Allocated resources"

# aws-node 데몬셋 로그도 힌트가 됨
kubectl -n kube-system logs ds/aws-node --tail=200

해결 방향

  • 노드 타입을 Pod 밀도에 맞게 조정(ENI가 많은 타입)
  • 서브넷 IP 여유 확보, CNI 설정(프리 할당 등) 튜닝
  • 근본 원인/해결은 IP 고갈/할당 구조와 연결되므로, 자세한 절차는 위에서 언급한 CNI IP 글을 권장

8) (덜 흔하지만 치명적) PDB/스케줄링 정책/우선순위로 막히는 경우

  • PDB(PodDisruptionBudget)는 보통 eviction/드레인에 영향을 주지만, 특정 운영 자동화(업그레이드/노드 교체)와 결합되면 “노드가 비는데도 새 Pod가 못 뜨는” 상황이 길어질 수 있습니다.
  • PriorityClass가 없어서 중요한 Pod가 리소스 경쟁에서 계속 밀리는 경우도 있습니다.

확인:

kubectl get pdb -A
kubectl get priorityclass
kubectl describe pod -n <ns> <pod> | egrep -i "priority|preempt"

해결은 워크로드 중요도에 맞게 PriorityClass를 정의하고, PDB는 롤링/장애 대응 시나리오를 기준으로 재설계합니다.

9) 빠른 결론: 이벤트 문자열별 “즉시 액션” 표

이벤트 키워드1차 확인즉시 해결책
Insufficient cpu/memoryrequests/노드 allocatablerequests 낮추기 또는 노드 증설(오토스케일러/ASG max)
had taint ... NoSchedulenode taintstolerations 추가 또는 taint 제거
didn't match node selector/affinitypod nodeSelector/affinity, node labels라벨/셀렉터 정합성 맞추기
unbound immediate PVCPVC/SC/CSI 이벤트SC 수정(WFFC), CSI 상태/권한/쿼터 점검
volume node affinity conflictPV AZ, 노드 AZ해당 AZ에 노드 확보 또는 스토리지 재설계
Too many pods노드 Pod 한도/ENI/IP노드 타입/서브넷/CNI 튜닝

10) 운영에서 재발을 줄이는 체크리스트

  1. 배포 전 requests sanity check: PR 단계에서 requests가 노드 타입 대비 비현실적으로 큰지 검사
  2. 노드풀 분리 시 라벨/태인트 표준화: workload=, dedicated= 키 네이밍을 조직 표준으로 고정
  3. StorageClass 기본값 검토: 멀티 AZ라면 WaitForFirstConsumer가 기본으로 더 안전한 경우가 많음
  4. 오토스케일러 관측성: Cluster Autoscaler/Karpenter 로그를 중앙 수집하고 “왜 scale-out이 안 됐는지”를 알람화
  5. CNI/IP 용량 계획: 서브넷/노드 타입/Pod 수요를 함께 계획(특히 트래픽 이벤트/배치 러시)

0/XX nodes available은 결론이 아니라 분기점입니다. describe pod 이벤트 한 줄을 기준으로 원인을 분류하면, 대부분은 10~20분 내로 “리소스/제약/스토리지/IP” 중 어디가 병목인지 확정할 수 있습니다. 그 다음부터는 해당 축을 확실히 고쳐서, Pending을 “가끔 생기는 미스터리”가 아니라 예측 가능한 용량/정책 문제로 바꾸는 게 핵심입니다.