Karpenter는 CA와 마찬가지로 POD의 스케줄링 상태를 모니터링하다가 Node의 스케일링이 이루어 진다. 하지만 CA와 다르게, POD가 Node에 적절하게 스케줄링되어 낭비되는 리소스가 없게 Node의 Rightsizing을 도와준다.

게다가 CA의 단점을 보완하여 몇 초 만에(Just-in-time) 컴퓨팅 리소스를 제공한다는 장점이 있다!

Karpenter는 빠르고 자동화되어 있고, 가장 저렴한 인스턴스를 프로비저닝해 준다!

Untitled

Karpenter

Karpenter 설치

이제 Karpenter 실습을 위한 클러스터를 구축하겠다!

실습 환경은 가시다님께서 제공해 주셔서 감사하게도 쉽게 구축할 수 있었다.

실습에 필요한 환경변수를 설정하였고

(클러스터 버전은 1.29이고 Karpenter 버전은 0.35.2이다.)

export KARPENTER_NAMESPACE="kube-system"
export K8S_VERSION="1.29"
export KARPENTER_VERSION="0.35.2"
export TEMPOUT=$(mktemp)
export ARM_AMI_ID="$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2-arm64/recommended/image_id --query Parameter.Value --output text)"
export AMD_AMI_ID="$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2/recommended/image_id --query Parameter.Value --output text)"
export GPU_AMI_ID="$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2-gpu/recommended/image_id --query Parameter.Value --output text)"
export AWS_PARTITION="aws"
export CLUSTER_NAME="${USER}-karpenter-demo"
echo "export CLUSTER_NAME=$CLUSTER_NAME" >> /etc/profile
echo $KARPENTER_VERSION $CLUSTER_NAME $AWS_DEFAULT_REGION $AWS_ACCOUNT_ID $TEMPOUT $ARM_AMI_ID $AMD_AMI_ID $GPU_AMI_ID

실습에 필요한 IAM Policy와 IAM Role을 생성하였다.

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}"

마지막으로 eksctl로 클러스터를 생성하였다.

eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ${CLUSTER_NAME}
  region: ${AWS_DEFAULT_REGION}
  version: "${K8S_VERSION}"
  tags:
    karpenter.sh/discovery: ${CLUSTER_NAME}

iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: karpenter
      namespace: "${KARPENTER_NAMESPACE}"
    roleName: ${CLUSTER_NAME}-karpenter
    attachPolicyARNs:
    - arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}
    roleOnly: true

iamIdentityMappings:
- arn: "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}"
  username: system:node:
  groups:
  - system:bootstrappers
  - system:nodes

managedNodeGroups:
- instanceType: m5.large
  amiFamily: AmazonLinux2
  name: ${CLUSTER_NAME}-ng
  desiredCapacity: 2
  minSize: 1
  maxSize: 10
  iam:
    withAddonPolicies:
      externalDNS: true
EOF

실습에 필요한 몇몇 리소스를 더 설치하고 아래 karpenter 설치를 위한 클러스터 엔드포인트와 IAM Role 변수를 설정해 주었다.

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 install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "${KARPENTER_NAMESPACE}" --create-namespace \
  --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${KARPENTER_IAM_ROLE_ARN}" \
  --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

Untitled 1

23년 여름즈음에 0.32.0 버전이 출시되면서 crd이름이 아래와 같이 변경되었고 v0.33.0부터는 기존 API는 사용 중단되었다.

v1alpha5/Provisioner → v1beta1/NodePool
v1alpha1/AWSNodeTemplate → v1beta1/EC2NodeClass 
v1alpha5/Machine → v1beta1/NodeClaim

Untitled 2

