Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Karpenter의 동작 원리

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

img

Karpenter의 핵심 구성 요소

  1. Provisioner (NodePool)
    • Karpenter가 어떤 노드를 생성해야 하는지 결정하는 정책을 정의하는 부분이다.
    • 클러스터의 워크로드 요구 사항을 기반으로 최적의 EC2 인스턴스를 선택하여 생성한다.
    • 특정 AZ에서 온디맨드 노드만 사용하거나 특정 인스턴스 유형만 사용하도록 제한할 수 있다.
  2. Controller
    • 쿠버네티스의 API 서버를 모니터링하며, 새로운 노드가 필요하거나 기존 노드를 정리해야 하는 상황을 감지한다.
    • NodeClaim을 생성하여 EC2 인스턴스를 요청하고 노드가 필요 없어지면 정리한다.
    • 파드의 스케줄링 요청을 실시간으로 감지하여 새로운 노드를 동적으로 생성한다.
  3. 웹훅
  • 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

스크린샷 2025-03-08 오전 12.41.14.png

생성된 워커 노드

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

스크린샷 2025-03-08 오전 1.19.32.png

스케일 업 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