Published on

Karpenter 도입 후 EKS 노드가 안 늘 때 해결법

Authors

서버리스처럼 자동으로 노드를 늘려줄 것 같아 Karpenter를 도입했는데, 막상 부하가 올라가도 EKS 노드가 전혀 늘지 않고 Pod가 Pending에 머무르는 경우가 꽤 흔합니다. 이 문제는 대부분 “Karpenter가 스케일 아웃 결정을 못 내림” 또는 “결정은 했지만 AWS에서 인스턴스 생성이 실패함” 두 갈래로 나뉩니다.

이 글에서는 증상을 이벤트로 확정 → Karpenter 로그로 원인 분류 → IAM/네트워크/리소스/스펙 설정을 체크리스트로 해결하는 흐름으로 정리합니다.

관련해서 Pod Pending 자체의 일반 원인도 함께 확인하면 진단 속도가 빨라집니다: EKS Pod Pending 0/XX nodes available 원인별 해결


1) 먼저 “정말 Karpenter가 스케일해야 하는 상황”인지 확정

1-1. Pending Pod 이벤트 확인

Karpenter가 노드를 늘리려면 기본적으로 **스케줄러가 배치하지 못한 Pod(Pending)**가 있어야 합니다.

kubectl get pod -A --field-selector=status.phase=Pending
kubectl describe pod -n <ns> <pod>

Events에서 아래 같은 메시지가 핵심입니다.

  • 0/XX nodes are available: ... Insufficient cpu/memory → 스케일 아웃 후보
  • node(s) had taint {…}, that the pod didn't tolerate → 노드 늘려도 못 올라감(스펙/톨러레이션 문제)
  • pod has unbound immediate PersistentVolumeClaims → 스토리지 바인딩 문제(노드 스케일과 무관)

1-2. 리소스 요청(requests)이 없는 Pod는 “스케일 신호”가 약해질 수 있음

Karpenter는 스케줄링 불가 Pod의 요구사항을 기반으로 인스턴스를 선택합니다. resources.requests가 비어 있거나 너무 작으면, 실제로는 부족한데도 스케일 트리거가 애매해질 수 있습니다(특히 binpack/daemonset 오버헤드 포함 시).

resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "1"
    memory: "1Gi"

2) Karpenter 컨트롤러가 “스케일 결정을 내렸는지” 확인

2-1. Karpenter 로그에서 가장 먼저 볼 것

kubectl -n karpenter logs deploy/karpenter -c controller --tail=200

자주 나오는 패턴:

  • no subnets found / no security groups found → 태그/셀렉터 문제
  • AccessDenied / UnauthorizedOperation → IRSA/IAM 권한 문제
  • launch template / CreateFleet 실패 → EC2 용량/쿼터/AMI/인스턴스 타입 문제
  • cannot satisfy requirements → NodePool/NodeClass 요구조건이 너무 빡셈

2-2. NodePool/NodeClass 상태 확인

(Karpenter v1 계열 기준) 리소스 상태와 이벤트를 함께 봅니다.

kubectl get nodepool -A
kubectl describe nodepool <name>

kubectl get ec2nodeclass -A
kubectl describe ec2nodeclass <name>

Events에 이유가 거의 다 적힙니다. 예: Failed resolving subnets, Failed to discover AMI, Insufficient permissions.


3) 원인 1순위: IRSA/IAM 권한 문제로 인스턴스 생성이 막힘

Karpenter는 EC2, IAM, EKS, SSM(AMI 조회 방식에 따라), Pricing(옵션) 등 여러 API를 호출합니다. 권한이 하나라도 빠지면 스케일 결정을 해도 실제 노드 생성이 실패합니다.

3-1. 흔한 증상

  • 로그에 AccessDenied / sts:AssumeRoleWithWebIdentity 실패
  • CreateFleet / RunInstances / CreateLaunchTemplate 권한 부족

IRSA 자체가 꼬였을 가능성이 크면 아래 글의 체크리스트가 그대로 도움이 됩니다.

3-2. 서비스어카운트에 올바른 role-arn이 붙었는지

kubectl -n karpenter get sa karpenter -o yaml | yq '.metadata.annotations'