[Karpenter graduates to beta Amazon Web Services](https://aws.amazon.com/ko/blogs/containers/karpenter-graduates-to-beta/)

NodePool 기본 사용

Getting Started with Karpenter

NodePool 리소스를 생성해서 정말 Karpenter가 해당 클러스터에 필요한 노드를 생성해 주는지 확인해 보겠다

cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1beta1
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: ["spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["2"]
      nodeClassRef:
        apiVersion: karpenter.k8s.aws/v1beta1
        kind: EC2NodeClass
        name: default
  limits:
    cpu: 1000
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h # 30 * 24h = 720h
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2 # Amazon Linux 2
  role: "KarpenterNodeRole-${CLUSTER_NAME}" # replace with your cluster name
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  amiSelectorTerms:
    - id: "${ARM_AMI_ID}"
    - id: "${AMD_AMI_ID}"
#   - id: "${GPU_AMI_ID}" # <- GPU Optimized AMD AMI 
#   - name: "amazon-eks-node-${K8S_VERSION}-*" # <- automatically upgrade when a new AL2 EKS Optimized AMI is released. This is unsafe for production workloads. Validate AMIs in lower environments before deploying them to production.
EOF

securityGroupSelectorsubnetSelectorkarpenter.sh/discovery 태그 값을 통해 관리 리소스를 찾을 수 있다.

consolidationPolicy는 사용하지 않는 노드를 정리하는 정책으로, 데몬셋은 제외한다. 현재 720시간 한달로 되어 있어, 한달마다 사용하지 않는 노드를 정리하여 클러스터 비용을 줄일 수 있다. 즉, 비용 최적화가 가능한 정책이다!

Untitled 3

이제 테스트 파드를 만들어서 replicas를 늘려줬는데 아래와 같은 ServiceLinkedRoleCreationNotPermitted 에러가 발생했다.

Untitled 4

그래서 아래와 같은 명령어로 권한을 생성해 줬다.

aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true

그리고 다시 POD 개수를 늘려 주니 프로비저닝 컨트롤러가 POD를 찾고 nodeclaim을 등록하고 시작하였다.

Untitled

개수를 줄일 때도 마찬가지로 즉각적으로 노드를 삭제했다.

terminating되는 POD를 식별하고 노드를 삭제하고 nodeclaim을 삭제했다.

Untitled 5

spotToSpotConsolidation

Untitled 6

[Applying Spot-to-Spot consolidation best practices with Karpenter Amazon Web Services](https://aws.amazon.com/ko/blogs/compute/applying-spot-to-spot-consolidation-best-practices-with-karpenter/)

기존에 온디맨드로만 되던 consolidation 기능이 0.34.0 버전부터 spotToSpotConsolidation가 활성화되어 스팟 인스턴스도 사용 가능해졌다.

스팟 인스턴스는 유휴자원을 저렴하게 빌려서 사용하다가 즉시 반납해야 하는 케이스때문에 없던 기능이었다. 하지만 이제는 최소 15개의 인스턴스 유형이 포함되어야 한다는 제약 조건이 추가되어 가용성을 좀 높이고 중단 빈도를 낮췄다.

settings.featureGates.spotToSpotConsolidation=true value를 변경하여 카펜터 헬름을 재배포하자!

helm upgrade karpenter -n kube-system oci://public.ecr.aws/karpenter/karpenter --reuse-values --set settings.featureGates.spotToSpotConsolidation=true

nodepool과 nodeclass를 다시 생성하고 테스트를 진행한다.

cat <<EOF > nodepool.yaml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    metadata:
      labels:
        intent: apps
    spec:
      nodeClassRef:
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c","m","r"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano","micro","small","medium"]
        - key: karpenter.k8s.aws/instance-hypervisor
          operator: In
          values: ["nitro"]
  limits:
    cpu: 100
    memory: 100Gi
  disruption:
    consolidationPolicy: WhenUnderutilized
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: Bottlerocket
  subnetSelectorTerms:          
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  role: KarpenterNodeRole-${CLUSTER_NAME}" # replace with your cluster name
  tags:
    Name: karpenter.sh/nodepool/default
    IntentLabel: "apps"
EOF
kubectl apply -f nodepool.yaml

테스트 POD개수를 5개로 늘렸을 때, instance-type에서 골라서 적절한 사양으로 사이즈업된 노드를 생성해 준다.

Untitled 7

Untitled 8

다시 POD를 1개로 줄이게 되면

nodeclaim을 다시 등록하고 적절한 사양을 골라 노드를 재배포한다. (c6gd.2xlarge → c6gd.large)

instance-type도 줄었다.

Untitled 9

Untitled 10

여기까지 Karpenter 맛보기 실습을 진행하면서 왜 다들 카펜터를 사용하는지 체감할 수 있었다. 비용최적화나 관리적인 측면으로 운영하는 데에 많은 도움이 될 것 같다.

Untitled

쿠버네티스도 버전업이 빠른데 카펜터도 버전업이 빠르다고 하는데… 사용하게 되면 버전에 따른 기능 파악은 필요할 것 같다.

Categories:

Updated:

Leave a comment