Cloud

EKS Karpenter

Hanhorang31 2025. 3. 9. 05:56
 

이번 글에서는 Karpenter를 설치하고 특징들을 실습하겠습니다.

본 블로그 글은 CloudNet@ 가시다님이 진행하는 스터디, AEWS3기에서 참고하였습니다.

 

 

Karpenter

AWS Karpenter는 Kubernetes 클러스터의 효율적인 오토스케일링을 위한 오픈소스 노드 프로비저닝 도구입니다.

기존의 Cluster Autoscaler가 ASG(Auto Scaling Group)에 의존해 제한적인 방식으로 동작하는 반면, Karpenter는 EC2 Fleet을 직접 활용해 더욱 유연하고 빠르게 노드를 생성하고 삭제합니다.

  1. Kubernetes 클러스터에서 파드 오토스케일링으로 인해 신규 파드가 생성됩니다.
  2. 신규 파드가 스케줄링되지 못하고 보류(Pending) 상태가 됩니다.
  3. Karpenter가 보류 중인 파드를 감지하고, NodePool(프로비저너) 설정에 따라 EC2 노드 생성을 결정합니다.
  4. EC2노드클래스(EC2NodeClass) 또는 AWS노드템플릿(AWSNodeTemplate)을 참고하여 최적화된 EC2 인스턴스를 즉시 프로비저닝합니다.

 

 

실습 환경 구성

1. 환경 변수 설정

eksctl (>= v0.202.0) 버전 업데이트 필요

# mac eksctl 버전 업데이트
brew tap weaveworks/tap
brew install weaveworks/tap/eksctl

eksctl version
0.205.0

# 변수 설정
export KARPENTER_NAMESPACE="kube-system"
export KARPENTER_VERSION="1.2.1"
export K8S_VERSION="1.32"

export AWS_PARTITION="aws" # if you are not using standard partitions, you may need to configure to aws-cn / aws-us-gov
export CLUSTER_NAME="hsh-karpenter-demo" # ${USER}-karpenter-demo
export AWS_DEFAULT_REGION="ap-northeast-2"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export TEMPOUT="$(mktemp)"
export ALIAS_VERSION="$(aws ssm get-parameter --name "/aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2023/x86_64/standard/recommended/image_id" --query Parameter.Value | xargs aws ec2 describe-images --query 'Images[0].Name' --image-ids | sed -r 's/^.*(v[[:digit:]]+).*$/\1/')"

# 확인
echo "${KARPENTER_NAMESPACE}" "${KARPENTER_VERSION}" "${K8S_VERSION}" "${CLUSTER_NAME}" "${AWS_DEFAULT_REGION}" "${AWS_ACCOUNT_ID}" "${TEMPOUT}" "${ALIAS_VERSION}"

 

 

2. Karpenter 노드에 대한 IAM 역할 및 인스턴스 프로필 생성

Karpenter가 시작한 인스턴스가 컨테이너를 실행하고 네트워킹을 구성하는 데 필요한 권한을 부여하기 위해 IAM 역할 및 인스턴스 프로필을 생성합니다.

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  > "${TEMPOUT}" \
&& aws cloudformation deploy \
  --stack-name "Karpenter-${CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${CLUSTER_NAME}"
  • 배포 약 2분 소요

 

3. EKS 클러스터 생성

# 클러스터 생성 : EKS 클러스터 생성 15분 정도 소요
eksctl create cluster -f - <https://github.com/aws/karpenter/issues/5099.
  # - eks:kube-proxy-windows

managedNodeGroups:
- instanceType: m5.large
  amiFamily: AmazonLinux2023
  name: ${CLUSTER_NAME}-ng
  desiredCapacity: 2
  minSize: 1
  maxSize: 10


