- Published on
Karpenter 실전 가이드 — Kubernetes 노드 오토스케일링의 새로운 패러다임
- Authors
- Name
- 들어가며
- Cluster Autoscaler vs Karpenter
- Karpenter 아키텍처 이해
- 설치 및 구성
- 실전 NodePool 패턴
- 통합(Consolidation) 전략
- Spot 인터럽션 처리
- 모니터링
- CA에서 Karpenter 마이그레이션
- 비용 최적화 팁
- 트러블슈팅
- 퀴즈
- 마무리
- 참고 자료

들어가며
Kubernetes 클러스터에서 노드 오토스케일링은 비용 최적화와 서비스 안정성의 핵심입니다. 오랫동안 **Cluster Autoscaler(CA)**가 표준이었지만, AWS가 개발한 Karpenter는 근본적으로 다른 접근 방식으로 노드 프로비저닝을 혁신했습니다.
2025년 Salesforce는 1,000개 이상의 EKS 클러스터를 Cluster Autoscaler에서 Karpenter로 마이그레이션했고, 이는 Karpenter의 성숙도를 보여주는 대표적 사례입니다.
Cluster Autoscaler vs Karpenter
Cluster Autoscaler의 한계
# Cluster Autoscaler는 Node Group 단위로만 스케일링
# 미리 정의된 인스턴스 타입에 종속
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
managedNodeGroups:
- name: general
instanceType: m5.xlarge # 고정된 인스턴스 타입
minSize: 2
maxSize: 10
desiredCapacity: 3
CA의 주요 한계:
- Node Group 종속: 미리 정의된 ASG/Node Group에서만 스케일링
- 느린 반응: 스케줄링 실패 → CA 감지 → ASG 업데이트 → EC2 프로비저닝 (수 분 소요)
- 비효율적 Bin-packing: 노드 그룹 단위 스케일링으로 리소스 낭비
- 수동 인스턴스 선택: 워크로드에 최적인 인스턴스를 수동으로 설정
Karpenter의 접근 방식
# Karpenter는 Pod 요구사항에 맞는 최적 인스턴스를 자동 선택
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ['amd64', 'arm64']
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand', 'spot']
- key: karpenter.k8s.aws/instance-category
operator: In
values: ['c', 'm', 'r']
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ['4']
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: '1000'
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
Karpenter의 핵심 장점:
| 특성 | Cluster Autoscaler | Karpenter |
|---|---|---|
| 스케일링 단위 | Node Group (ASG) | 개별 Pod 기반 |
| 인스턴스 선택 | 수동 설정 | 자동 최적화 |
| 프로비저닝 속도 | 수 분 | 수십 초 |
| Bin-packing | 제한적 | 최적화 |
| Spot 처리 | 별도 설정 필요 | 네이티브 지원 |
| 통합(Consolidation) | 미지원 | 네이티브 지원 |
Karpenter 아키텍처 이해
핵심 리소스
Karpenter v1(GA)은 두 가지 핵심 CRD를 사용합니다:
1. NodePool — 노드 프로비저닝 정책 정의
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu-workloads
spec:
template:
metadata:
labels:
workload-type: gpu
spec:
requirements:
- key: karpenter.k8s.aws/instance-family
operator: In
values: ['p4d', 'p5', 'g5']
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand']
taints:
- key: nvidia.com/gpu
effect: NoSchedule
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: gpu-nodes
limits:
cpu: '100'
nvidia.com/gpu: '16'
weight: 10 # 우선순위 (높을수록 먼저 시도)
2. EC2NodeClass — AWS 인프라 설정
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
role: 'KarpenterNodeRole-my-cluster'
amiSelectorTerms:
- alias: al2023@latest
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: 'my-cluster'
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: 'my-cluster'
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 100Gi
volumeType: gp3
iops: 3000
throughput: 125
encrypted: true
userData: |
#!/bin/bash
echo "Custom bootstrap script"
프로비저닝 플로우
Pod 생성 (Pending)
↓
Karpenter Controller 감지
↓
Pod 요구사항 분석
(CPU, Memory, GPU, Topology, Affinity)
↓
최적 인스턴스 타입 계산
(가격, 가용성, Bin-packing 최적화)
↓
EC2 인스턴스 직접 생성
(ASG 없이 EC2 Fleet API 사용)
↓
NodeClaim 생성 → Node 등록
↓
Pod 스케줄링
설치 및 구성
Helm으로 설치
# Karpenter v1.1.x 설치 (2026년 최신)
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--version "1.1.2" \
--namespace kube-system \
--set "settings.clusterName=my-cluster" \
--set "settings.interruptionQueue=my-cluster" \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--set controller.resources.limits.cpu=1 \
--set controller.resources.limits.memory=1Gi \
--wait
IAM 구성
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "KarpenterController",
"Effect": "Allow",
"Action": [
"ec2:CreateFleet",
"ec2:CreateLaunchTemplate",
"ec2:CreateTags",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeImages",
"ec2:DescribeInstances",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeInstanceTypes",
"ec2:DescribeLaunchTemplates",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DeleteLaunchTemplate",
"ec2:RunInstances",
"ec2:TerminateInstances",
"iam:PassRole",
"ssm:GetParameter",
"pricing:GetProducts"
],
"Resource": "*"
},
{
"Sid": "KarpenterInterruption",
"Effect": "Allow",
"Action": ["sqs:DeleteMessage", "sqs:GetQueueUrl", "sqs:ReceiveMessage"],
"Resource": "arn:aws:sqs:*:*:my-cluster"
}
]
}
실전 NodePool 패턴
패턴 1: 범용 워크로드
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ['amd64']
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand', 'spot']
- key: karpenter.k8s.aws/instance-category
operator: In
values: ['c', 'm', 'r']
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ['5']
- key: karpenter.k8s.aws/instance-size
operator: NotIn
values: ['metal', '24xlarge', '16xlarge']
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: '500'
memory: 2000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
패턴 2: Spot 우선 + On-Demand 폴백
# Spot 전용 NodePool (높은 우선순위)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-first
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ['spot']
- key: karpenter.k8s.aws/instance-category
operator: In
values: ['c', 'm', 'r']
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
weight: 80 # 높은 우선순위
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 15s
---
# On-Demand 폴백 NodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: on-demand-fallback
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand']
- key: karpenter.k8s.aws/instance-category
operator: In
values: ['c', 'm']
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
weight: 20 # 낮은 우선순위 (Spot 실패 시 사용)
패턴 3: ARM64 Graviton 활용
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: graviton
spec:
template:
metadata:
labels:
arch: arm64
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ['arm64']
- key: karpenter.k8s.aws/instance-family
operator: In
values: ['c7g', 'm7g', 'r7g', 'c8g', 'm8g']
- key: karpenter.sh/capacity-type
operator: In
values: ['on-demand', 'spot']
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: graviton
weight: 90 # Graviton 우선
통합(Consolidation) 전략
Karpenter의 가장 강력한 기능 중 하나는 자동 통합입니다.
통합 정책
disruption:
# WhenEmpty: 빈 노드만 제거
# WhenEmptyOrUnderutilized: 빈 노드 + 저활용 노드 통합
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
# 특정 시간에만 통합 허용
budgets:
- nodes: '10%' # 한 번에 최대 10% 노드만 통합
- nodes: '0'
schedule: '0 9 * * 1-5' # 평일 9시에는 통합 중지
duration: 8h # 8시간 동안
통합 동작 방식
현재 상태:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Node A │ │ Node B │ │ Node C │
│ CPU: 80% │ │ CPU: 20% │ │ CPU: 15% │
│ m5.2xl │ │ m5.2xl │ │ m5.2xl │
└──────────┘ └──────────┘ └──────────┘
통합 후:
┌──────────┐ ┌──────────┐
│ Node A │ │ Node D │ Node B, C 제거
│ CPU: 80% │ │ CPU: 35% │ → 더 작은 인스턴스로 교체
│ m5.2xl │ │ m5.xl │
└──────────┘ └──────────┘
Do-Not-Disrupt 어노테이션
# 중요 워크로드가 있는 Pod에 적용
apiVersion: v1
kind: Pod
metadata:
annotations:
karpenter.sh/do-not-disrupt: 'true'
spec:
containers:
- name: critical-job
image: batch-processor:latest
Spot 인터럽션 처리
# SQS 큐를 통한 Spot 인터럽션 알림
# Karpenter가 자동으로 처리:
# 1. Spot 인터럽션 알림 수신 (2분 전)
# 2. 해당 노드의 Pod를 Cordon + Drain
# 3. 새로운 노드 프로비저닝
# 4. Pod 재스케줄링
# EventBridge Rule (CloudFormation)
AWSTemplateFormatVersion: '2010-09-09'
Resources:
SpotInterruptionRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source: ['aws.ec2']
detail-type: ['EC2 Spot Instance Interruption Warning']
Targets:
- Arn: !GetAtt InterruptionQueue.Arn
Id: SpotInterruptionTarget
RebalanceRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source: ['aws.ec2']
detail-type: ['EC2 Instance Rebalance Recommendation']
Targets:
- Arn: !GetAtt InterruptionQueue.Arn
Id: RebalanceTarget
InterruptionQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: my-cluster
MessageRetentionPeriod: 300
모니터링
Prometheus 메트릭
# ServiceMonitor for Karpenter
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: karpenter
namespace: kube-system
spec:
selector:
matchLabels:
app.kubernetes.io/name: karpenter
endpoints:
- port: http-metrics
interval: 30s
주요 메트릭:
# 프로비저닝된 노드 수
karpenter_nodeclaims_created_total
# 노드 프로비저닝 지연 시간
histogram_quantile(0.99, karpenter_nodeclaims_registered_duration_seconds_bucket)
# 통합으로 제거된 노드 수
karpenter_nodeclaims_disrupted_total{reason="consolidation"}
# Pending Pod 수 (Karpenter 대기)
karpenter_pods_state{state="pending"}
# 인스턴스 타입별 노드 분포
count by (instance_type) (karpenter_nodeclaims_instance_type)
CA에서 Karpenter 마이그레이션
단계별 전환
# 1단계: Karpenter 설치 (CA와 공존)
helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
--namespace kube-system \
--set "settings.clusterName=my-cluster"
# 2단계: NodePool 생성 (CA Node Group과 겹치지 않도록)
kubectl apply -f nodepool-general.yaml
# 3단계: 새 워크로드를 Karpenter로 유도
kubectl label nodes -l eks.amazonaws.com/nodegroup=old-ng \
migration-phase=legacy
# 4단계: 기존 워크로드 점진적 이전
# 5단계: CA Node Group 축소 및 제거
eksctl scale nodegroup --cluster my-cluster \
--name old-ng --nodes 0 --nodes-min 0
주의사항
# PodDisruptionBudget 확인
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: critical-app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: critical-app
# Karpenter는 PDB를 존중하므로
# 통합 시 PDB 위반 가능성이 있으면 건너뜀
비용 최적화 팁
1. 다양한 인스턴스 타입 허용
# 나쁜 예: 인스턴스 타입 제한
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values: ["m5.xlarge"] # Spot 가용성 낮음
# 좋은 예: 넓은 범위 허용
requirements:
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"]
2. 적절한 리소스 요청
# Pod의 리소스 요청이 정확할수록 Bin-packing 효율 상승
resources:
requests:
cpu: '500m'
memory: '512Mi'
limits:
cpu: '1000m'
memory: '1Gi'
3. Topology Spread 활용
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
트러블슈팅
자주 발생하는 문제
# NodeClaim이 생성되지 않을 때
kubectl describe nodepool default
kubectl get events --field-selector reason=FailedProvisioning
# 인스턴스 용량 부족 (ICE)
# → 더 많은 인스턴스 타입 허용
# → 더 많은 AZ 허용
# 느린 프로비저닝
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter \
--tail=100 | grep -i "provision"
# AMI 문제
kubectl get ec2nodeclass default -o yaml | grep -A5 amiSelector
퀴즈
Q1. Karpenter가 Cluster Autoscaler와 근본적으로 다른 점은 무엇인가요?
Karpenter는 Node Group(ASG) 없이 Pod의 요구사항을 직접 분석하여 최적의 EC2 인스턴스를 프로비저닝합니다. CA는 미리 정의된 Node Group에서만 스케일링합니다.
Q2. Karpenter v1에서 사용하는 두 가지 핵심 CRD는?
NodePool (노드 프로비저닝 정책)과 EC2NodeClass (AWS 인프라 설정)입니다.
Q3. consolidationPolicy의 WhenEmptyOrUnderutilized은 어떤 동작을 하나요?
빈 노드를 제거하고, 저활용(underutilized) 노드의 Pod를 다른 노드로 이동시킨 후 해당 노드를 제거하거나 더 작은 인스턴스로 교체합니다.
Q4. Spot 인터럽션을 Karpenter가 처리하려면 어떤 AWS 서비스가 필요한가요?
SQS 큐와 EventBridge Rule이 필요합니다. EC2 Spot 인터럽션 경고와 리밸런싱 권장 이벤트를 SQS로 전달하면 Karpenter가 자동 처리합니다.
Q5. karpenter.sh/do-not-disrupt 어노테이션의 용도는?
해당 Pod가 실행 중인 노드를 Karpenter의 통합(Consolidation)이나 만료(Expiration)로 인한 중단에서 보호합니다. 배치 작업이나 중요 워크로드에 사용합니다.
Q6. NodePool의 weight 필드는 어떤 역할을 하나요?
여러 NodePool이 있을 때 우선순위를 결정합니다. weight 값이 높을수록 먼저 시도됩니다. 예: Spot NodePool(weight: 80)을 On-Demand(weight: 20)보다 먼저 사용합니다.
Q7. Karpenter에서 Graviton(ARM64) 인스턴스를 활용하려면 어떻게 설정하나요?
NodePool의 requirements에서 kubernetes.io/arch: arm64와 Graviton 인스턴스 패밀리(c7g, m7g 등)를 지정합니다. 애플리케이션이 ARM64를 지원해야 합니다.
마무리
Karpenter는 Kubernetes 노드 오토스케일링의 패러다임을 바꾸고 있습니다. Node Group이라는 중간 계층을 제거하고, Pod 요구사항에 직접 반응하여 최적의 인스턴스를 프로비저닝합니다. 2026년 현재 v1 GA가 안정적으로 운영되고 있으며, Salesforce의 1,000+ 클러스터 마이그레이션 사례가 증명하듯 대규모 프로덕션 환경에서도 검증되었습니다.