Karpenter의 동작 원리
Karpenter는 쿠버네티스의 동적 노드 자동 확장(Autoscaling) 솔루션이다. 쿠버네티스 클러스터에서 사용자의 요구사항에 맞게 EC2 인스턴스를 자동으로 추가 및 제거하는 역할을 하여 쿠버네티스의 Cluster Autoscaler보다 더 빠르고 효율적으로 노드를 관리하는 도구이다.

Karpenter의 핵심 구성 요소
- Provisioner (NodePool)
- Karpenter가 어떤 노드를 생성해야 하는지 결정하는 정책을 정의하는 부분이다.
- 클러스터의 워크로드 요구 사항을 기반으로 최적의 EC2 인스턴스를 선택하여 생성한다.
- 특정 AZ에서 온디맨드 노드만 사용하거나 특정 인스턴스 유형만 사용하도록 제한할 수 있다.
- Controller
- 쿠버네티스의 API 서버를 모니터링하며, 새로운 노드가 필요하거나 기존 노드를 정리해야 하는 상황을 감지한다.
- NodeClaim을 생성하여 EC2 인스턴스를 요청하고 노드가 필요 없어지면 정리한다.
- 파드의 스케줄링 요청을 실시간으로 감지하여 새로운 노드를 동적으로 생성한다.
- 웹훅
- API 서버와 통신하면서, Karpenter가 생성하는 리소스를 검증하고 조정한다.
- MutatingWebhook과 ValidatingWebhook을 사용하여 NodePool과 NodeClaim을 관리한다.
- 잘못된 요청이 들어오지 않도록 필터링하는 역할을 한다.
스케일업 test
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: kubernetes.io/os
operator: In
values: ["linux"]
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: 720h # 30 * 24h = 720h
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
role: "KarpenterNodeRole-${CLUSTER_NAME}" # replace with your cluster name
amiSelectorTerms:
- alias: "al2023@${ALIAS_VERSION}" # ex) al2023@latest
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
EOF
kubectl get nodepool,ec2nodeclass,nodeclaims
NAME NODECLASS NODES READY AGE
nodepool.karpenter.sh/default default 0 True 4m3s
NAME READY AGE
ec2nodeclass.karpenter.k8s.aws/default True 4m3s