addons:
- name: eks-pod-identity-agent
EOF
  • tags: karpenter.sh/discovery : 노드를 프로비저닝할 때 사용할 클러스터를 자동으로 식별하기 위해 설정, 이 태그를 기반으로 프로비저닝할 EC2 노드를 클러스터에 연결
  • podIdentityAssociations: karpenter 컨트롤러로 생성할 ServiceAccount 에 IAM 역할 연결
  • iamIdentityMappings : EC2 노드가 클러스터에 조인하기 위해 Kubernetes RBAC 연결

 

Karpenter IAM 권한 설정은 AWS 웹 콘솔에서 확인이 가능합니다.

EKS > 액세스 > IAM 액세스 항목, Pod Identity 연결

# 노드 RBAC 권한 확인 
kubectl describe cm -n kube-system aws-auth
# EC2 Spot Fleet IAM 역할 생성 확인 
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true

 

 

4. 카펜터 설치

# Logout of helm registry to perform an unauthenticated pull against the public ECR
helm registry logout public.ecr.aws

# Karpenter 설치를 위한 변수 설정 및 확인
export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name "${CLUSTER_NAME}" --query "cluster.endpoint" --output text)"
export KARPENTER_IAM_ROLE_ARN="arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter"
echo "${CLUSTER_ENDPOINT} ${KARPENTER_IAM_ROLE_ARN}"

# 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 \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --wait

카펜터 설치시 옵션에 따라 카펜터 동작을 변경할 수 있습니다.

카펜터 helm chart를 확인하면 변경할 수 있는 옵션은 다음과 같습니다.

settings:
  # -- 최대 배치 시간 (기본 10초). 이 시간이 길수록 더 많은 파드를 한 번에 고려하여, 보다 적은 수의 더 큰 노드를 생성할 수 있습니다.
  batchMaxDuration: 10s

  # -- 파드 도착이 없는 최대 유휴 시간 (기본 1초). 이 시간보다 빠르게 파드가 들어오면 batchMaxDuration까지 배치가 연장되고, 더 느리면 별도의 배치로 처리됩니다.
  batchIdleDuration: 1s

  # -- 노드가 프로비저닝될 때 사용할 클러스터 CA 인증서 번들. 비워두면 컨트롤러가 API 서버에서 자동으로 가져옵니다.
  clusterCABundle: ""

  # -- 연결할 Kubernetes 클러스터의 이름입니다.
  clusterName: ""

  # -- Kubernetes 클러스터의 엔드포인트 주소. 비워두면 시작 시 EKS 환경에서 자동 탐지됩니다.
  clusterEndpoint: ""

  # -- true일 경우, AWS 서비스가 VPC 엔드포인트 없이 접근 불가능한 환경으로 간주하여 AWS 외부 서비스 연결을 비활성화합니다.
  # AWS Pricing 서비스 등의 외부 서비스 조회도 중단됩니다.
  isolatedVPC: false

  # -- EKS 환경에서 true로 설정하면 Karpenter가 AWS의 DescribeCluster API로 클러스터 정보를 자동으로 탐색합니다.
  eksControlPlane: false

  # -- VM 메모리 오버헤드 비율 (기본값: 0.075, 즉 7.5%). 이만큼의 메모리는 각 노드에서 사용할 수 있는 총 메모리에서 제외됩니다.
  vmMemoryOverheadPercent: 0.075

  # -- EC2 스팟 중단(interruption) 이벤트를 처리할 AWS SQS 큐 이름입니다. 지정하지 않으면 중단 이벤트 처리가 비활성화됩니다.
  interruptionQueue: ""

  # -- 예약된 ENI(Elastic Network Interface)의 개수. VPC CNI의 커스텀 네트워킹 환경에서 주로 사용되며,
  # 지정된 개수의 ENI는 최대 파드 계산(max-pods) 및 kube-reserved 계산에서 제외됩니다.
  reservedENIs: "0"

  # -- 실험적 기능(Feature Gate)을 활성화하거나 비활성화합니다.
  featureGates:
    # -- nodeRepair(노드 자동 복구)는 현재 알파 단계이며 기본값은 비활성화(false)입니다.
    nodeRepair: false

    # -- reservedCapacity(온디맨드 용량 예약)는 알파 단계이며 기본값은 비활성화(false)입니다.
    reservedCapacity: false

    # -- spotToSpotConsolidation(스팟 인스턴스 간 교체 최적화)는 알파 단계이며 기본값은 비활성화(false)입니다.
    spotToSpotConsolidation: false

