https://catalog.workshops.aws/karpenter/en-US/basic-nodepool
Karpenter Workshop (Immersion Day)
This is an advanced 300-400 level workshop to showcase advanced for Karpenter, If you want a 200-300 level of Karpenter usecases then please use EKS Immersion workshop https://catalog.workshops.aws/eks-immersionday/en-US.
catalog.workshops.aws
해당 워크샵 내용을 쭉 따라하면서 정리해보았습니다. 대부분 NodePool을 설정하는 내용으로 잘 익혀두면 Karpenter를 효율적으로 사용할 수 있을거라 생각합니다.
NodePool 이란?
karpenter에서 NodePool은 클러스터에서 Karpenter의 동작을 구성할 수 있는 쿠버네티스의 CRD 입니다.
NodePool의 역할
리소스가 더 필요하면 노드를 추가하고, 필요하지 않다면 노드를 제거합니다. 해당 풀을 통해서 Karpenter가 생성할 수 있는 노드 풀을 가지게되고 파드의 제약조건도 설정할 수 있습니다.
EC2NodeClass를 참조해서 spec.template.spec.nodeClassRef 에 지정하여 사용할 수 있습니다.
NodePool 설정은 다음과 같이할 수 있습니다.
- strengthenAfter : 사용률이 낮아 노드를 축소하기 위해 기다리는 시간
- requirements : 프로비저닝된 노드의 매개변수를 제한하는 요구사항으로 pod.spec.affinity.nodeAffinity 규칙과 같이 결합됩니다.
- label : 모든 노드에 적용되는 임의의 키 값이다. nodeSelector로 쓸 수 있습니다.
- nodeClassRef : 클라우드 공급자별 사용자 정의 리소스를 참조합니다.
- limits : 클러스터 전체 크기에 대한 리소스 제한이다. 해당 제한을 초과하면 새 인스턴스를 만들지 못합니다.
다음 예시를 통해서 NodePool을 만들어보자.
cd ~/environment/karpenter
cat <<EoF> basic.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
metadata:
labels:
eks-immersion-team: my-team
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
expireAfter: Never
requirements:
- key: "karpenter.k8s.aws/instance-generation" # Filter out older instance types
operator: Gt
values: ["2"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type # If not included, the webhook for the AWS cloud provider will default to on-demand
operator: In
values: ["on-demand"]
- key: kubernetes.io/os
operator: In
values: ["linux"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
limits:
cpu: "10"
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: karpenterNodeRole-${CLUSTER_NAME}
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: ${CLUSTER_NAME}
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: ${CLUSTER_NAME}
tags:
intent: apps
EoF
kubectl apply -f basic.yaml
애플리케이션 스케일링
파드가 생성되면 스케쥴러가 클러스터의 노드에 파드를 스케쥴링 한다. 파드를 수용할 수 있는 노드가 없다면 노드가 사용 가능해질 때까지 Pending 상태로 유지하게 되는데, 이때 Karpenter가 모니터링하다가 요구사항을 충족시키기 위해 새 노드를 프로비저닝하여 클러스터를 확장시킬 수 있다.
샘플 애플리케이션 배포
- 컨테이너 리소스 request는 cpu 1로 설정한다.
- NodePool을 만들때 eks-immersion-team: my-team label을 추가했으므로 이제 nodeSelector에 의해 해당 nodePool에서 만들어진 인스턴스에 추가되도록 한다.
cd ~/environment/karpenter
cat <<EoF> basic-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
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
nodeSelector:
eks-immersion-team: my-team
EoF
kubectl apply -f basic-deploy.yaml
현재는 노드가 3개 존재하고 있다.
현재 클러스터에 몇 개의 노드가 있는지 확인한 뒤, 스케일 적용을 해보자. pod가 1개 증가하면서 5개의 pending 파드가 실행된 것을 확인할 수 있다.
kubectl scale deployment -n workshop inflate --replicas 5
karpenter에 의해서 생성된 노드이므로 노드풀 규칙에 의해 my-team 라벨링이 붙었다.
kubectl get nodes -l eks-immersion-team=my-team
다음 명령어를 통해서 karpenter 로그를 확인해볼 수 있다.
kubectl -n $KARPENTER_NAMESPACE logs -l app.kubernetes.io/name=karpenter | grep launched | jq -s
리소스 제한 설정
애플리케이션이 필요로 하는 리소스를 미리 설정해놓으면, Karpenter가 그 한도 내에서 자원을 자동으로 할당해줍니다. 만약 애플리케이션이 설정한 한도를 넘으면 어떻게 될지도 같이 봅시다.
애플리케이션을 12개로 늘리기
replicas를 12개로 늘려 Nodepool의 한도를 초과해봅시다.
kubectl scale deployment -n workshop inflate --replicas 12
기존 노드의 limits 설정이 cpu:10 이였으므로 노드를 추가하였더라도 pending 파드가 존재하게된다.
kubectl -n workshop get pods| grep Pending
카펜터 로그 확인
NodePool의 제한이 "10"인 상황에서 이미 두 노드가 이를 초과했으므로 더이상 karpenter는 노드에 프로비저닝 할 수 없게됩니다.
kubectl -n $KARPENTER_NAMESPACE logs -l app.kubernetes.io/name=karpenter
이런 로그들을 확인할 수 있다.
"error":"all available instance types exceed limits for nodepool
Disruption (분열, 중단)
Karpenter는 필요없는 노드를 자동으로 정리하는 기능도 있는데 이것은 consolidateAfter이라는 설정으로 진행합니다. 이 설정은 노드가 비어있을때 얼마나 기다렸다가 정리할지를 나타내는 설정입니다.
설정은 spec.disruption에서 진행하면 됩니다.
지금 한 설정은 노드가 비어있으면 30초 후에 정리하고, 노드가 완전히 비었을때만 정리하라는 옵션이 있습니다.
spec:
disruption:
consolidateAfter: 30s
consolidationPolicy: WhenEmpty
template:
spec:
expireAfter: Never
실습
애플리케이션 줄이기
일단 애플리케이션의 replicas 수를 0으로 줄여 노드에 실행중인 파드를 모두 제거해보자.
kubectl scale deployment -n workshop inflate --replicas 0
그리고 노드가 비어있게되니 consolidateAfter 30초 설정에 따라 30초 후 노드를 제거하게 된다.
Karpenter 로그에서도 확인해보자
kubectl -n karpenter logs -l app.kubernetes.io/name=karpenter
# 안될경우
kubectl -n karpenter logs -f deployment/karpenter --all-containers=true --since=20m | grep -i disruption
다음과 같이 로그가 보인다면 잘작동한 것이다.
"message": "disrupting nodeclaim(s) via delete"
노드도 다시 3개로 줄었습니다.
Drift (노드 자동 교체)
이 기능은 노드가 사용하는 AMI가 설정된 AMI와 다를 경우 이를 감지하고 자동으로 노드를 교체해주는 기능입니다. 예를 들어 노드가 구버전 AMI를 쓰고있다면 최신 버전으로 바꾸는 기능을 할 수 있습니다.
동작원리
karpenter는 노드의 AMI가 EC2NodeClass에 설정된 AMI와 맞지 않으면 노드에 drifted라는 표시를 붙입니다. 그러면 노드를 정리하고 종료한 뒤 새 노드를 띄웁니다. 다만 파드가 Pod Disruption Budget(PDB)나 karpenter.sh/do-not-disrupt: "true" 설정을 가지고 있다면 노드 종료를 막아줍니다.
실습
kubernetes 1.29 AMI를 노드로 만들고 1.30 AMI로 변경하는 실습을 해보자.
현재 클러스터버전보다 하나 낮은 버전의 AMI를 가져옵니다.
export OLD_VERSION=$(kubectl version --output=json | jq -r '.serverVersion.major + "." + ((.serverVersion.minor | rtrimstr("+") | tonumber) - 1 | tostring)')
export AMI_OLD=$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/$OLD_VERSION/amazon-linux-2023/x86_64/standard/recommended/image_id --region $AWS_REGION --query "Parameter.Value" --output text)
echo "AMI ID for EKS Version $OLD_VERSION is $AMI_OLD"
나의 경우 노드가 1.30이므로 1.29 AMI 정보를 가져왔다.
구버전으로 EC2NodeClass 생성
oldnode_class.yaml을 생성해 1.29의 AMI로 사용합니다.
cd ~/environment/karpenter
cat << EoF > oldnode_class.yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: oldnode
spec:
amiFamily: AL2023
amiSelectorTerms:
- id: $AMI_OLD
role: karpenterNodeRole-$CLUSTER_NAME
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
tags:
intent: apps
EoF
kubectl -f oldnode_class.yaml create
기존 NodePool이 oldnode를 사용하도록 합니다.
kubectl patch nodepool default --type='json' -p '[{"op": "replace", "path": "/spec/template/spec/nodeClassRef/name", "value":"oldnode"}]'
샘플 앱 파드를 5개 띄웁니다.
cd ~/environment/karpenter
cat << EoF > drift-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
spec:
replicas: 5
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
nodeSelector:
eks-immersion-team: my-team
EoF
kubectl apply -f drift-deploy.yaml
새로운 노드가 1.29버전으로 생성되었는지 확인해봅니다. 잘 생성되었네요.
kubectl get nodes -l eks-immersion-team=my-team
자 이제 최신 1.30버전으로 변경해봅시다.
export AMI_NEW=$(aws ssm get-parameter --name /aws/service/eks/optimized-ami/1.30/amazon-linux-2023/x86_64/standard/recommended/image_id --region $AWS_REGION --query "Parameter.Value" --output text)
echo "1.30=$AMI_NEW"
이걸로 새 EC2NodeClass를 만듭니다.
cd ~/environment/karpenter
cat << EoF > newnode_class.yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: newnode
spec:
amiFamily: AL2023
amiSelectorTerms:
- id: $AMI_NEW
role: karpenterNodeRole-$CLUSTER_NAME
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
tags:
intent: apps
EoF
kubectl -f newnode_class.yaml create
기본 nodePool을 새 newnode로 변경합니다.
kubectl patch nodepool default --type='json' -p '[{"op": "replace", "path": "/spec/template/spec/nodeClassRef/name", "value":"newnode"}]'
kubectl get nodes -l eks-immersion-team=my-team
잠시만 기다리면 바로 새로운 노드가 1.30버전으로 뜨면서 교체되는 것을 확인할 수 있다.
Drift 관련 로그 확인
이 명령어를 통해서 손쉽게 확인할 수 있다.
kubectl -n $KARPENTER_NAMESPACE logs -l app.kubernetes.io/name=karpenter | grep -i drift
관련 리소스 삭제
# deployment 삭제
kubectl delete deployment inflate -n workshop
# NodePool 원래대로
kubectl patch nodepool default --type='json' -p '[{"op": "replace", "path": "/spec/template/spec/nodeClassRef/name", "value":"default"}]'
# EC2NodeClass 삭제
kubectl delete ec2nodeclass oldnode
kubectl delete ec2nodeclass newnode
Rightsizing (자원 최적화)
이 기능은 파드에 필요한 자원을 정확하게 맞춰 리소스 낭비를 줄이고, 애플리케이션 성능을 높이는데 효과가 있는 기능이다. 파드의 리소스 Request와 NodePool 설정을 보고 딱 맞는 노드에 띄워준다.
여기서 특별한 기능은 Karpenter가 파드가 요청한 리소스와 NodePool의 한도를 보고 가장 적합한 노드를 자동으로 선택해준다는 것이다.
실습
이번 실습에서는 파드8개를 배포하고, karpenter가 어떻게 선택하는지 확인해보겠습니다.
먼저 karpenter가 띄운노드가있는지 확인해봅니다.
eks-node-viewer --node-selector "eks-immersion-team" --resources cpu,memory
이렇게 나오면 준비완료. 즉 karpenter가 띄운노드는 없어야한다.
Waiting for update or no nodes found...
inflate 라는 이름의 파드 8개를 배포하겠습니다. 각 파드는 CPU 1개에 메모리 1Gi 요청입니다.
cd ~/environment/karpenter
cat <<EoF> basic-rightsizing.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
spec:
replicas: 8
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:
memory: 1Gi
cpu: 1
nodeSelector:
eks-immersion-team: my-team
EoF
kubectl apply -f basic-rightsizing.yaml
Rightsizing 확인
karpenter가 어떤 노드를 선택했는지 로그를 확인해봅시다.
kubectl -n $KARPENTER_NAMESPACE logs -l app.kubernetes.io/name=karpenter
로그에서는 karpenter는 특히 nodeclaim 쪽을 보면 좋습니다. 내용을 읽어보면 c4.2xlarge를 비롯한 27개의 인스턴스 타입을 고민해보고 생성한 것을 확인할 수 있습니다.
{"level":"INFO","time":"2025-03-08T16:16:45.870Z","logger":"controller","message":"created nodeclaim","commit":"058c665","controller":"provisioner","namespace":"","name":"","reconcileID":"42d43823-a6a8-4159-9989-f894c67cb7ab","NodePool":{"name":"default"},"NodeClaim":{"name":"default-b55vr"},"requests":{"cpu":"1150m","memory":"1Gi","pods":"4"},"instance-types":"c4.large, c5.large, c5a.large, c5d.large, c5n.large and 27 other(s)"}
{"level":"INFO","time":"2025-03-08T16:16:45.874Z","logger":"controller","message":"created nodeclaim","commit":"058c665","controller":"provisioner","namespace":"","name":"","reconcileID":"42d43823-a6a8-4159-9989-f894c67cb7ab","NodePool":{"name":"default"},"NodeClaim":{"name":"default-wm2cd"},"requests":{"cpu":"7150m","memory":"7Gi","pods":"10"},"instance-types":"c4.2xlarge, c5.2xlarge, c5a.2xlarge, c5d.2xlarge, c5n.2xlarge and 27 other(s)"}
{"level":"INFO","time":"2025-03-08T16:16:47.758Z","logger":"controller","message":"launched nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-wm2cd"},"namespace":"","name":"default-wm2cd","reconcileID":"a77bd992-176e-4a9e-b4f1-0e498f8aa49d","provider-id":"aws:///ap-northeast-2b/i-0537bacef5f471d99","instance-type":"c5a.2xlarge","zone":"ap-northeast-2b","capacity-type":"on-demand","allocatable":{"cpu":"7910m","ephemeral-storage":"17Gi","memory":"14162Mi","pods":"58","vpc.amazonaws.com/pod-eni":"38"}}
{"level":"INFO","time":"2025-03-08T16:16:48.104Z","logger":"controller","message":"launched nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-b55vr"},"namespace":"","name":"default-b55vr","reconcileID":"010936af-f79f-4fc6-9914-c6fb40b29f85","provider-id":"aws:///ap-northeast-2a/i-00d9c7923340fc312","instance-type":"c5a.large","zone":"ap-northeast-2a","capacity-type":"on-demand","allocatable":{"cpu":"1930m","ephemeral-storage":"17Gi","memory":"3114Mi","pods":"29","vpc.amazonaws.com/pod-eni":"9"}}
{"level":"INFO","time":"2025-03-08T16:17:06.166Z","logger":"controller","message":"registered nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-wm2cd"},"namespace":"","name":"default-wm2cd","reconcileID":"78bbdecb-ab17-489d-bbd5-8826437c7feb","provider-id":"aws:///ap-northeast-2b/i-0537bacef5f471d99","Node":{"name":"ip-192-168-69-66.ap-northeast-2.compute.internal"}}
{"level":"INFO","time":"2025-03-08T16:17:09.736Z","logger":"controller","message":"registered nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-b55vr"},"namespace":"","name":"default-b55vr","reconcileID":"a8051220-9b83-4356-bc8d-e9cd16e8a4e9","provider-id":"aws:///ap-northeast-2a/i-00d9c7923340fc312","Node":{"name":"ip-192-168-134-84.ap-northeast-2.compute.internal"}}
{"level":"INFO","time":"2025-03-08T16:17:15.118Z","logger":"controller","message":"initialized nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-wm2cd"},"namespace":"","name":"default-wm2cd","reconcileID":"7d39c2ae-e166-4731-ad4c-cba1dc70fb01","provider-id":"aws:///ap-northeast-2b/i-0537bacef5f471d99","Node":{"name":"ip-192-168-69-66.ap-northeast-2.compute.internal"},"allocatable":{"cpu":"7910m","ephemeral-storage":"18181869946","hugepages-1Gi":"0","hugepages-2Mi":"0","memory":"15140112Ki","pods":"58"}}
{"level":"INFO","time":"2025-03-08T16:17:25.869Z","logger":"controller","message":"initialized nodeclaim","commit":"058c665","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"default-b55vr"},"namespace":"","name":"default-b55vr","reconcileID":"7ba18b5c-cd0e-4d0d-b4cf-2681cb10b290","provider-id":"aws:///ap-northeast-2a/i-00d9c7923340fc312","Node":{"name":"ip-192-168-134-84.ap-northeast-2.compute.internal"},"allocatable":{"cpu":"1930m","ephemeral-storage":"18181869946","hugepages-1Gi":"0","hugepages-2Mi":"0","memory":"3229360Ki","pods":"29"}}
Karpenter가 띄운 노드 확인
eks-node-viewer --node-selector "eks-immersion-team" --resources cpu,memory
Weighting NodePool(우선순위 정하기)
.spec.weight 값을 지정해 karpenter가 어떤 nodepool을 먼저 사용할지 정할 수 있습니다. 특정 인스턴스 타입을 선호하거나 비용을 줄이고 싶을때 유용하게 사용됩니다.
가중치(Weighting)은 여러 NodePool 중 어떤 걸 먼저 쓸지 순서를 정하는 것으로 weight값이 높은 nodepool을 먼저 사용합니다.
실습 (Weighting NodePool 테스트하기)
nodePool을 확인합니다. 현재는 기본 nodepool만 있어 weight값이 없습니다.
kubectl get nodepools.karpenter.sh -o wide
weight nodepool 배포
먼저 weight:2로 우선순위를 높게 설정하고, 인스턴스 타입은 t 계열로 지정해보겠습니다.
mkdir -p ~/environment/karpenter
cd ~/environment/karpenter
cat <<EoF> weight-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: weight-nodepool
spec:
disruption:
consolidateAfter: 30s
consolidationPolicy: WhenEmpty
limits:
cpu: "50"
template:
metadata:
labels:
eks-immersion-team: weight-nodepool
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: weight-nodepool
requirements:
- key: karpenter.k8s.aws/instance-generation # Filter out older instance types
operator: Gt
values:
- "2"
- key: karpenter.k8s.aws/instance-category
operator: In
values:
- t
- key: kubernetes.io/arch
operator: In
values:
- amd64
- key: kubernetes.io/os
operator: In
values:
- linux
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
expireAfter: Never
weight: 2
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: weight-nodepool
spec:
amiSelectorTerms:
- alias: al2023@latest
role: "KarpenterNodeRole-${CLUSTER_NAME}"
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
tags:
intent: apps
managed-by: karpenter
EoF
kubectl apply -f weight-nodepool.yaml
다시 조회해봅니다. weight 값이 2인 노드가 생겼습니다.
이제 파드 8개를 배포해 새 노드를 만들어보겠습니다.
cd ~/environment/karpenter
cat <<EoF> weight-workload.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
spec:
replicas: 8
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
containers:
- name: inflate
image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
resources:
requests:
memory: 1Gi
cpu: 1
EoF
kubectl apply -f weight-workload.yaml
노드를 확인해보면 t3a.2xlarge로 추가된 것을 확인할 수 있습니다.
만든 리소스를 삭제합니다.
# 파드삭제
kubectl -n workshop delete deployment inflate
# 노드풀 정리
kubectl delete nodepool.karpenter.sh weight-nodepool
kubectl delete ec2nodeclass.karpenter.k8s.aws weight-nodepool
Gravition
Gravition은 ARM기반의 프로세서로 컨테이너 워크로드에서 뛰어난 성능과 비용이 강점입니다. Karpenter는 Graviton 인스턴스를 쉽게 띄워 애플리케이션을 실행할 수 있게합니다. Gravition의 특징과 노드를 만드는 과정을 실습합니다.
Gravition을 모두 지원하는 NodePool 생성
mkdir -p ~/environment/karpenter
cd ~/environment/karpenter
cat <<EoF | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 0s
limits:
cpu: 1k
memory: 1000Gi
template:
metadata:
labels:
intent: apps
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.k8s.aws/instance-generation # Filter out older instance types
operator: Gt
values:
- "2"
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
expireAfter: 720h
weight: 100
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: KarpenterNodeRole-$CLUSTER_NAME
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
tags:
intent: apps
eks: $CLUSTER_NAME
EoF
파드 2개를 배포할때 arm64 아키텍처를 강제합니다.
cd ~/environment/karpenter
cat <<EoF > inflate.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
spec:
replicas: 2
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
nodeSelector:
intent: apps
kubernetes.io/arch: arm64
containers:
- image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
name: inflate
resources:
requests:
cpu: "1"
memory: 256M
EoF
kubectl apply -f inflate.yaml
graviton 노드 확인
eks-node-viewer --node-selector intent --resources cpu,memory
실제로 아키텍처를 확인해보자. kernel-version이 aarch64면 graviton노드 이다. 여기서 확인할 수 있었다.
kubectl get nodes -o wide
On-Demand & Spot Ratio Split(온디맨드와 스팟 비율 나누기)
즉 온디맨드 타입과 스팟타입의 비율을 조절하는 것이다. capacity-spread와 NodePool 설정으로 노드 비율을 조절할 수 있다.
실습
실습은 온디맨드 20%, 스팟 80%로 조절해보자.
일단 먼저 깨끗하게 비우고 시작하자.
kubectl delete deployment -n workshop inflate
kubectl delete nodepool.karpenter.sh default
kubectl delete ec2nodeclass.karpenter.k8s.aws default
노드를 확인하자. 없어야한다.
eks-node-viewer --node-selector "eks-immersion-team" --resources cpu,memory
노드풀 배포
온디맨드와 스팟 두 개의 Nodepool을 설정하고 capacity-spread로 나눈다.
mkdir ~/environment/karpenter
cd ~/environment/karpenter
cat <<EoF> ratiosplit.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: on-demand
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 0s
limits:
cpu: "100"
template:
metadata:
labels:
eks-immersion-team: my-team
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.k8s.aws/instance-generation # Filter out older instance types
operator: Gt
values:
- "2"
- key: karpenter.k8s.aws/instance-category
operator: In
values:
- c
- m
- r
- key: capacity-spread
operator: In
values:
- "1"
- key: kubernetes.io/arch
operator: In
values:
- amd64
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
- key: kubernetes.io/os
operator: In
values:
- linux
expireAfter: Never
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 0s
limits:
cpu: "100"
template:
metadata:
labels:
eks-immersion-team: my-team
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.k8s.aws/instance-generation # Filter out older instance types
operator: Gt
values:
- "2"
- key: karpenter.k8s.aws/instance-category
operator: In
values:
- c
- m
- r
- key: capacity-spread
operator: In
values:
- "2"
- "3"
- "4"
- "5"
- key: kubernetes.io/arch
operator: In
values:
- amd64
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- key: kubernetes.io/os
operator: In
values:
- linux
expireAfter: Never
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: "KarpenterNodeRole-${CLUSTER_NAME}"
securityGroupSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
subnetSelectorTerms:
- tags:
alpha.eksctl.io/cluster-name: $CLUSTER_NAME
tags:
intent: apps
eks: $CLUSTER_NAME
eks-immersion-team: my-team
EoF
kubectl apply -f ratiosplit.yaml
이쪽을 중심으로 봐야하는데 on-demand의 경우 1, spot의 경우 2, 3, 4, 5를 가져간다.
- key: capacity-spread
operator: In
values: ["2", "3", "4", "5"]
파드 5개를 배포해보자. 1:4로 비율이 나눠져야한다.
cd ~/environment/karpenter
cat <<EoF> capacity-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
namespace: workshop
spec:
replicas: 5
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: capacity-spread
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: inflate
terminationGracePeriodSeconds: 0
containers:
- name: inflate
image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
resources:
requests:
cpu: 1
nodeSelector:
eks-immersion-team: my-team
EoF
kubectl apply -f capacity-deploy.yaml
잘보면 스팟 4개에, 온디맨드 1개인것을 볼 수 있다.
이제 replicas를 10개로 해보자. 역시 spot 8개에, ondemand 2개가 나와야할 것이다.
벌써 생기고 있는것만 봐도 정확하게 맞는것을 볼 수 있다.
kubectl scale deployment -n workshop inflate --replicas 10
eks-node-viewer --node-selector "eks-immersion-team" --resources cpu,memory
실습이 끝났으면 리소스는 삭제하도록 하자.
kubectl delete deployment inflate -n workshop
kubectl delete nodepool on-demand
kubectl delete nodepool spot
kubectl delete ec2nodeclass.karpenter.k8s.aws default