생성된 워커 노드
kubectl get nodeclaims
NAME TYPE CAPACITY ZONE NODE READY AGE
default-edcede c5a.2xlarge on-demand ap-northeast-2b ip-192-168-105-117.ap-northeast-2.compute.internal True 8m44s
카펜터가 api 요청을 하는 부분을 살펴보면 lawest-price를 선택해서 워커 노드를 생성하게 된다.
{
"level": "INFO",
"time": "2025-03-07T14:43:55.112Z",
"logger": "controller.controller-runtime.metrics",
"message": "Starting metrics server",
"commit": "058c665"
}
{
"level": "INFO",
"time": "2025-03-07T14:43:55.112Z",
"logger": "controller.controller-runtime.metrics",
"message": "Serving metrics server",
"commit": "058c665",
"bindAddress": ":8080",
"secure": false
}
Karpenter 컨트롤러가 시작되면서 메트릭 서버가 시작된다.
{
"level": "INFO",
"time": "2025-03-07T15:40:15.226Z",
"logger": "controller",
"message": "launched nodeclaim",
"commit": "058c665",
"controller": "nodeclaim.lifecycle",
"controllerGroup": "karpenter.sh",
"controllerKind": "NodeClaim",
"NodeClaim": {
"name": "default-hw4wj"
},
"namespace": "",
"name": "default-edcede",
"reconcileID": "bab16b84-9695-4e51-8e0c-481e7f9d804d",
"provider-id": "aws:///ap-northeast-2b/i-0335961f7c4185177",
"instance-type": "c5a.2xlarge",
"zone": "ap-northeast-2b",
"capacity-type": "on-demand",
"allocatable": {
"cpu": "7910m",
"ephemeral-storage": "17Gi",
"memory": "14162Mi",
"pods": "58",
"vpc.amazonaws.com/pod-eni": "38"
}
}
Karpenter가 새로운 노드(default-hw4wj)를 생성한다.
{
"level": "INFO",
"time": "2025-03-07T15:40:33.750Z",
"logger": "controller",
"message": "registered nodeclaim",
"commit": "058c665",
"controller": "nodeclaim.lifecycle",
"controllerGroup": "karpenter.sh",
"controllerKind": "NodeClaim",
"NodeClaim": {
"name": "default-edcede"
},
"namespace": "",
"name": "default-edcede",
"reconcileID": "6ff1af57-3e4f-4e67-b22c-957d23f60776",
"provider-id": "aws:///ap-northeast-2b/i-0335961f7c4185177",
"Node": {
"name": "ip-192-168-105-117.ap-northeast-2.compute.internal"
}
}
노드가 Kubernetes 클러스터에 등록된다.
{
"level": "INFO",
"time": "2025-03-07T15:40:43.686Z",
"logger": "controller",
"message": "initialized nodeclaim",
"commit": "058c665",
"controller": "nodeclaim.lifecycle",
"controllerGroup": "karpenter.sh",
"controllerKind": "NodeClaim",
"NodeClaim": {
"name": "default-edcede"
},
"namespace": "",
"name": "default-edcede",
"reconcileID": "6affb0d5-822c-460d-a82d-bc7c01b21be5",
"provider-id": "aws:///ap-northeast-2b/i-0335961f7c4185177",
"Node": {
"name": "ip-192-168-105-117.ap-northeast-2.compute.internal"
},
"allocatable": {
"cpu": "7910m",
"ephemeral-storage": "18181869946",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "15140112Ki",
"pods": "58"
}
}
노드가 정상적으로 초기화되고 (initialized nodeclaim), 할당 가능 리소스(allocatable)가 다시 표시됨으로써 리소스가 사용 가능한 준비 상태이다.
스케일 다운
kubectl scale deployment/inflate --replicas 1
레플리카를 1로 하여 스케일 다운을 해본다.
스케일 다운 시 한번에 워커 노드 감소 뿍뽝뽝하고 줄어드는 것이 아닌, 점진적으로 줄어드는 것을 확인할 수 있다.
{
"level": "INFO",
"time": "2025-03-07T15:57:32.564Z",
"logger": "controller",
"message": "disrupting nodeclaim(s) via replace, terminating 1 nodes (1 pods) ip-192-168-105-117.ap-northeast-2.compute.internal/c5a.2xlarge/on-demand and replacing with on-demand node from types c5a.large, c7i-flex.large, c5.large, c6i.large, c7i.large and 52 other(s)",
"reason": "underutilized"
}
Karpenter는 기존 c5a.2xlarge 노드가 사용률이 감소하고 있다(underutilized)라고 판단하고 해당 노드를 종료하고 더 작은 노드로 자동 리소스 최적화 기능을 통해 교체한다.
{
"level": "INFO",
"time": "2025-03-07T15:57:54.489Z",
"logger": "controller",
"message": "registered nodeclaim",
"NodeClaim": {
"name": "default-hwede"
},
"provider-id": "aws:///ap-northeast-2b/i-0666wef2b4wde3a7ad",
"Node": {
"name": "ip-192-168-2-126.ap-northeast-2.compute.internal"
}
}
더 작은 사이즈의 새로운 노드가 클러스터에 등록되는데 새로운 인스턴스는 ap-northeast-2b 가용 영역에 배치된다.
클러스터는 점진적으로 더 작은 인스턴스를 활용하도록 조정된다.
{
"level": "INFO",
"time": "2025-03-07T15:58:12.143Z",
"logger": "controller",
"message": "initialized nodeclaim",
"NodeClaim": {
"name": "default-rzhmm"
},
"allocatable": {
"cpu": "1930m",
"memory": "3229360Ki",
"pods": "29"
}
}
기존 큰 노드 대신 더 작은 노드가 사용되면서 자원을 최적화한다.
{
"level": "INFO",
"time": "2025-03-07T15:58:18.998Z",
"logger": "controller",
"message": "tainted node",
"Node": {
"name": "ip-192-168-105-117.ap-northeast-2.compute.internal"
},
"taint.Key": "karpenter.sh/disrupted",
"taint.Effect": "NoSchedule"
}
기존 노드 ip-192-168-105-117 에 NoSchedule 태인트가 적용되어, 이 노드에는 새로운 파드를 배치할 수 없도록한다.
{
"level": "INFO",
"time": "2025-03-07T15:59:02.087Z",
"logger": "controller",
"message": "deleted node",
"Node": {
"name": "ip-192-168-105-117.ap-northeast-2.compute.internal"
}
}
클러스터에서 더 이상 필요 없는 노드를 제거한다.
{
"level": "INFO",
"time": "2025-03-07T15:59:02.338Z",
"logger": "controller",
"message": "deleted nodeclaim",
"NodeClaim": {
"name": "default-hwede"
}
}
워커 노드 확인
kubectl get nodeclaims
NAME TYPE CAPACITY ZONE NODE READY AGE
default-hwede c5a.large on-demand ap-northeast-2b ip-192-168-2-126.ap-northeast-2.compute.internal True 8m1s
Spot-to-Spot Consolidation 실습
Karpenter를 이용해 AWS EC2 Spot 인스턴스를 자동으로 관리하는 방법을 테스트해본다.
Karpenter node pool, ec2 node class 생성
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"]
EC2 nodeclass 생성
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
role: "KarpenterNodeRole-${CLUSTER_NAME}"
amiSelectorTerms:
- alias: "bottlerocket@latest"
테스트 워크로드 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
spec:
replicas: 5
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
containers:
- name: inflate
image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
resources:
requests:
cpu: 1
memory: 1.5Gi

스케일 업 test
kubectl get nodeclaims
NAME TYPE CAPACITY ZONE NODE READY AGE
default-qwvbv c6g.2xlarge spot ap-northeast-2d ip-192-168-132-105.ap-northeast-2.compute.internal True 4m55s
default-qxbz6 c6g.2xlarge spot ap-northeast-2d ip-192-168-46-137.ap-northeast-2.compute.internal True 59s
스케일 다운 test
kubectl get nodeclaims
NAME TYPE CAPACITY ZONE NODE READY AGE
default-qxbz6 c6g.2xlarge spot ap-northeast-2d ip-192-168-46-137.ap-northeast-2.compute.internal True 4m43s