Karpenter는 Kubernetes 클러스터의 애플리케이션을 처리하는 데 적합한 컴퓨팅 리소스만 자동으로 시작하고 빠르고 간단한 컴퓨팅 프로비저닝으로 클라우드를 최대한 활용할 수 있도록 설계 되었습니다.
Karpenter는 AWS로 구축된 유연한 오픈 소스의 고성능 Kubernetes 클러스터 오토스케일러입니다. 애플리케이션 로드의 변화에 대응하여 적절한 크기의 컴퓨팅 리소스를 신속하게 실행함으로써 애플리케이션 가용성과 클러스터 효율성을 개선할 수 있습니다. 또한 Karpenter는 애플리케이션의 요구 사항을 충족하는 컴퓨팅 리소스를 적시에 제공하며, 앞으로 클러스터의 컴퓨팅 리소스 공간을 자동으로 최적화하여 비용을 절감하고 성능을 개선 할수 있습니다.
Karpenter 이전에는 Kubernetes 사용자가 Amazon EC2 Auto Scaling 그룹과 Kubernetes Cluster Autoscaler를 사용하는 애플리케이션을 지원하기 위해 클러스터의 컴퓨팅 파워를 동적으로 조정해야 했습니다. EKS를 사용하는 많은 고객들이 Kubernetes Cluster Autoscaler를 사용하여 클러스터 Auto Scaling을 구성하기가 어렵고 구성할 수 있는 범위가 제한적인 것에 대해 개선을 요구했습니다
Karpenter가 클러스터에 설치되면 Karpenter는 예약되지 않은 포드의 전체 리소스 요청을 관찰하고 새 노드를 시작하고 종료하는 결정을 내림으로써 예약 대기 시간과 인프라 비용을 줄입니다. 이를 위해 Karpenter는 Kubernetes 클러스터 내의 이벤트를 관찰한 다음 Amazon EC2와 같은 기본 클라우드 공급자의 컴퓨팅 서비스로 명령을 전송합니다.
Karpenter는 Apache License 2.0을 통해 라이선스가 부여되는 오픈 소스 프로젝트입니다. 모든 주요 클라우드 공급업체 및 온프레미스 환경을 포함하여, 모든 환경에서 실행되는 모든 Kubernetes 클러스터와 함께 작동하도록 설계되었습니다.
상세한 내용은 아래 URL을 참조하기 바랍니다.
https://karpenter.sh/
Karpenter 설치
1.환경설정 및 VPC 구성
ap-northeast-1 (도쿄) region에 새로운 Cluster를 설치하기 위한 환경변수를 설정합니다.
echo "export KARPENTER_ACCOUNT_ID=${ACCOUNT_ID}" | tee -a ~/.bash_profile
export KARPENTER_AWS_REGION=ap-northeast-1
echo "export KARPENTER_AWS_REGION=$KARPENTER_AWS_REGION" | tee -a ~/.bash_profile
KMS 를 ap-northeast-1 도쿄리전에서 사용할 수 있도록 설정합니다.
# kms 를 생성
aws kms create-alias --alias-name alias/k-eksworkshop --target-key-id --region ap-northeast-1 $(aws kms create-key --query KeyMetadata.Arn --region ap-northeast-1 --output text)
# kms 값을 환경변수에 저장합니다.
export K_MASTER_ARN=$(aws kms describe-key --key-id alias/k-eksworkshop --query KeyMetadata.Arn --region ap-northeast-1 --output text)
echo "export K_MASTER_ARN=${K_MASTER_ARN}" | tee -a ~/.bash_profile
echo $K_MASTER_ARN
### make env for the karpenter test
export KARPENTER_AWS_REGION=ap-northeast-1
export K_EKSCLUSTER_NAME=K1
export K_PUBLIC_MGMD_NODE="frontend"
export K_PRIVATE_MGMD_NODE="backend"
export KARPENTER_VERSION="v0.27.5"
export EKS_VERSION=1.25
echo ${K_EKSCLUSTER_NAME}
echo ${K_PUBLIC_MGMD_NODE}
echo ${K_PRIVATE_MGMD_NODE}
echo ${KARPENTER_VERSION}
echo "export KARPENTER_AWS_REGION=${KARPENTER_AWS_REGION}" | tee -a ~/.bash_profile
echo "export K_EKSCLUSTER_NAME=${K_EKSCLUSTER_NAME}" | tee -a ~/.bash_profile
echo "export K_PUBLIC_MGMD_NODE=${K_PUBLIC_MGMD_NODE}" | tee -a ~/.bash_profile
echo "export K_PRIVATE_MGMD_NODE=${K_PRIVATE_MGMD_NODE}" | tee -a ~/.bash_profile
#echo "export EKS_VERSION=${EKS_VERSION}" | tee -a ~/.bash_profile
echo "export KARPENTER_VERSION=${KARPENTER_VERSION}" | tee -a ~/.bash_profile
source ~/.bash_profile
eksctl을 사용해서 새로운 Cluster를 생성하기 위해, 앞서 구성한 VPC들의 주요 정보들을 환경 변수에 저장합니다.
### VPC 정보
cd ~/environment/
#VPC ID export
export K_VPC_ID=$(aws ec2 describe-vpcs --filters Name=tag:Name,Values=eksworkshop --region ap-northeast-1| jq -r '.Vpcs[].VpcId')
# VPC, Subnet ID 환경변수 저장
export K_PublicSubnet01=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PublicSubnet01/{print $1}')
export K_PublicSubnet02=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PublicSubnet02/{print $1}')
export K_PublicSubnet03=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PublicSubnet03/{print $1}')
export K_PrivateSubnet01=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PrivateSubnet01/{print $1}')
export K_PrivateSubnet02=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PrivateSubnet02/{print $1}')
export K_PrivateSubnet03=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$K_VPC_ID --region ap-northeast-1 | jq -r '.Subnets[]|.SubnetId+" "+.CidrBlock+" "+(.Tags[]|select(.Key=="Name").Value)' | awk '/eksworkshop-PrivateSubnet03/{print $1}')
echo "export K_VPC_ID=${K_VPC_ID}" | tee -a ~/.bash_profile
echo "export K_PublicSubnet01=${K_PublicSubnet01}" | tee -a ~/.bash_profile
echo "export K_PublicSubnet02=${K_PublicSubnet02}" | tee -a ~/.bash_profile
echo "export K_PublicSubnet03=${K_PublicSubnet03}" | tee -a ~/.bash_profile
echo "export K_PrivateSubnet01=${K_PrivateSubnet01}" | tee -a ~/.bash_profile
echo "export K_PrivateSubnet02=${K_PrivateSubnet02}" | tee -a ~/.bash_profile
echo "export K_PrivateSubnet03=${K_PrivateSubnet03}" | tee -a ~/.bash_profile
source ~/.bash_profile
kubectl get pods --namespace karpenter
kubectl get deployment -n karpenter
Karpenter Pod는 Controller와 Webhook을 담당하는 컨테이너가 배치되어 있습니다.
6.Provisioner 구성
Karpenter 구성은 Provisioner CRD(Custom Resource Definition) 형식으로 제공됩니다. 단일 Karpenter Provisioner는 다양한 Pod를 구성할 수 있습니다. Karpenter는 Label 및 Affinity와 같은 Pod의 속성을 기반으로 Scheduling 및 프로비저닝 결정을 할 수 있습니다. Karpenter는 다양한 노드 그룹을 관리할 필요가 없습니다.
아래 명령을 사용하여 기본 프로비저닝 도구를 만들기 위한 yaml을 정의합니다.이 프로비저닝 도구는 securityGroupSelector 및 subnetSelector를 사용하여 노드를 시작하는 데 사용되는 리소스를 검색합니다. 위의 eksctl 명령에 karpenter.sh/discovery 태그를 적용했습니다.
instanceProfile: Karpenter에서 시작한 인스턴스는 컨테이너를 실행하고 네트워킹을 구성하는 데 필요한 권한을 부여하는 InstanceProfile로 실행해야 합니다.
requirements : Provisioner CRD는 인스턴스 type 및 AZ와 같은 노드 속성을 선택할 수 있습니다. 예를 들어 topology.kubernetes.io/zone=ap-northeast-2a 레이블에 대한 응답으로 Karpenter는 해당 가용성 영역에 노드를 프로비저닝합니다. 이 예에서는 karpenter.sh/capacity-type을 설정하여 EC2 스팟 인스턴스를 사용합니다. 여기에서 사용할 수 있는 다른 속성을 확인할 수 있습니다. 이번 랩에서 몇 가지 더 작업할 것입니다.
limit : provisioner는 클러스터에 할당된 CPU 및 메모리 수의 제한을 정의할 수 있습니다. ttlSecondsAfterEmpty: 값은 빈 노드를 종료하도록 Karpenter를 구성합니다.
provider:tags : EC2 인스턴스가 생성될 때 가지게 되는 Tag를 정의할 수도 있습니다. 이것은 EC2 수준에서 Billing 및 거버넌스를 활성화하는 데 도움이 됩니다.
ttlSecondsAfterEmpty : 값은 Karpenter가 노드에 자원이 배치가 없는 경우 종료하도록 구성합니다.값을 정의하지 않은 상태로 두면 이 동작을 비활성화할 수 있습니다. 이 경우 빠른 시연을 위해 30초 값으로 설정했습니다.
7. 자동 노드 프로비저닝 1
Spot을 구동하기 위해 아래와 같이 EC2 Spot Service에 대한 설정을 합니다.
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
Deployment 하기 전에 웹브라우저에서 앞서 생성한 kube-ops-view를 열어두고 배포를 살펴 봅니다.
#go 설치
curl -LO https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.bashrc
source ~/.bashrc
go version
#EKS Node Viewer 설치 (2~3분 이상 소요)
mkdir -p ~/go/bin
go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@v0.5.0
#새로운 터미널에서 실행
cd ~/go/bin
./eks-node-viewer
아래와 같이 새로운 터미널에서 노드의 상태를 확인 할 수 있습니다.
Karpenter는 이제 활성화되었으며 노드 프로비저닝을 시작할 준비가 되었습니다. Deployment를 사용하여 Pod를 만들고 Karpenter가 노드를 프로비저닝하는 것을 확인해 봅니다.자동 노드 프로비저닝 이 배포는 pause image를 사용하고 replica가 없는 상태에서 시작합니다.
# Karpenter 테스트용 네임스페이스 생성
kubectl create namespace karpenter-inflate
# Deployment 정의 파일을 생성합니다.
cat << EOF > ~/environment/karpenter/karpenter-inflate1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate1 # Deployment 이름
namespace: karpenter-inflate # Deployment가 생성될 네임스페이스
spec:
replicas: 0 # 초기 Pod 개수 (Karpenter가 노드를 자동 프로비저닝할 때 증가시킬 예정)
selector:
matchLabels:
app: inflate1 # 이 Deployment의 Pod를 식별하는 라벨
template:
metadata:
labels:
app: inflate1 # 생성되는 Pod에 적용될 라벨
spec:
terminationGracePeriodSeconds: 0 # Pod 종료 대기 시간을 0으로 설정 (즉시 종료)
containers:
- name: inflate1
image: public.ecr.aws/eks-distro/kubernetes/pause:3.2 # 최소한의 리소스를 소비하는 pause 컨테이너 사용
resources:
requests:
cpu: 1 # 각 Pod가 1 vCPU를 요청 (Karpenter가 이에 맞는 노드를 자동 생성)
nodeSelector:
karpenter.sh/capacity-type: spot # Karpenter가 Spot 인스턴스를 선택하도록 강제
EOF
# 생성된 Deployment 정의를 Kubernetes에 적용 (Deployment 배포)
kubectl apply -f ~/environment/karpenter/karpenter-inflate1.yaml
2023-01-29T06:08:37.738Z DEBUG controller.provisioner discovered subnets {"commit": "5a7faa0-dirty", "subnets": ["subnet-092f608fed124f61a (ap-northeast-1c)", "subnet-0869716c80d96d66b (ap-northeast-1a)"]}
2023-01-29T06:08:37.858Z DEBUG controller.provisioner discovered EC2 instance types zonal offerings for subnets {"commit": "5a7faa0-dirty", "subnet-selector": "{\"karpenter.sh/discovery\":\"k-eksworkshop\"}"}
2023-01-29T06:08:38.048Z INFO controller.provisioner found provisionable pod(s) {"commit": "5a7faa0-dirty", "pods": 5}
2023-01-29T06:08:38.048Z INFO controller.provisioner computed new node(s) to fit pod(s) {"commit": "5a7faa0-dirty", "newNodes": 1, "pods": 5}
2023-01-29T06:08:38.048Z INFO controller.provisioner launching node with 5 pods requesting {"cpu":"5125m","pods":"7"} from types c5d.metal, r6id.16xlarge, r5d.8xlarge, c5.24xlarge, m5dn.8xlarge and 196 other(s) {"commit": "5a7faa0-dirty", "provisioner": "default"}
2023-01-29T06:08:38.236Z DEBUG controller.provisioner.cloudprovider discovered security groups {"commit": "5a7faa0-dirty", "provisioner": "default", "security-groups": ["sg-0ca6d78e6e9c402f9", "sg-0e58a7ca488f9b652", "sg-04976538139e6ff95", "sg-030cbd50908fc2e77"]}
2023-01-29T06:08:38.240Z DEBUG controller.provisioner.cloudprovider discovered kubernetes version {"commit": "5a7faa0-dirty", "provisioner": "default", "kubernetes-version": "1.22"}
2023-01-29T06:08:38.311Z DEBUG controller.provisioner.cloudprovider discovered new ami {"commit": "5a7faa0-dirty", "provisioner": "default", "ami": "ami-0db66a825cfe82f8f", "query": "/aws/service/eks/optimized-ami/1.22/amazon-linux-2/recommended/image_id"}
2023-01-29T06:08:38.477Z DEBUG controller.provisioner.cloudprovider created launch template {"commit": "5a7faa0-dirty", "provisioner": "default", "launch-template-name": "Karpenter-k-eksworkshop-8022396668081538733", "launch-template-id": "lt-081ac3b8a0f85f3af"}
2023-01-29T06:08:41.110Z INFO controller.provisioner.cloudprovider launched new instance {"commit": "5a7faa0-dirty", "provisioner": "default", "id": "i-0790ae0c76c905f84", "hostname": "ip-10-21-4-182.ap-northeast-1.compute.internal", "instance-type": "c5.2xlarge", "zone": "ap-northeast-1a", "capacity-type": "spot"}
kube-ops-view 에서도 신규 노드가 할당된 것을 확인 할 수 있습니다.
8. 자동 노드 프로비저닝 2
Karpenter Provisioner CRD를 새로운 형태로 만들어 봅니다.
이 구성은 특정 인스턴스 타입을 Taint와 Toleration 등을 조합하여, 적용해 보는 예제입니다.
인스턴스 타입 : C5.xlarge
Zone : ap-northeast-1a
인스턴스 Capa: On Demand
# Karpenter Provisioner 정의 파일을 생성합니다.
cat << EOF > ~/environment/karpenter/karpenter-provisioner2.yaml
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: provisioner2 # Provisioner 이름 (Karpenter가 노드를 자동 생성할 때 사용됨)
spec:
taints:
- key: cpuIntensive
value: "true"
effect: NoSchedule # 특정 Pod가 Toleration 없이 이 노드에서 실행되지 않도록 설정
labels:
phase: test2 # 노드가 생성될 때 추가되는 라벨
nodeType: cpu-node # CPU 집약적인 워크로드를 위한 노드 유형 태그
requirements:
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["c5.xlarge"] # 생성할 노드 유형을 c5.xlarge로 제한
- key: "topology.kubernetes.io/zone"
operator: In
values: ["ap-northeast-1a"] # ap-northeast-1a 가용 영역에서만 노드를 생성
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand"] # 온디맨드 인스턴스만 프로비저닝
limits:
resources:
cpu: 1000 # Karpenter가 프로비저닝할 총 CPU 리소스 제한 (1000 vCPU)
provider:
subnetSelector:
karpenter.sh/discovery: ${K_EKSCLUSTER_NAME} # Karpenter가 EKS 클러스터 서브넷을 자동으로 검색하도록 설정
securityGroupSelector:
karpenter.sh/discovery: ${K_EKSCLUSTER_NAME} # EKS 클러스터 보안 그룹을 자동으로 검색하도록 설정
ttlSecondsAfterEmpty: 30 # 노드가 30초 동안 사용되지 않으면 자동 종료
EOF
# 생성된 Karpenter Provisioner를 적용하여 노드 자동 프로비저닝을 활성화합니다.
kubectl apply -f ~/environment/karpenter/karpenter-provisioner2.yaml