예상:

eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/KarpenterControllerRole-<CLUSTER>

3-3. ControllerRole에 필요한 권한이 실제로 있는지(핵심)

환경마다 정책은 달라질 수 있지만, 최소한 다음 계열이 막히면 노드가 안 늘어납니다.

  • ec2:CreateFleet, ec2:RunInstances, ec2:CreateLaunchTemplate*, ec2:Describe*, ec2:CreateTags
  • iam:PassRole (노드 인스턴스 프로파일/역할 전달)
  • ssm:GetParameter (AL2/AL2023 AMI를 SSM로 찾는 구성일 때)

권한이 맞는데도 실패한다면, 조건(Condition)으로 리소스 태그 제한을 걸어둔 조직 정책(SCP/Permission Boundary)도 같이 확인해야 합니다.


4) 원인 2순위: 서브넷/보안그룹 디스커버리(태그) 실패

Karpenter는 EC2NodeClass에서 서브넷/보안그룹을 태그 셀렉터로 찾는 구성이 일반적입니다. 여기서 태그가 안 맞으면 로그에 no subnets found가 뜨고, 노드 생성이 진행되지 않습니다.

4-1. EC2NodeClass 예시(태그 기반)

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2
  role: KarpenterNodeRole-my-eks
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-eks
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-eks
  tags:
    karpenter.sh/discovery: my-eks

4-2. 서브넷/SG에 discovery 태그가 실제로 있는지

aws ec2 describe-subnets \
  --filters "Name=tag:karpenter.sh/discovery,Values=my-eks" \
  --query 'Subnets[].SubnetId' --output text

aws ec2 describe-security-groups \
  --filters "Name=tag:karpenter.sh/discovery,Values=my-eks" \
  --query 'SecurityGroups[].GroupId' --output text

결과가 비어 있으면 100% 이슈입니다.

4-3. 퍼블릭/프라이빗 서브넷 라우팅도 함께 확인

  • 프라이빗 서브넷이면 NAT/프록시 없이 ECR/SSM 접근이 막혀 부팅 후 조인 실패
  • VPC 엔드포인트(EC2, ECR, SSM, STS 등) 구성이 없으면 제한망에서 실패 가능

이 경우 “노드가 생성은 됐는데 Ready가 안 됨” 형태로도 나타납니다.


5) 원인 3순위: NodePool 요구조건이 너무 엄격해서 “만족 가능한 인스턴스가 없음”

Karpenter는 NodePool의 requirements를 만족하는 인스턴스 타입/가용영역/아키텍처를 찾아야 합니다. 조건이 조금만 과해도 cannot satisfy requirements로 끝납니다.

5-1. 과한 requirements의 전형

  • 특정 AZ만 고정했는데 그 AZ에 용량이 없음
  • 인스턴스 패밀리/사이즈를 너무 제한함
  • capacity-type=spot만 허용했는데 스팟이 없음
  • 아키텍처를 arm64로 고정했는데 이미지가 amd64

5-2. 현실적인 NodePool 예시(온디맨드 + 범용)

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m6i.large", "m6i.xlarge", "c6i.large", "c6i.xlarge"]
  limits:
    cpu: "200"
  disruption:
    consolidationPolicy: WhenEmpty

디버깅 단계에서는 인스턴스 타입 목록을 넓히고, AZ 고정을 풀어 “일단 뜨게” 만든 뒤 점진적으로 조이는 게 빠릅니다.


6) 원인 4순위: limits/quotas 때문에 더 이상 못 늘어남

6-1. NodePool limits에 걸림

spec.limits.cpu 같은 제한을 걸어두면, 그 이상은 스케일 아웃이 멈춥니다.

kubectl describe nodepool general | sed -n '/Limits/,$p'

6-2. AWS 계정/리전 EC2 쿼터

특히 신규 계정/리전은 vCPU 한도가 낮아 CreateFleet이 실패합니다.

확인 포인트:

  • On-Demand vCPU limit
  • Spot vCPU limit
  • 특정 인스턴스 패밀리 제한
aws service-quotas list-service-quotas --service-code ec2 \
  --query 'Quotas[?contains(QuotaName, `vCPU`) == `true`].[QuotaName,Value]' \
  --output table

