Published on

EKS에서 Karpenter 노드가 안 늘 때 10분 진단

Authors

서버리스처럼 자동으로 노드가 늘어날 거라 믿고 배포했는데, 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/memory
  • pod has unbound immediate PersistentVolumeClaims (이건 스토리지 문제일 가능성이 큼)
  • node(s) didn't match Pod's node affinity/selector
  • node(s) had taint ... that the pod didn't tolerate

중요 포인트

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/IRSA
  • failed 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는 크게 두 종류 권한이 필요합니다.

  1. 컨트롤러(karpenter) 권한: EC2 RunInstances, CreateTags, Describe*, Pricing(옵션), SQS(인터럽션), IAM PassRole 등
  2. 노드(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분 안에 어느 구간에서 교집합이 깨졌는지가 드러나고, 그 순간부터는 설정을 완화할지(운영 우선), 구조를 바꿀지(비용/정책 우선) 판단만 남습니다.