설정이 필요하다면, helm 차트 배포시에 --set settings.featureGates.spotToSpotConsolidation=true 을 추가하면 됩니다.

배포 완료 후 카펜터 CRD는 다음과 같이 확인할 수 있습니다.

kubectl get crd | grep karpenter
---
ec2nodeclasses.karpenter.k8s.aws             2025-03-08T19:06:55Z
nodeclaims.karpenter.sh                      2025-03-08T19:06:55Z
nodepools.karpenter.sh                       2025-03-08T19:06:55Z

 

 

5. 모니터링 설정

brew tap aws/tap
brew install eks-node-viewer

eks-node-viewer 
# 프로메테우스 설정
helm repo add grafana-charts https://grafana.github.io/helm-charts
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace monitoring

# 프로메테우스 설치
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v"${KARPENTER_VERSION}"/website/content/en/preview/getting-started/getting-started-with-karpenter/prometheus-values.yaml | envsubst | tee prometheus-values.yaml
helm install --namespace monitoring prometheus prometheus-community/prometheus --values prometheus-values.yaml
extraScrapeConfigs: |
    - job_name: karpenter
      kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
          - kube-system
      relabel_configs:
      - source_labels:
        - __meta_kubernetes_endpoints_name
        - __meta_kubernetes_endpoint_port_name
        action: keep
        regex: karpenter;http-metrics

# 프로메테우스 얼럿매니저 미사용으로 삭제
kubectl delete sts -n monitoring prometheus-alertmanager


# 그라파나 설치 
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v"${KARPENTER_VERSION}"/website/content/en/preview/getting-started/getting-started-with-karpenter/grafana-values.yaml | tee grafana-values.yaml
helm install --namespace monitoring grafana grafana-charts/grafana --values grafana-values.yaml
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      version: 1
      url: http://prometheus-server:80
      access: proxy
dashboardProviders:
  dashboardproviders.yaml:
    apiVersion: 1
    providers:
    - name: 'default'
      orgId: 1
      folder: ''
      type: file
      disableDeletion: false
      editable: true
      options:
        path: /var/lib/grafana/dashboards/default
dashboards:
  default:
    capacity-dashboard:
      url: https://karpenter.sh/preview/getting-started/getting-started-with-karpenter/karpenter-capacity-dashboard.json
    performance-dashboard:
      url: https://karpenter.sh/preview/getting-started/getting-started-with-karpenter/karpenter-performance-dashboard.json


kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

kubectl port-forward --namespace monitoring svc/grafana 3000:80 &
open http://127.0.0.1:3000
  • 카펜터 메트릭을 수집하기 위한 커스텀 메트릭과 그라파나 대시보드가 기본으로 설정됩니다.

 

6. NodePool 생성

NodePool ?

  • 카펜터의 역할은 예약할 수 없는 Pod를 처리하기 위해 노드를 추가하고, 해당 노드에 Pod를 예약하고, 필요하지 않으면 노드를 제거하는 것입니다.
  • Karpenter를 구성하려면 Karpenter가 예약할 수 없는 Pod를 관리하고 노드를 만료하는 방법을 정의하는 NodePool을 만듭니다.
  • NodePool은 Karpenter가 생성할 수 있는 노드와 해당 노드에서 실행될 수 있는 포드에 제약 조건을 설정합니다.

NodeClass에서는 노드를 생성할 AMI와 보안그룹 서브넷을 태그 기반으로 식별하여 선택합니다.

echo $ALIAS_VERSION
v20250228


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 # 사용률이 낮다고 판단하면 1분 후 삭제 
---
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    5m15s

NAME                                     READY   AGE
ec2nodeclass.karpenter.k8s.aws/default   True    5m15s