7) 원인 5순위: 노드는 만들어졌는데 클러스터에 조인(Ready)하지 못함

이 케이스는 “노드 수가 안 늘어”로 보이지만, 실제로는 EC2가 생성되었다가 부팅 후 조인 실패로 사라지거나 NotReady로 남습니다.

7-1. 빠른 확인

kubectl get nodes -owide
kubectl get events -A --sort-by=.lastTimestamp | tail -n 50

7-2. aws-auth / Access Entry 문제

Karpenter 노드 역할이 EKS에 인증되지 않으면 노드가 조인하지 못합니다.

  • aws-auth ConfigMap(구형 방식) 또는
  • EKS Access Entry(신형 방식)

을 사용 중인 클러스터 정책에 맞게 노드 역할을 등록해야 합니다.

7-3. CNI/kube-proxy/CoreDNS 등 애드온 장애

노드가 떠도 네트워킹 애드온이 깨져 있으면 Ready 전환이 늦거나 파드가 계속 Pending/CrashLoopBackOff로 이어질 수 있습니다. 특히 kube-proxy iptables 이슈는 노드/파드 통신을 망가뜨립니다.


8) 실전 트러블슈팅: “10분 안에 결론내는” 커맨드 루틴

아래 순서대로 보면 대부분 1~2회전 내에 원인이 좁혀집니다.

# 1) Pending 파드와 이벤트
kubectl get pod -A --field-selector=status.phase=Pending
kubectl describe pod -n <ns> <pod>

# 2) Karpenter 로그
kubectl -n karpenter logs deploy/karpenter -c controller --tail=300

# 3) NodePool/NodeClass 이벤트
kubectl describe nodepool <nodepool>
kubectl describe ec2nodeclass <nodeclass>

# 4) 디스커버리 태그로 서브넷/SG가 잡히는지
aws ec2 describe-subnets --filters "Name=tag:karpenter.sh/discovery,Values=<cluster>" --query 'Subnets[].SubnetId' --output text
aws ec2 describe-security-groups --filters "Name=tag:karpenter.sh/discovery,Values=<cluster>" --query 'SecurityGroups[].GroupId' --output text

# 5) IRSA/권한(AccessDenied 여부)
kubectl -n karpenter get sa karpenter -o yaml | yq '.metadata.annotations'
  • AccessDenied가 보이면: IRSA/정책/PassRole부터 해결
  • no subnets/security groups found면: 태그/셀렉터 수정
  • cannot satisfy requirements면: NodePool requirements 완화
  • vCPU limit/InsufficientInstanceCapacity면: 쿼터/인스턴스 타입/AZ 분산

9) 재발 방지 체크리스트(운영 관점)

9-1. 관측성

  • Karpenter controller 로그를 CloudWatch로 수집
  • Karpenter 이벤트(Provisioning 실패)를 알람으로 연결
  • EC2 Fleet/Spot 실패 지표 모니터링

9-2. 구성 안정성

  • NodePool requirements는 “최소 제약”부터 시작해 점진 강화
  • 서브넷/SG 태그는 IaC(Terraform/CDK)로 강제
  • 온디맨드 fallback(스팟만 쓰지 않기)

9-3. 장애 시 빠른 우회

  • 임시로 NodePool에 온디맨드 허용
  • 인스턴스 타입 풀 확장
  • limits 상향(또는 일시 해제)

마무리

Karpenter 도입 후 노드가 안 늘어나는 문제는 대부분 다음 5개 중 하나로 귀결됩니다.

  1. Pending Pod가 스케일 대상으로 성립하지 않음(톨러레이션/PVC 등)
  2. IRSA/IAM 권한 부족(특히 PassRole, CreateFleet)
  3. 서브넷/보안그룹 디스커버리 태그 실패
  4. NodePool requirements 과제약 또는 EC2 쿼터/용량 이슈
  5. 노드는 생성됐지만 조인 실패(aws-auth/AccessEntry, 네트워크)

위의 커맨드 루틴으로 이벤트와 로그를 먼저 수집하면, 감으로 설정을 바꾸는 시간을 크게 줄일 수 있습니다.