- Published on
EKS에서 Karpenter 노드가 안 늘 때 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스처럼 자동으로 노드가 늘어날 거라 믿고 배포했는데, Pod는 Pending인데도 Karpenter가 조용하면 꽤 당황스럽습니다. 이 글은 **“왜 노드가 안 늘지?”**를 10분 안에 좁히는 것을 목표로 합니다. 핵심은 (1) Pending Pod가 ‘스케줄 불가’ 상태인지, (2) Karpenter가 그 요구를 만족하는 인스턴스를 만들 수 있는 설정/권한/네트워크가 있는지, **(3) 만들려고 했는데 AWS에서 막히는지(용량/쿼터/정책)**를 순서대로 확인하는 것입니다.
아래 순서는 실제 장애 대응에서 가장 빨리 결론에 도달하는 흐름으로 정리했습니다.
0) 전제: “Karpenter가 반응해야 하는 Pending”인지 확인
Karpenter는 스케줄러가 배치할 수 없는 Pod(unschedulable)에 반응합니다. 단순히 이미지 풀링 중이거나, PVC 바인딩 대기 등으로 Pending인 경우는 노드 증설과 무관할 수 있습니다.
1분 체크: Pod 이벤트에서 이유를 읽는다
kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod> | sed -n '/Events:/,$p'
다음과 같은 메시지가 보이면 “노드가 부족해서”가 맞습니다.
0/.. nodes are available: insufficient cpu/memorypod has unbound immediate PersistentVolumeClaims(이건 스토리지 문제일 가능성이 큼)node(s) didn't match Pod's node affinity/selectornode(s) had taint ... that the pod didn't tolerate
중요 포인트
unbound PVC면 Karpenter가 노드를 늘려도 해결이 안 됩니다. EBS CSI 이슈가 의심되면 EKS에서 EBS CSI Attach timeout으로 볼륨 못붙을 때도 같이 확인하세요.
1) 2분: Karpenter 컨트롤러가 정상 동작 중인지
Karpenter가 죽어 있거나, 리더 선출/웹훅/권한 문제로 이벤트 처리를 못 하면 확장이 멈춥니다.
kubectl -n karpenter get deploy,po
kubectl -n karpenter logs deploy/karpenter -c controller --tail=200
로그에서 즉시 찾을 키워드:
AccessDenied,UnauthorizedOperation,sts:AssumeRoleWithWebIdentity→ IAM/IRSAfailed to list,throttling,RequestLimitExceeded→ AWS API 제한/쿼터webhook/timeout/context deadline exceeded→ 웹훅/Admission 경로 문제
웹훅 타임아웃이 의심되면(특히 CRD 적용/변경 시 멈춤) EKS에서 Webhook 타임아웃? Admission 진단법 흐름으로 네트워크/엔드포인트부터 점검하는 게 빠릅니다.
2) 2분: “Karpenter가 실제로 스케줄 불가 Pod를 보고 있는지” 확인
Karpenter는 내부적으로 스케줄러 이벤트를 보고 “프로비저닝할 수요”를 계산합니다. 가장 빠른 확인은 Karpenter 이벤트/로그에서 Provisioning 시도를 했는지를 보는 것입니다.
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50
kubectl -n karpenter logs deploy/karpenter -c controller --tail=200 | egrep -i "provision|launch|unschedul|nodeclaim|nodepool|error"
- 로그에
computed new nodeclaim같은 흔적이 전혀 없다면: Pod가 unschedulable이 아니거나, Karpenter가 클러스터 이벤트를 보지 못하는 상태(권한/컨트롤러 오류)일 수 있습니다. created nodeclaim까지 갔는데 노드가 안 생기면: AWS 쪽(용량/네트워크/권한/정책)에서 막히는 경우가 많습니다.
3) 2분: NodePool/EC2NodeClass 조건이 Pod 요구사항을 ‘충족 불가능’하게 만들었는지
Karpenter v1.x 기준으로 흔한 원인은 NodePool 제약이 너무 빡빡하거나, Pod의 nodeSelector/affinity/taints 요구가 NodePool과 충돌하는 경우입니다.
NodePool 상태와 조건 확인
kubectl get nodepool
kubectl describe nodepool <name>
kubectl get ec2nodeclass
kubectl describe ec2nodeclass <name>
보면서 체크할 것:
- NodePool의
requirements(예:instance-family,instance-size,capacity-type,arch,zone)가 너무 제한적이지 않은가? limits로 CPU/메모리 상한을 너무 낮게 잡아 “더 이상 늘 수 없는” 상태는 아닌가?disruption/consolidation설정이 과도하게 공격적이어서 생성 직후 정리되는 패턴은 아닌가?
Pod 요구사항과 충돌 여부를 빠르게 보는 법
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.nodeSelector}'; echo
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.affinity}'; echo
kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.tolerations}'; echo
자주 터지는 충돌 예시:
- Pod에
nodeSelector: karpenter.sh/capacity-type=spot이 있는데 NodePool은on-demand만 허용 - Pod가 특정 AZ만 허용하는데(NodeAffinity) NodePool/EC2NodeClass는 다른 서브넷(AZ)만 선택
- NodePool이 특정 taint를 기본으로 주는데 Pod에 toleration이 없음
4) 2분: IAM/IRSA/인스턴스 프로파일(노드 역할) 문제
Karpenter는 크게 두 종류 권한이 필요합니다.
- 컨트롤러(karpenter) 권한: EC2 RunInstances, CreateTags, Describe*, Pricing(옵션), SQS(인터럽션), IAM PassRole 등
- 노드(EC2) 권한: ECR Pull, CNI, CloudWatch 등(보통 EKS Managed Node와 유사)
컨트롤러 권한 문제는 로그가 가장 빠르다
kubectl -n karpenter logs deploy/karpenter -c controller --tail=200 | egrep -i "accessdenied|unauthorized|passrole|assume"
특히 많이 놓치는 포인트:
iam:PassRole누락 → NodeClass에 지정한 NodeRole을 Karpenter가 EC2에 붙이지 못함- IRSA 설정은 됐는데 OIDC/조건이 안 맞아
AssumeRoleWithWebIdentity실패
IRSA 자체는 되는데 특정 AWS API만 403/AccessDenied가 나는 패턴은 원인 분해가 필요합니다. 유사한 접근으로 EKS IRSA는 되는데 S3만 403? 30분 진단을 참고하면 “권한/엔드포인트/조건”을 나눠 보는 데 도움이 됩니다.
5) 3분: 네트워크(서브넷/보안그룹/IP)로 인해 노드가 못 뜨거나, 떠도 클러스터에 못 붙는 경우
Karpenter가 인스턴스를 띄웠는데도 노드가 클러스터에 등록되지 않으면, 겉으로는 “노드가 안 늘었다”처럼 보입니다. 이때는 EC2는 생성됐는지부터 확인합니다.
NodeClaim/EC2 생성 여부 확인
kubectl get nodeclaim
kubectl describe nodeclaim <name>
NodeClaim 이벤트에 Launched가 있는데도 Node가 안 생기면 다음을 의심합니다.
(1) 서브넷 태그/선택 조건 문제
- EC2NodeClass의
subnetSelectorTerms가 실제 서브넷과 매칭되지 않음 - 서브넷에 Karpenter/EKS가 기대하는 태그가 누락
(2) 보안 그룹 인바운드/아웃바운드, VPC 엔드포인트, NAT
- 노드가 EKS API 서버에 붙으려면(프라이빗 엔드포인트 구성 포함) 필요한 경로가 열려 있어야 합니다.
- 특히 프라이빗 서브넷에서 NAT/엔드포인트 구성이 꼬이면 부팅은 됐는데 조인 실패가 발생합니다.
(3) IP 부족(서브넷 CIDR/ENI)
- 서브넷 IP가 부족하면 EC2 생성 자체가 실패하거나, CNI가 Pod IP를 못 줘서 운영이 불안정해집니다.
노드가 NotReady로 보이거나 조인 직후 불안정하면, 노드 측 로그 수집이 핵심입니다. Bottlerocket을 쓰는 경우 EKS Bottlerocket 노드 NotReady일 때 로그 수집법처럼 “노드 안으로 못 들어가도” 로그를 모으는 루트를 준비해두면 진단 시간이 크게 줄어듭니다.
6) 3분: AWS 용량/쿼터/스팟 중단/인스턴스 타입 이슈
Karpenter는 조건을 만족하는 인스턴스를 찾지만, AWS가 그 순간 제공을 못 하면 실패합니다.
흔한 원인
- 특정 AZ에서 특정 인스턴스 패밀리 용량 부족
- Spot만 허용했는데 Spot 재고가 없음
- vCPU On-Demand/Spot 계정 쿼터 부족
- Launch Template/AMI/아키텍처 불일치(예: arm64만 가능한데 amd64 이미지)
빠른 완화책(진단 겸)
- NodePool requirements를 잠깐 완화해 범위를 넓혀보기
- instance-family/size를 더 허용
capacity-type에 on-demand를 임시로 추가- zone 제한을 제거(멀티 AZ)
예시(NodePool requirements 완화 아이디어):
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m6i.large", "m6i.xlarge", "c6i.large", "c6i.xlarge"]
이렇게 바꿨는데 바로 노드가 뜬다면, 원인은 거의 항상 용량/제약 조건입니다. 이후 다시 비용 최적화 조건으로 좁히되, “최소 23개 패밀리 + 23개 사이즈 + 멀티 AZ” 정도는 안전장치로 남기는 편이 운영에 유리합니다.
7) 2분: 실제로는 노드가 늘었는데도 Pod가 계속 Pending인 케이스
노드는 늘었지만 특정 워크로드만 안 올라가는 경우도 많습니다.
- PDB/리소스 요청 과다/anti-affinity로 배치 불가
- DaemonSet 오버헤드 때문에 실제 가용 리소스가 부족
- 특정 노드에만 필요한 CSI/디바이스 플러그인 미설치
이때는 새로 생성된 노드에 대해:
kubectl get nodes -o wide
kubectl describe node <new-node>
kubectl top node <new-node> # metrics-server 필요
그리고 Pending Pod 이벤트가 여전히 Insufficient cpu라면, Pod request가 너무 크거나(예: cpu 8, mem 32Gi) 선택 가능한 인스턴스가 그 스펙을 만족하지 못하는 것입니다. NodePool에서 더 큰 타입을 허용하거나, 워크로드 request/limit을 재조정해야 합니다.
10분 요약 체크리스트
1) Pod가 진짜 “unschedulable”인가?
kubectl describe pod이벤트에서Insufficient .../didn't match ...확인
2) Karpenter 컨트롤러는 살아 있고 에러가 없는가?
kubectl -n karpenter logs에서 AccessDenied/웹훅/타임아웃 확인
3) NodePool/EC2NodeClass가 Pod 요구사항을 충족 가능한가?
- requirements/taints/tolerations/affinity 충돌 제거
4) NodeClaim이 만들어지는가? 만들어졌다면 어디서 멈추나?
kubectl describe nodeclaim이벤트로 Launch 실패/조인 실패 분기
5) AWS 쿼터/용량/Spot 재고 문제인가?
- 인스턴스 타입/용량 타입/zone 제약을 임시 완화해 재현
부록: 현장에서 바로 쓰는 커맨드 묶음
# 1) Pending Pod 원인
kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod> | sed -n '/Events:/,$p'
# 2) Karpenter 상태/로그
kubectl -n karpenter get deploy,po
kubectl -n karpenter logs deploy/karpenter -c controller --tail=200
# 3) NodePool/NodeClass/NodeClaim
kubectl get nodepool
kubectl describe nodepool <name>
kubectl get ec2nodeclass
kubectl describe ec2nodeclass <name>
kubectl get nodeclaim
kubectl describe nodeclaim <name>
# 4) 최근 이벤트 훑기
kubectl get events -A --sort-by=.lastTimestamp | tail -n 80
Karpenter 노드 증설 문제는 대부분 “Pod 요구사항 ↔ NodePool 제약 ↔ AWS 현실(용량/권한/네트워크)”의 교집합이 비어 있을 때 발생합니다. 위 순서대로 보면 10분 안에 어느 구간에서 교집합이 깨졌는지가 드러나고, 그 순간부터는 설정을 완화할지(운영 우선), 구조를 바꿀지(비용/정책 우선) 판단만 남습니다.