- Published on
EKS Pod Pending(Insufficient cpu) 원인과 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스처럼 보이지만, Kubernetes 스케줄링은 매우 ‘물리적’입니다. EKS에서 Pod가 Pending으로 오래 머물고 0/.. nodes are available: Insufficient cpu 이벤트가 반복되면, 대부분은 단순히 “CPU가 부족하다”가 아니라 **요청(request)·제한(limit)·노드 용량·스케줄링 제약(affinity/taint/PDB/priority)**이 맞물린 결과입니다. 이 글에서는 Insufficient cpu를 스케줄러 관점에서 해부하고, 운영에서 바로 쓰는 해결 순서를 제시합니다.
> 같은 EKS 트러블슈팅 맥락에서 네트워크/런타임 이슈도 함께 점검하면 좋습니다: EKS CrashLoopBackOff인데 로그가 0줄? 원인 8가지, EKS에서 NLB 타겟 Unhealthy - 헬스체크·Pod·SG
증상 확인: 정말 CPU가 ‘부족’한가?
먼저 “노드의 실제 사용률”이 아니라 Pod의 request 합 기준으로 스케줄링이 막힌다는 점을 확인해야 합니다. 노드에 CPU가 남아 보여도(top) request가 과도하면 스케줄러는 배치하지 않습니다.
1) Pending Pod 이벤트 확인
kubectl get pod -n <ns>
kubectl describe pod <pod> -n <ns>
Events에 보통 아래 같은 문구가 보입니다.
0/6 nodes are available: 6 Insufficient cpu.- 또는
Insufficient cpu, Insufficient memory혼합 - 또는
node(s) had taint {..}같이 다른 제약과 동시 발생
2) 클러스터에서 어떤 리소스가 병목인지 빠르게 보기
kubectl get nodes
kubectl describe node <node>
Allocatable과 Allocated resources(requests/limits 합계)를 비교하세요.
추가로 Metrics Server가 있다면:
kubectl top nodes
kubectl top pods -A --sort-by=cpu
단, top은 “실사용”이고 스케줄링은 “request” 기반이므로 둘이 다를 수 있습니다.
원인 1: Pod CPU request가 과도하거나 잘못 설정됨
가장 흔한 케이스입니다. 특히 HPA/VPA를 도입했거나, 템플릿에서 과거 값이 그대로 남아 request가 과하게 잡혀 있는 경우가 많습니다.
체크 포인트
resources.requests.cpu가 실제 필요량 대비 과도limit만 있고request가 없는 경우(기본적으로 request=limit로 잡히는 정책이 있거나, LimitRange가 강제하는 경우)- 사이드카(Envoy, Fluent Bit 등) request 합산으로 커짐
예시: request/limit 합리화
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: example.com/api:1.0.0
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
운영 팁:
- 초기에는 request를 보수적으로 낮게, limit은 피크 보호용으로 설정
- CPU는 throttling이 있어도 죽지 않는 경우가 많아(워크로드 특성에 따라) request를 낮추는 효과가 큼
- 반대로 latency 민감 서비스라면 limit을 너무 낮게 잡으면 throttling으로 지연이 커질 수 있음
원인 2: 노드의 Allocatable CPU가 생각보다 작음(오버헤드)
EC2 인스턴스 vCPU가 4라고 해서 쿠버네티스가 Pod에 4 vCPU를 다 주지 않습니다.
- kubelet/system-reserved, kube-reserved
- DaemonSet(aws-node, kube-proxy, coredns, 로깅/모니터링 에이전트)
- ENI/IP 제약과 함께 노드가 사실상 ‘작게’ 느껴짐
확인 방법
kubectl describe node <node> | sed -n '/Allocatable/,+20p'
여기서 cpu allocatable이 기대보다 작다면, 노드 타입 자체를 키우거나(스케일 업), 노드 수를 늘리거나(스케일 아웃), DaemonSet 리소스를 최적화해야 합니다.
원인 3: Cluster Autoscaler/Karpenter가 스케일 아웃을 못함
Pending인데 노드가 늘지 않는다면, 오토스케일러가 작동하지 않거나 “늘려도 배치할 수 없는 Pod”로 판단했을 수 있습니다.
Cluster Autoscaler 점검
kubectl -n kube-system get deploy | grep -i autoscaler
kubectl -n kube-system logs deploy/cluster-autoscaler --tail=200
자주 보는 원인:
- 노드그룹
maxSize가 이미 꽉 참 - ASG/노드그룹이 특정 AZ에만 묶여 있고 해당 AZ 용량 부족
- Pod에
nodeSelector/affinity가 걸려 있는데 해당 라벨을 가진 노드그룹이 없음 - 스케일 아웃 권한(IAM) 문제
Karpenter 점검(사용 시)
kubectl -n karpenter logs deploy/karpenter --tail=200
kubectl get nodepool,ec2nodeclass -A
특정 인스턴스 패밀리만 허용해 두고 해당 타입이 스팟/온디맨드에서 품절이면, 결과적으로 Insufficient cpu가 지속됩니다.
원인 4: 스케줄링 제약이 CPU 부족처럼 보이게 만듦
이벤트 메시지에 Insufficient cpu만 찍히는 경우도 있지만, 실제로는 아래 제약이 더 큰 문제인 경우가 있습니다.
nodeSelector/nodeAffinity로 특정 노드에만 배치 가능taints/tolerations불일치topologySpreadConstraints가 너무 빡빡함podAntiAffinity로 인해 같은 노드에 못 올라감
한 번에 보는 방법
kubectl describe pod <pod> -n <ns>
# Node-Selectors, Tolerations, Affinity, Topology Spread 확인
그리고 스케줄러의 실제 판단을 보려면:
kubectl -n kube-system get events --sort-by=.lastTimestamp | tail -n 50
해결 전략: “지금 당장” vs “재발 방지”
아래는 운영에서 효과가 큰 순서입니다.
1) 즉시 조치(가장 빠름): request 낮추거나 replicas 줄이기
긴급 장애 상황에서는 스케줄링이 먼저입니다.
- CPU request를 낮춰 Pending을 해소
- 일시적으로 replicas를 줄여 핵심 트래픽만 처리
kubectl -n <ns> scale deploy/<name> --replicas=1
또는 values(Helm)로 request를 조정 후 재배포합니다.
2) 노드 스케일 아웃: 노드그룹 maxSize/서브넷/AZ 확인
Cluster Autoscaler를 쓰는 경우:
- Managed Node Group의
maxSize상향 - 서브넷 IP 여유(특히 VPC CNI 사용 시) 확인
- AZ 분산이 한쪽으로 쏠리지 않게 구성
노드가 늘어나도 여전히 Pending이면, “늘어난 노드가 Pod 요구조건을 만족하는지(라벨/taint/아키텍처)”를 확인해야 합니다.
3) 노드 스케일 업: 인스턴스 타입 재설계
작은 노드를 많이 쓰는 전략이 항상 이득은 아닙니다.
- DaemonSet 오버헤드가 큰 클러스터는 노드가 작을수록 손해
- CPU request가 큰 워크로드(예: 2 vCPU 이상)는 작은 노드에 조각나서 못 들어갈 수 있음(단편화)
예:
t3.medium(2 vCPU)다수 →m6i.large(2 vCPU)로 바꿔도 큰 차이는 없을 수 있음- 오버헤드 고려 시
m6i.xlarge(4 vCPU)이상으로 올리면 효율이 좋아지는 경우가 많음
4) Pod 우선순위/선점(Preemption) 활용
중요한 Pod가 Pending이면, 덜 중요한 Pod를 밀어내는 전략이 필요할 수 있습니다.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: critical
value: 100000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Critical workloads"
그리고 Deployment에:
spec:
template:
spec:
priorityClassName: critical
주의: 선점은 클러스터 전체 안정성에 영향을 주므로 “무조건” 답은 아닙니다.
5) HPA/VPA/LimitRange 정책 재점검(재발 방지 핵심)
HPA가 replicas를 늘리는데 노드가 못 따라가는 경우
- HPA target CPU utilization이 너무 낮아 과도하게 스케일 아웃
- Cluster Autoscaler 반응 속도(노드 프로비저닝 수 분)와 HPA 반응 속도(수십 초)가 불일치
해결:
- HPA 정책 완화(최대 replicas 제한)
- scaleUp/scaleDown behavior 튜닝
VPA가 request를 과하게 올리는 경우
VPA를 쓰면 request가 커져 Pending을 유발할 수 있습니다. 특히 Auto 모드에서 급격한 상향이 발생하면, 기존 노드로는 스케줄 불가가 됩니다.
LimitRange가 request를 강제하는 경우
네임스페이스에 LimitRange가 있으면 “명시하지 않은 request”가 자동으로 커질 수 있습니다.
kubectl get limitrange -n <ns>
kubectl describe limitrange -n <ns>
6) 리소스 단편화(Fragmentation) 줄이기
클러스터에 여유 CPU 총량이 있어도, 각 노드에 남은 CPU 조각이 request보다 작으면 Pending이 납니다.
대응:
- 노드 타입을 더 큰 것으로 섞기(혼합 인스턴스)
- Pod request를 조금 낮춰 “들어갈 틈” 만들기
- Karpenter 사용 시 다양한 인스턴스 패밀리/사이즈 허용
실전 디버깅 체크리스트(명령어 중심)
1) 어떤 조건 때문에 못 올라가는지
kubectl describe pod <pod> -n <ns>
2) 노드 allocatable과 requests 합
kubectl describe node <node>
3) 네임스페이스 정책(LimitRange/ResourceQuota)
kubectl get resourcequota,limitrange -n <ns>
kubectl describe resourcequota -n <ns>
4) 오토스케일러 로그
kubectl -n kube-system logs deploy/cluster-autoscaler --tail=300
# 또는
kubectl -n karpenter logs deploy/karpenter --tail=300
자주 하는 실수 5가지
- 노드 CPU 사용률이 낮으니 여유가 있다고 착각: 스케줄링은 request 기준입니다.
- 사이드카 request를 빼먹음: Envoy/로그 에이전트가 합산되어 큰 Pod가 됩니다.
- maxSize를 안 올림: Autoscaler는 있어도 늘릴 수 없으면 Pending이 지속됩니다.
- nodeSelector/taint로 스스로 가둠: 특정 노드군에만 배치되도록 해두고 CPU 부족을 겪습니다.
- 너무 작은 노드로만 구성: 오버헤드와 단편화로 실제 가용 CPU가 급감합니다.
마무리: Insufficient cpu는 “request 설계” 문제로 귀결된다
EKS에서 Pod Pending: Insufficient cpu는 단순 증상이고, 근본 원인은 보통 아래 중 하나로 정리됩니다.
- request가 현실 대비 과도하거나 정책(LimitRange/VPA)로 부풀려짐
- 노드 allocatable이 오버헤드로 작아짐(특히 DaemonSet)
- 오토스케일러가 확장하지 못함(maxSize/AZ/제약/권한)
- 스케줄링 제약으로 특정 노드에만 몰림
운영 관점에서 가장 효과적인 접근은 (1) Pending Pod 이벤트로 제약을 정확히 읽고 → (2) request/replicas를 즉시 조정해 서비스 복구 → (3) 노드그룹/오토스케일러/정책을 재설계해 재발을 막는 것입니다.