DevOps/Kubernetes

[EKS] Karpenter - Groupless Node AutoScaling 사용법

Jen'_' 2022. 11. 17. 19:38
반응형

K8S Auto Scaling

Pod 기반

  • Kubernetes Event-driven Autoscaling - 이벤트 기반 파드 오토스케일링을 지원합니다. 
  • Horizontal Pod Autoscaler - 필요에 따라 배포에 더 많은 파드를 추가하거나 제거합니다.
  • Vertical Pod Autoscaler - 파드의 CPU 및 메모리 요청 크기를 조정하고 부하에 맞게 제한합니다.

 

Node 기반

  • Karpenter (현재는 AWS EKS만 가능)
  • Cluster Autoscaler

 

Karpenter 개요

Karpenter는 AWS로 구축된 유연한 오픈 소스의 고성능 Kubernetes 클러스터 오토스케일러입니다. 애플리케이션 로드의 변화에 대응하여 적절한 크기의 컴퓨팅 리소스를 신속하게 실행함으로써 애플리케이션 가용성과 클러스터 효율성을 개선할 수 있습니다. 또한 Karpenter는 애플리케이션의 요구 사항을 충족하는 컴퓨팅 리소스를 적시에 제공하며, 앞으로 클러스터의 컴퓨팅 리소스 공간을 자동으로 최적화하여 비용을 절감하고 성능을 개선하게 됩니다.

참고: https://aws.amazon.com/ko/blogs/korea/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/

 

Provisioning(Scale Out) 기준

  • Unschedulable pod 발생
  • Resource Request을 기준으로 노드 선택 및 증설

 

Deprovisioning(Scale In) 기준

  • ttlSecondsAfterEmpty 설정했을 경우 노드에 데몬셋을 제외한 예약된 Pod가 없을 때
  • ttlSecondsUntilExpired 에 설정한 수명이 다하면 롤링 업데이트가 발생하고 기존의 노드는 삭제됨
  • Pod가 제거되는 속도를 조절하기 위해 PDB를 설정

 

 

Cluster Autoscaler 동작 원리

Cluster Autoscaler

1. CA는 Pod가 Pending이 나면 ASG Desired Capacity 값을 증가시킵니다.

2. 이를 인지한 ASG가 새로운 Node를 추가합니다.

3. kube-scheduler가 Pod를 새로운 Node에 할당합니다. 

 

 

Karpneter 동작 원리

Karpenter

1. Unshedulable Pod가 발생하면 Karpenter는 새로운 Node 추가를 결정합니다.

2. Unshedulable Pod의 nodeSelector, nodeAffinity, tolerations을 확인해서 적절한 Provisioner(CRD)를 선택하고 Resource Request에 따라 Node의 스펙을 결정하여 직접 새로운 Node를 배포합니다.

3. 추가된 Node가 Ready 상태가 되면 Karpenter는 kube-scheduler를 대신하여 Pod를 Node에 직접 할당합니다. 

 

ASG를 사용해서 노드를 확장하는 CA와 달리 Karpenter는 자체 구현된 오픈소스 JIT(Just-In-Time)으로 빠르게 Node를 확장하고 Pod를 할당하기 때문에 프로비저닝 속도가 훨씬 빠릅니다.

 

 

CA를 Karpenter로 대체했을 때 장점은 다음과 같습니다.

  1. CA를 위하여 Launch Template을 어렵게 구성할 필요가 없습니다. Karpenter는 Groupless Auto Scaling입니다. 더 이상 ASG 나 Launch Template을 관리하지 않고 그룹 정의와는 상관없이 EC2의 타입(T/M/R), 구매 유형(on-demand/Spot), 가용 영역 등을 자유롭게 선택할 수 있습니다.
  2. 유연하고 빠르게 노드를 확장/제거할 수 있습니다. 실제로 CA를 사용할 때는 노드가 생성되고 Pod가 Ready가 될 때까지 5~10분이 걸렸으면, Karpenter를 사용하면 1~2분이 걸렸습니다.
  3. 자동으로 Node가 롤링 업데이트 되기 때문에 운영 관리가 간편화됩니다. Karpenter Provisioner에 ttlSecondsUntilExpired를 정의하면 Node를 최신 버전의 AMI로 주기적으로 롤링 업데이트하여 Node를 최신화합니다. 이를 통해 EKS를 업그레이드할 때는 컨트롤 플레인과 Karpenter를 사용하지 않는 노드그룹 업그레이만 신경 쓰면 됩니다. 

 

설치 방법

Environment Variables

export KARPENTER_VERSION=v0.18.1
export CLUSTER_NAME="test-eks"
export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output text)"
export AWS_DEFAULT_REGION="ap-northeast-2"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"

 

노드가 생성될 서브넷, 사용할 보안 그룹에 태깅 작업

karpenter.sh/discovery = ${CLUSTER_NAME}

 

Karpenter가 생성할 노드에 붙일 IAM Role 생성

TEMPOUT=$(mktemp)

curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-eksctl/cloudformation.yaml  > $TEMPOUT \
&& aws cloudformation deploy \
  --stack-name "Karpenter-${CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${CLUSTER_NAME}"

 

이 명령은 Karpenter node role을 aws-auth configmap에 추가하여 이 역할을 가진 노드가 클러스터에 연결할 수 있도록 합니다.

