[AEKS2] 5주차 - EKS Autoscaling: Karpenter
Karpenter는 CA와 마찬가지로 POD의 스케줄링 상태를 모니터링하다가 Node의 스케일링이 이루어 진다. 하지만 CA와 다르게, POD가 Node에 적절하게 스케줄링되어 낭비되는 리소스가 없게 Node의 Rightsizing을 도와준다.
게다가 CA의 단점을 보완하여 몇 초 만에(Just-in-time) 컴퓨팅 리소스를 제공한다는 장점이 있다!
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
23년 여름즈음에 0.32.0 버전이 출시되면서 crd이름이 아래와 같이 변경되었고 v0.33.0부터는 기존 API는 사용 중단되었다.
v1alpha5/Provisioner → v1beta1/NodePool
v1alpha1/AWSNodeTemplate → v1beta1/EC2NodeClass
v1alpha5/Machine → v1beta1/NodeClaim
[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
securityGroupSelector
와 subnetSelector
의 karpenter.sh/discovery
태그 값을 통해 관리 리소스를 찾을 수 있다.
consolidationPolicy
는 사용하지 않는 노드를 정리하는 정책으로, 데몬셋은 제외한다. 현재 720시간 한달로 되어 있어, 한달마다 사용하지 않는 노드를 정리하여 클러스터 비용을 줄일 수 있다. 즉, 비용 최적화가 가능한 정책이다!
이제 테스트 파드를 만들어서 replicas를 늘려줬는데 아래와 같은 ServiceLinkedRoleCreationNotPermitted
에러가 발생했다.
그래서 아래와 같은 명령어로 권한을 생성해 줬다.
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
그리고 다시 POD 개수를 늘려 주니 프로비저닝 컨트롤러가 POD를 찾고 nodeclaim을 등록하고 시작하였다.
개수를 줄일 때도 마찬가지로 즉각적으로 노드를 삭제했다.
terminating되는 POD를 식별하고 노드를 삭제하고 nodeclaim을 삭제했다.
spotToSpotConsolidation
[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에서 골라서 적절한 사양으로 사이즈업된 노드를 생성해 준다.
다시 POD를 1개로 줄이게 되면
nodeclaim을 다시 등록하고 적절한 사양을 골라 노드를 재배포한다. (c6gd.2xlarge → c6gd.large)
instance-type도 줄었다.
여기까지 Karpenter 맛보기 실습을 진행하면서 왜 다들 카펜터를 사용하는지 체감할 수 있었다. 비용최적화나 관리적인 측면으로 운영하는 데에 많은 도움이 될 것 같다.
쿠버네티스도 버전업이 빠른데 카펜터도 버전업이 빠르다고 하는데… 사용하게 되면 버전에 따른 기능 파악은 필요할 것 같다.
Leave a comment