이번 포스팅에서는 Karpenter에 대해서 알아보겠다.
Karpenter automatically launches just the right compute resources to handle your cluster's applications. It is designed to let you take full advantage of the cloud with fast and simple compute provisioning for Kubernetes clusters.
공홈의 정의를 보면 CA를 할 때 빠르고 간편하게 프로비저닝이 가능하게 하는 기능이라고 설명이 되어있다.
실제로 CSP와 연동되는 CA를 사용하다 보면, 가장 큰 단점이 느리다는 것이다. k8s의 리소스와 CSP의 리소스 연동이 필요하고, 이 중간에서 뭔가 주고 받는 것들이 많아서 그럴텐데, Karpenter는 이 불편한과 느림을 해결해준 솔루션이다.
실제로 CSP에서 CA를 사용하면 동작이 꽤나 느린데, Karpenter로 실습해보면 진짜 엄청 빨라서 놀란다. 오픈소스라고는 하지만 AWS에서 만들어서 그런지 EKS만 지원되는 것 같으니 참고하자.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/karpenter-preconfig.yaml
# CloudFormation 스택 배포
예시) aws cloudformation deploy --template-file karpenter-preconfig.yaml --stack-name myeks2 --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks2 --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks2 --query 'Stacks[*].Outputs[0].OutputValue' --output text
# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks2 --query 'Stacks[*].Outputs[0].OutputValue' --output text)
먼저 bastion용 EC2를 설치하고, EKS, Karpenter를 설치해준다. 아래 스크립트에서는 IAM Policy, Role, EC@ Instance Profile을 생성해준 후 EKS 설치, Karpenter설치를 한다.
# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER' | egrep -v 'SECRET|KEY'
# 환경변수 설정
export KARPENTER_VERSION=v0.27.5
export TEMPOUT=$(mktemp)
echo $KARPENTER_VERSION $CLUSTER_NAME $AWS_DEFAULT_REGION $AWS_ACCOUNT_ID $TEMPOUT
# CloudFormation 스택으로 IAM Policy, Role, EC2 Instance Profile 생성 : 3분 정도 소요
curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/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}"
# 클러스터 생성 : myeks2 EKS 클러스터 생성 19분 정도 소요
eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ${CLUSTER_NAME}
region: ${AWS_DEFAULT_REGION}
version: "1.24"
tags:
karpenter.sh/discovery: ${CLUSTER_NAME}
iam:
withOIDC: true
serviceAccounts:
- metadata:
name: karpenter
namespace: karpenter
roleName: ${CLUSTER_NAME}-karpenter
attachPolicyARNs:
- arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}
roleOnly: true
iamIdentityMappings:
- arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}"
username: system:node:{{EC2PrivateDNSName}}
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
## Optionally run on fargate
# fargateProfiles:
# - name: karpenter
# selectors:
# - namespace: karpenter
EOF
# eks 배포 확인
eksctl get cluster
eksctl get nodegroup --cluster $CLUSTER_NAME
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
eksctl get addon --cluster $CLUSTER_NAME
# [터미널1] eks-node-viewer
cd ~/go/bin && ./eks-node-viewer
# k8s 확인
kubectl cluster-info
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -n kube-system -owide
kubectl describe cm -n kube-system aws-auth
...
mapRoles:
----
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::911283464785:role/KarpenterNodeRole-myeks2
username: system:node:{{EC2PrivateDNSName}}
- groups:
- system:bootstrappers
- system:nodes
rolearn: arn:aws:iam::911283464785:role/eksctl-myeks2-nodegroup-myeks2-ng-NodeInstanceRole-1KDXF4FLKKX1B
username: system:node:{{EC2PrivateDNSName}}
...
# 카펜터 설치를 위한 환경 변수 설정 및 확인
export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output text)"
export KARPENTER_IAM_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter"
echo $CLUSTER_ENDPOINT $KARPENTER_IAM_ROLE_ARN
# EC2 Spot Fleet 사용을 위한 service-linked-role 생성 확인 : 만들어있는것을 확인하는 거라 아래 에러 출력이 정상!
# 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.
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
# docker logout : Logout of docker to perform an unauthenticated pull against the public ECR
docker logout public.ecr.aws
# karpenter 설치
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 settings.aws.clusterName=${CLUSTER_NAME} \
--set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
--set settings.aws.interruptionQueueName=${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
# 확인
kubectl get-all -n karpenter
kubectl get all -n karpenter
kubectl get cm -n karpenter karpenter-global-settings -o jsonpath={.data} | jq
kubectl get crd | grep karpenter
Karpenter는 CA에 비해 엄청 빠르게 Node를 프로비저닝 해주고, 심지어 비용계산을 해서 어떤 높은 스펙의 Node를 사용할지, 낮은 스펙의 Node를 여러 개 사용할지 까지도 계산해서 Scale Out/In을 해주는 기능을 제공한다. (Consolidation)
Karpenter 설치가 끝났으면, Provisioner를 세팅해준다. 여기서 Provisioner는 Karpenter가 Scale Out/In 하기 위한 노드 스펙과, Diprovisioning 하기위한 설정값들을 세팅하는 역할을 한다.
#
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
limits:
resources:
cpu: 1000
providerRef:
name: default
ttlSecondsAfterEmpty: 30
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
name: default
spec:
subnetSelector:
karpenter.sh/discovery: ${CLUSTER_NAME}
securityGroupSelector:
karpenter.sh/discovery: ${CLUSTER_NAME}
EOF
# 확인
kubectl get awsnodetemplates,provisioners
이제 Deployment를 배포하고, 현재 클러스터의 리소스를 초과하게끔 replica를 늘려보자.
(pkos-admin@myeks2:N/A) [root@myeks2-bastion-EC2 ~]# kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type,node.kubernetes.io/instance-type
NAME STATUS ROLES AGE VERSION CAPACITYTYPE CAPACITY-TYPE INSTANCE-TYPE
ip-192-168-0-129.ap-northeast-2.compute.internal Ready <none> 34m v1.24.13-eks-0a21954 ON_DEMAND m5.large
ip-192-168-45-244.ap-northeast-2.compute.internal Ready <none> 2m19s v1.24.13-eks-0a21954 spot c4.2xlarge
ip-192-168-89-196.ap-northeast-2.compute.internal Ready <none> 34m v1.24.13-eks-0a21954 ON_DEMAND m5.large
Provisioner 의 spec을 spot 인스턴스로 지정하였고, Karpenter가 필요한 만큼의 Node를 증설해주었다.
workloads running on under-utilized compute instances are compacted to fewer instances. This improves the overall efficiency of how we run workloads on the compute, resulting in less overhead and lower costs.
Karpenter는 Consolidation 기능도 제공해주는데, 여러 Node가 떠 있을 경우, 비용 계산을 해서 비용이 큰 Node 1개가 더 저렴할 경우 Node 교체를 해주기도 한다. (위에서 실습한 provisioner는 삭제를 해준다.)
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
consolidation:
enabled: true
labels:
type: karpenter
limits:
resources:
cpu: 1000
memory: 1000Gi
providerRef:
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
- key: node.kubernetes.io/instance-type
operator: In
values:
- c5.large
- m5.large
- m5.xlarge
EOF
#
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.7
resources:
requests:
cpu: 1
EOF
kubectl scale deployment inflate --replicas 12
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
kubectl scale deployment inflate --replicas 5
On-Demand Node로 필요한 만큼 Scale Out된다. Deployment를 Scale Out/In을 해보면 Karpenter에서 Consolidation 기능이 켜져있기 때문에 Node를 비용 최적화하여 배포해주는 기능을 제공한다.
CA를 사용해 본 적이 있으면, Karpenter가 대체 얼마나 빠른지 체감할 수 가 있다. 가끔 100개의 Node를 Scale Out 해야할 경우가 있는데, 제법 느려서 이게 제대로 되고 있는 지 의심이 들 때가 있다. 특히 이벤트성 작업을 할 때 CA가 작동을 하지 않으면 대략 난감한 상황이 펼쳐지기도 한다.
Karpenter도 이슈가 없다고 보장되지는 않지만, 이 미친듯한 속도감이 주는 성능은 정말 대단하다고 생각한다. 다만, 아쉬운 점은 오픈소스이지만 AWS하고만 연동이 된다는 점이다. 현재 사용 중인 AKS 에도 Karpenter 사용을 위해 이슈가 등록되어 있지만, 뭔가 빨리 연동될 것 같지는 않은 느낌이다. https://github.com/Azure/AKS/issues/2712. 아마 Azure에서 비슷한 기능을 먼저 개발하는게 빠를지도..?