7. 예제 애플리케이션 배포

예제 애플리케이션을 배포하고 카펜터에 의해 노드가 생성되도록 설정하겠습니다.

# puase 파드 1개에 CPU 1개 최소 보장할 수 있는 디플로이먼트 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      terminationGracePeriodSeconds: 0
      securityContext:
        runAsUser: 1000
        runAsGroup: 3000
        fsGroup: 2000
      containers:
      - name: inflate
        image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
        resources:
          requests:
            cpu: 1
        securityContext:
          allowPrivilegeEscalation: false
EOF

# 터미널 모니터링 설정 
eks-node-viewer --resources cpu,memory 


# 스케일 업 
kubectl scale deployment inflate --replicas 5 



# 출력 로그 확인
kubectl logs -n "${KARPENTER_NAMESPACE}" -l app.kubernetes.io/name=karpenter -c controller | grep 'launched nodeclaim' | jq '.' 
{
  "level": "INFO",
  "time": "2025-03-08T19:49:00.259Z",
  "logger": "controller",
  "message": "launched nodeclaim",
  "commit": "058c665",
  "controller": "nodeclaim.lifecycle",
  "controllerGroup": "karpenter.sh",
  "controllerKind": "NodeClaim",
  "NodeClaim": {
    "name": "default-5tvbf"
  },
  "namespace": "",
  "name": "default-5tvbf",
  "reconcileID": "6e36a28e-3310-4d89-bf5f-a6b99b970d48",
  "provider-id": "aws:///ap-northeast-2b/i-0b37368ecd0496279",
  "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"
  }
  
  
# 카펜터로 구성된 노드 확인
kubectl get nodeclaims
NAME            TYPE          CAPACITY    ZONE              NODE                                                READY   AGE
default-5tvbf   c5a.2xlarge   on-demand   ap-northeast-2b   ip-192-168-158-44.ap-northeast-2.compute.internal   True    65s


# 카펜터 노드 구성 정보 확인 
kubectl describe nodeclaims
..
Spec:
  Expire After:  720h
  Node Class Ref:
    Group:  karpenter.k8s.aws
    Kind:   EC2NodeClass
    Name:   default
  Requirements:
    Key:       karpenter.k8s.aws/instance-generation
    Operator:  Gt
    Values:
      2
    Key:       node.kubernetes.io/instance-type
    Operator:  In
    Values:
      c4.2xlarge
      c4.4xlarge
      c5.2xlarge
      c5.4xlarge
      c5a.2xlarge
      c5a.4xlarge
      c5a.8xlarge
      c5d.2xlarge
      c5d.4xlarge
      c5n.2xlarge
      c5n.4xlarge
      c6i.2xlarge
      c6i.4xlarge
      c6id.2xlarge
      c6id.4xlarge
      c6in.2xlarge
      c6in.4xlarge
      c7i-flex.2xlarge
      c7i-flex.4xlarge
      c7i.2xlarge
      c7i.4xlarge
      m4.2xlarge
      m4.4xlarge
      m5.2xlarge
      m5.4xlarge
      m5a.2xlarge
      m5a.4xlarge
      m5ad.2xlarge
      m5ad.4xlarge
      m5d.2xlarge
      m5d.4xlarge
      m5zn.2xlarge
      m5zn.3xlarge
      m6i.2xlarge
      m6i.4xlarge
      m6id.2xlarge
      m6id.4xlarge
      m7i-flex.2xlarge
      m7i-flex.4xlarge
      m7i.2xlarge
      m7i.4xlarge
      r3.2xlarge
      r4.2xlarge
      r4.4xlarge
      r5.2xlarge
      r5.4xlarge
      r5a.2xlarge
      r5a.4xlarge
      r5ad.2xlarge
      r5ad.4xlarge
      r5b.2xlarge
      r5d.2xlarge
      r5d.4xlarge
      r5dn.2xlarge
      r5n.2xlarge
      r6i.2xlarge
      r6i.4xlarge
      r6id.2xlarge
      r7i.2xlarge
      r7i.4xlarge
    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/ec2nodeclass
    Operator:  In
    Values:
      default
    Key:       karpenter.sh/nodepool
    Operator:  In
    Values:
      default
    Key:       karpenter.k8s.aws/instance-category
    Operator:  In
    Values:
      c
      m
      r
  Resources:
    Requests:
      Cpu:   5150m
      Pods:  9
