Karpenter 완벽 가이드 — EKS 노드 오토스케일링, 비용 절감, 스팟 전략
이 글의 핵심
Karpenter는 AWS가 개발한 Kubernetes 노드 오토스케일러로, 기존 Cluster Autoscaler가 노드 그룹(ASG) 단위에서만 작동한다는 한계를 극복합니다. Pending Pod의 요구사항을 직접 분석해 EC2 인스턴스를 맞춤 프로비저닝하고, 스팟+온디맨드 혼합·빈 노드 자동 청소로 비용을 30-70% 줄이는 것이 흔합니다. 이 글은 설치·NodePool/NodeClass·Disruption·프로덕션 운영까지 실전으로 정리합니다.
이 글의 핵심
Karpenter는 AWS가 2021년 공개하고 2023년 v1.0 GA, 2026년 현재 v1.1/1.2 기준으로 EKS의 사실상 표준 노드 오토스케일러입니다. Kubernetes SIG의 Cluster Autoscaler가 가진 다음 한계를 해결합니다.
- ASG 단위로만 스케일 → 인스턴스 타입 고정
- Pending Pod의 실제 요구사항을 세밀히 반영하지 못함
- 프로비저닝 지연 1-3분
- 스팟+온디맨드 혼합 전략이 번거로움
- 빈 노드 정리가 느림
Karpenter는:
- ASG 없이 EC2 Fleet API 직접 호출
- Pending Pod의 CPU·RAM·GPU·아키텍처 요구를 정확히 반영해 인스턴스 선택
- 40초 이내 프로비저닝
- Consolidation으로 빈·저활용 노드를 자동 청소해 비용 절감
- 스팟 + 온디맨드 + CPU 아키텍처(amd64/arm64) 혼합을 NodePool 레벨에서 표현
이 글은 설치·설정·운영·비용 최적화를 실전 순서대로 다룹니다.
아키텍처
┌──────────────────────────────────────────────────────┐
│ Kubernetes API Server │
│ │
│ Pending Pod ── watch ──► Karpenter Controller │
│ │ │
│ ▼ │
│ NodePool / EC2NodeClass │
│ │ │
└──────────────────────────────────────┼────────────────┘
│
▼
AWS EC2 Fleet API
│
▼
┌─────────────────────────────────┐
│ EC2 인스턴스 (스팟/온디맨드) │
│ kubelet → Kubernetes 자동 가입 │
└─────────────────────────────────┘
설치
전제 조건
- EKS 클러스터 (관리 노드 그룹 최소 1개)
- IAM OIDC provider 연결
aws-load-balancer-controller·cert-manager·metrics-server등 표준 애드온
Helm 설치 (v1.x)
export CLUSTER_NAME=prod-eks
export AWS_REGION=ap-northeast-2
export KARPENTER_NAMESPACE=kube-system
export KARPENTER_VERSION=1.1.0
# IAM 역할·SQS 큐·EventBridge 규칙 생성 (공식 CloudFormation)
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v${KARPENTER_VERSION}/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml > karpenter.yaml
aws cloudformation deploy \
--stack-name Karpenter-${CLUSTER_NAME} \
--template-file karpenter.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides "ClusterName=${CLUSTER_NAME}"
# Karpenter 컨트롤러 설치
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version ${KARPENTER_VERSION} \
--namespace ${KARPENTER_NAMESPACE} --create-namespace \
--set "settings.clusterName=${CLUSTER_NAME}" \
--set "settings.interruptionQueue=${CLUSTER_NAME}" \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--wait
Karpenter 컨트롤러는 2 replicas로 HA 구성을 권장합니다.
NodeClass와 NodePool
v1부터 두 리소스로 분리되었습니다.
- EC2NodeClass: “어떤 AWS 리소스로 노드를 만들까” — AMI·서브넷·보안그룹·IAM·디스크·UserData
- NodePool: “어떤 Pod를 어떤 요구사항의 노드에 배치할까” — taint·requirements·limit·Disruption
EC2NodeClass
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2023
amiSelectorTerms:
- alias: al2023@latest
role: "KarpenterNodeRole-prod-eks"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "prod-eks"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "prod-eks"
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 100Gi
volumeType: gp3
iops: 3000
encrypted: true
deleteOnTermination: true
tags:
Environment: production
ManagedBy: karpenter
NodePool (기본 워크로드)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64", "arm64"]
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["c", "m", "r"]
- key: "karpenter.k8s.aws/instance-generation"
operator: Gt
values: ["5"]
expireAfter: 720h # 30일마다 롤링 교체 (보안·AMI 갱신)
limits:
cpu: "2000"
memory: "4000Gi"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
weight: 10
capacity-type: 스팟 먼저 시도, 실패 시 온디맨드 fallbackinstance-category: c(compute), m(general), r(memory) 세대 이상consolidationPolicy: 빈·저활용 노드 자동 통합weight: 여러 NodePool 간 선호도
상태 저장 워크로드용 NodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: stateful
spec:
template:
metadata:
labels:
workload: stateful
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
taints:
- key: workload
value: stateful
effect: NoSchedule
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand"] # 온디맨드만
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["m6i.xlarge", "m6i.2xlarge", "m6i.4xlarge"]
limits:
cpu: "200"
disruption:
consolidationPolicy: WhenEmpty # 저활용으로는 통합 안 함 (state 이동 위험)
consolidateAfter: 1h
weight: 20
Postgres·Redis 같은 stateful 워크로드는 taints + toleration으로 분리하고 온디맨드만 사용해 안정성을 확보합니다.
Pod에서 활용
# 무상태 웹 (스팟 허용)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 10
template:
spec:
containers:
- name: app
resources:
requests: {cpu: 500m, memory: 512Mi}
limits: {cpu: 1, memory: 1Gi}
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: {app: web}
PodDisruptionBudget 필수:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: web}
spec:
minAvailable: 80%
selector:
matchLabels: {app: web}
Disruption: 비용 절감의 핵심
Karpenter의 Consolidation은 “같은 워크로드를 더 적은/저렴한 노드로 재배치할 수 있다면 그렇게 한다”는 원리로 동작합니다.
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
budgets:
- nodes: "20%" # 동시에 최대 20%까지 Disrupt
- nodes: "0" # 업무 시간엔 Disrupt 금지
schedule: "0 9 * * mon-fri"
duration: 8h
Consolidation 유형:
- Empty: 비어있는 노드 제거
- Replace: 여러 노드의 Pod를 더 큰 단일 노드로 통합
- Delete: 크기가 과한 노드를 더 작은 노드로 교체
드리프트
NodeClass나 NodePool 스펙이 변경되면 기존 노드는 “drift”로 표시되고 점진적으로 새 스펙에 맞게 교체됩니다.
스팟 운영 베스트 프랙티스
- 다양한 인스턴스 타입 허용: 5종 이상 지정해 interruption 위험 분산
- 여러 AZ 허용: 하나의 AZ 스팟 풀 고갈 대비
- PDB 필수: 동시에 많은 노드가 사라지는 것 방지
- graceful termination: Pod
terminationGracePeriodSeconds: 30-120 - stateful/critical 은 분리 NodePool: 온디맨드만 사용
- interruption handler: Karpenter 자체가 처리하지만 애플리케이션도 SIGTERM 대응
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 20"] # drain 동안 연결 처리
비용 최적화 체크리스트
- NodePool에 다양한 인스턴스 타입(c/m/r + 여러 세대) 허용
- arm64(Graviton) 지원하는 워크로드는 활성화해 30% 가격 이점
-
consolidationPolicy: WhenEmptyOrUnderutilized+consolidateAfter: 30s - 주말·야간에 더 공격적인 consolidation 스케줄
- 무상태 워크로드는 스팟 우선
- Reserved Instance / Savings Plans와 혼합하여 “기본 부하는 RI, 스케일업은 스팟”
-
expireAfter로 주기적 롤링 (AMI 갱신, 보안 패치, drift 청소) -
limits로 예산 상한 설정 — 장애/버그로 Pod가 폭증해도 예산 초과 방지 - CloudWatch 비용 경보 + Karpenter 노드 태그로 비용 대시보드
관측: 반드시 확인할 메트릭
Karpenter는 Prometheus 메트릭을 :8000/metrics로 노출합니다.
| 메트릭 | 의미 |
|---|---|
karpenter_nodes_created | 분당 생성된 노드 수 |
karpenter_nodes_terminated | 분당 종료된 노드 수 |
karpenter_pods_startup_time_seconds | Pending→Running 시간 (p95 목표 60초 이하) |
karpenter_disruption_actions_performed_total | Disruption 실행 횟수·종류 |
karpenter_cloudprovider_instance_type_price_estimate | 현재 인스턴스 타입별 예상 가격 |
Grafana 공식 대시보드가 제공되어 바로 import 가능합니다.
보안
- Karpenter IAM 역할은 EC2 생성·삭제·태깅 권한만 최소화
iam:PassRole대상 역할은 노드가 실제로 써야 하는 IAM instance profile- 노드 OS 이미지는
AL2023최신 alias 사용 → 최신 보안 패치 자동 적용 - 민감 워크로드는 전용 NodePool + taint로 네트워크·호스트 분리
securityGroupSelectorTerms로 워크로드별 SG 분리- Pod Security Standard
restricted적용
트러블슈팅
Pending Pod가 노드를 못 받음
kubectl describe pod <pod> # Event 확인
kubectl logs -n kube-system deploy/karpenter -c controller --tail 200
일반 원인:
- NodePool
requirements에 PodnodeSelector가 맞지 않음 limits(cpu/memory) 도달- 서브넷/SG selector가 리소스를 못 찾음
- Pod의
nodeAffinity가 너무 엄격
새 노드가 Ready 상태로 못 넘어감
- IAM 권한 (특히
ec2:RunInstances,ec2:CreateTags) - aws-auth ConfigMap에 Karpenter 노드 역할 매핑 필수
- Bottlerocket/AL2023에서 UserData 충돌
스팟 interruption이 자주 발생
- 인스턴스 타입을 더 다양화 (5종 이상)
- 여러 AZ 허용
- AWS Spot Advisor로 interruption 빈도가 낮은 타입 우선 선택
Consolidation이 발생하지 않음
- 모든 Pod에 PDB가 있고
maxUnavailable: 0으로 설정되면 차단됨 do-not-disrupt어노테이션이 붙은 Pod는 보호됨 (고의/실수 확인)
실전 시나리오
시나리오 1: CI 빌드 버스트
- 아침 9시 일제히 빌드 Pending → Karpenter가 40초 내 20+ 스팟 노드 생성
- 빌드 완료 후 30초 뒤 consolidation으로 모두 종료
- 월 CI 비용 50% 이상 절감 사례 흔함
시나리오 2: 프로모션 트래픽
- HPA가 replicas를 10→200으로 증가
- Karpenter가 필요 용량 계산해 m6i.4xlarge 5대를 한 번에 fleet으로 생성
- 기존 Cluster Autoscaler 대비 프로비저닝 시간 3분 → 40초
시나리오 3: ML 추론
- GPU Pod Pending →
nvidia.com/gpu: 1요청 감지 → g5.xlarge 자동 선택 - NVIDIA Device Plugin이 자동으로 GPU 리소스 등록
v1 vs v0.x 주요 차이
Provisioner→NodePoolAWSNodeTemplate→EC2NodeClassttlSecondsAfterEmpty→disruption.consolidateAfterttlSecondsUntilExpired→disruption.expireAfter→spec.template.spec.expireAfter
v0.x 사용 중이라면 공식 마이그레이션 가이드를 따라 v1.x로 업그레이드하세요.
마무리
Karpenter는 EKS의 비용·속도·유연성을 동시에 개선하는 도구로, 도입 후 첫 달 안에 노드 비용 30% 이상 절감이 흔한 결과입니다. Cluster Autoscaler의 ASG 종속성에서 벗어나 “Pod 요구사항에 가장 잘 맞는 인스턴스를 필요한 만큼만 띄운다”는 원칙이 쿠버네티스 스케일링의 미래 방향을 보여줍니다. 프로덕션 도입 전 스테이징에서 NodePool·스팟 정책·PDB를 검증하고, 단계적으로 CA를 대체해 나가세요.
관련 글
- Kubernetes 완벽 가이드
- AWS EKS 완벽 가이드
- AWS 스팟 인스턴스 운영 가이드
- Kubernetes HPA/VPA 오토스케일링 가이드