eksctl create iamidentitymapping \
  --username system:node:{{EC2PrivateDNSName}} \
  --cluster "${CLUSTER_NAME}" \
  --arn "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}" \
  --group system:bootstrappers \
  --group system:nodes

 

Karpenter Controller IAM Role 생성

 eksctl create iamserviceaccount \
  --cluster "${CLUSTER_NAME}" --name karpenter --namespace karpenter \
  --role-name "${CLUSTER_NAME}-karpenter" \
  --attach-policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}" \
  --role-only \
  --approve

export KARPENTER_IAM_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter"

 

EC2 Spot Service Linked Role 생성

이 단계는 이 계정에서 EC2 Spot을 처음 사용하는 경우에만 필요합니다.

aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
# If the role has already been successfully created, you will see:
# An error occurred (InvalidInput) when calling the CreateServiceLinkedRole operation: Service role name AWSServiceRoleForEC2Spot has been taken in this account, please try a different suffix.

 

Install Karpenter Helm Chart

helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter --create-namespace \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=${KARPENTER_IAM_ROLE_ARN} \
  --set clusterName=${CLUSTER_NAME} \
  --set clusterEndpoint=${CLUSTER_ENDPOINT} \
  --set aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
  --wait

 

Provisioner

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: karpenter-provisioner
spec:    
  requirements:
    - key: node.kubernetes.io/instance-type
      operator: In
      values: ["t3.medium", "t3.large", "t3.xlarge"]  
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: ["ap-northeast-2a", "ap-northeast-2c"]      
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["on-demand"]  
  limits:
    resources:
      cpu: "1000"
      memory: 1000Gi
  ttlSecondsAfterEmpty: 30
  ttlSecondsUntilExpired: 2592000
  labels:
    role: ops
    provision: karpenter
  provider:
    securityGroupSelector:
      karpenter.sh/discovery: ${CLUSTER_NAME}
    subnetSelector:
      karpenter.sh/discovery: ${CLUSTER_NAME}
    tags:
      Name: karpenter.sh/provisioner-name/karpenter-provisioner
      karpenter.sh/discovery: ${CLUSTER_NAME}

Provisioner: https://karpenter.sh/v0.18.1/provisioner/

provider(AWSNodeTemplate): https://karpenter.sh/v0.19.0/aws/provisioning/

 

Node deprovisioning

아래 값이 설정되지 않은 경우 Karpenter는 인스턴스를 삭제하지 않습니다. 클러스터 축소를 활성화하려면 ttlSecondsAfterEmpty 값을 설정하는 것이 좋습니다.

  • spec.ttlSecondsAfterEmpty
    • 설정한 시간 동안 데몬셋을 제외한 Pod가 한 개도 없을 때 노드를 삭제합니다.
  • spec.ttlSecondsUntilExpired
    • 노드가 정의된 수명(초)에 도달하면 사용 중이더라도 삭제됩니다. 이를 통해 노드를 최신의 인스턴스(AMI)로 교체하여 주기적으로 노드를 효과적으로 "업그레이드"할 수 있습니다. 

 

spec.labels

모든 노드에 적용되는 label

 

spec.requirements

프로비저닝 된 노드의 매개변수를 제한하는 요구사항입니다.  이러한 요구 사항은 pod.spec.affinity.nodeAffinity 규칙과 결합됩니다. 연산자 { In, NotIn }은 값을 포함하거나 제외할 수 있도록 지원됩니다.

Kubernetes Well-Known Labels은 프로비저닝 도구 수준 또는 작업 부하 정의(예: pod.spec의 nodeSelector)에서 지정할 수 있습니다. 노드는 provisioner와 pod의 requirements를 모두 사용하여 선택됩니다.

 

spec.limits.resources

provisioner가 관리할 최대 리소스 양을 제한하는 limit section(spec.limits.resources)이 포함됩니다.

예를 들어 "spec.limits.resources.cpu"를 "1000"으로 설정하면 프로비저너가 모든 인스턴스에서 총 1000개의 CPU 코어로 제한됩니다. 이렇게 하면 원치 않는 클러스터의 과도한 성장을 방지할 수 있습니다.

 

spec.provider

cloud provider-specific custom resource 참조 (AWS의 경우 AWSNodeTemplate)

 

 

Autometic Node Provisioning 테스트

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
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2    
          resources:
            requests:
              cpu: 1
      nodeSelector:
        role: ops
EOF

kubectl scale deployment inflate --replicas 5
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller

 

확인

태그가 붙은 서브넷에 보안 그룹을 붙여서 노드가 프로비저닝 됩니다.

Karpenter는 Unschedulable Pod의 Resource Request, nodeSelector, nodeAffinity, tolerations를 확인하고 가장 적절한 Provisioner를 선택, 인스턴스 타입을 선택해서 노드를 프로비저닝 합니다.

Karpenter는 시작 템플릿을 생성해서 노드를 프로비저닝 합니다. 그리고 일정 시간이 지나면 시작 템플릿을 삭제합니다.

 

 

 

참고

https://karpenter.sh/v0.18.1/getting-started/getting-started-with-eksctl/

https://kubesandclouds.com/index.php/2022/01/04/karpenter-vs-cluster-autoscaler/

https://devblog.kakaostyle.com/ko/2022-10-13-1-karpenter-on-eks/

반응형