Status:
  Allocatable:
    Cpu:                        7910m
    Ephemeral - Storage:        17Gi
    Memory:                     14162Mi
    Pods:                       58
    vpc.amazonaws.com/pod-eni:  38
  Capacity:
    Cpu:                        8
    Ephemeral - Storage:        20Gi
    Memory:                     15155Mi
    Pods:                       58
    vpc.amazonaws.com/pod-eni:  38
  ...

카펜터로 배포한 노드는 태그를 통해 구별이 가능합니다.

또한, CloudTrail에서 CreateFleet 이벤트 이름을 조회하여 NodeClaim 정보를 확인할 수 있습니다.

노드 삭제 동작도 확인하겠습니다

# 예제 파드 삭제 
kubectl delete deployment inflate && date

# Karpenter 로그 확인 
kubectl logs -f -n "${KARPENTER_NAMESPACE}" -l app.kubernetes.io/name=karpenter -c controller | jq '.'
...
{
  "level": "INFO",
  "time": "2025-03-08T19:56:49.835Z",
  "logger": "controller",
  "message": "disrupting nodeclaim(s) via delete, terminating 1 nodes (0 pods) ip-192-168-158-44.ap-northeast-2.compute.internal/c5a.2xlarge/on-demand",
  "commit": "058c665",
  "controller": "disruption",
  "namespace": "",
  "name": "",
  "reconcileID": "2a754e66-e541-4e3f-868b-05894223b692",
  "command-id": "644c6a4f-71d9-489e-8289-5d2cd747e601",
  "reason": "empty"
}
{
  "level": "INFO",
  "time": "2025-03-08T19:56:50.750Z",
  "logger": "controller",
  "message": "tainted node",
  "commit": "058c665",
  "controller": "node.termination",
  "controllerGroup": "",
  "controllerKind": "Node",
  "Node": {
    "name": "ip-192-168-158-44.ap-northeast-2.compute.internal"
  },
  "namespace": "",
  "name": "ip-192-168-158-44.ap-northeast-2.compute.internal",
  "reconcileID": "3590a63f-aa04-4d7a-8db0-bd6b6ae2a9d0",
  "taint.Key": "karpenter.sh/disrupted",
  "taint.Value": "",
  "taint.Effect": "NoSchedule"
}

리소스 삭제

# 자원 삭제
kubectl delete nodepool,ec2nodeclass default 

# Karpenter helm 삭제 
helm uninstall karpenter --namespace "${KARPENTER_NAMESPACE}"

# Karpenter IAM Role 등 생성한 CloudFormation 삭제
aws cloudformation delete-stack --stack-name "Karpenter-${CLUSTER_NAME}"

# EC2 Launch Template 삭제
aws ec2 describe-launch-templates --filters "Name=tag:karpenter.k8s.aws/cluster,Values=${CLUSTER_NAME}" |
    jq -r ".LaunchTemplates[].LaunchTemplateName" |
    xargs -I{} aws ec2 delete-launch-template --launch-template-name {}

# 클러스터 삭제
eksctl delete cluster --name "${CLUSTER_NAME}"

Karpenter와 노드 그룹과 달리 별개로 운영됩니다.

클러스터 삭제 후에도 EC2 자원이 있는 지 확인이 필요합니다.

 

'Cloud' 카테고리의 다른 글

EKS Fargate & AutoMode  (0) 2025.03.23
EKS Security  (2) 2025.03.16
EKS Autoscaling  (0) 2025.03.09
Grafana Observability구성  (0) 2025.03.01
EKS 노드 그룹  (1) 2025.02.23