▶ 지난 포스팅
https://ssunghwan.tistory.com/12
Karpenter 소개 [Part 01]
▶ 지난 포스팅AutoScaling [Part 01] — 하늘을 나는 펭귄 AutoScaling [Part 01]01. EKS (Elastic Kubernetes Service) 설치💁♂️ EKS 소개Kubernetes Control plane을 구성할 필요 없이 AWS에서 Kubernetes를 제공하는 관리형
ssunghwan.tistory.com
01. 배포 전 고려사항
💁♂️ 배포하기 이전에 Karpenter Controller Pod 배치 고려사항
■ Pod Anti-Affinity를 통한 분산 배치
- 동일한 핵심 Pod의 복제본들이 같은 Node에 스케줄링되지 않도록 Anti-Affinity 적용 필요.
- 더 나아가 preferredDuringSchedulingIgnoredDuringExecution 규칙을 사용하여 다른 가용영역(AZ)에 분산되도록 유도
■ Pod Disruption Budget (PDB) 설정
- Karpenter가 노드를 drain하거나 종료할 때, PDB는 해당 Pod들의 구성된 최소 개수만큼은 항상 실행 상태를 유지하도록 제약한다.
■ Liveness and Readiness Probes 구성
- Liveness Probe는 Pod의 비정상 상태를 감지하여 Pod를 재시작함으로써 복구하는 데 사용된다.
- Readiness Probe는 Pod이 서비스 요청을 받을 준비가 되었는지 여부를 확인하며, 준비되지 않은 Pod으로는 트래픽을 라우팅하지 않도록 한다.
위와 같이 적절한 kubernetes 표준 기능을 karpenter 환경에 적절히 적용하여 안정적인 Pod 운영을 해보자!
02. Karpenter 배포
■ Karpenter 버전 지정
릴리즈 노트를 참고하여 배포하려는 Karpenter 버전을 설정 (kubernetes 버전, helm 버전을 고려해야한다)
https://github.com/aws/karpenter-provider-aws/releases
Releases · aws/karpenter-provider-aws
Karpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity. - aws/karpenter-provider-aws
github.com
export KARPENTER_VERSION=v0.34.1
■ Namespace 및 CRD 생성
Kubernetes 확장 기능으로 사용자 정의 리소스를 생성하는데 사용된다. 이를 통해 노드의 프로비저닝 로직을 관리한다.
kubectl create namespace karpenter
kubectl create -f \
https://raw.githubusercontent.com/aws/karpenter-provider-aws/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_nodepools.yaml
kubectl create -f \
https://raw.githubusercontent.com/aws/karpenter-provider-aws/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml
kubectl create -f \
https://raw.githubusercontent.com/aws/karpenter-provider-aws/${KARPENTER_VERSION}/pkg/apis/crds/karpenter.sh_nodeclaims.yaml

■ Karpenter 배포 yaml 설정
parameter는 karpenter 공식 helm차트의 values.yaml을 참고해서 커스터마이징 가능.
https://github.com/aws/karpenter-provider-aws/blob/main/charts/karpenter/values.yaml
karpenter-provider-aws/charts/karpenter/values.yaml at main · aws/karpenter-provider-aws
Karpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity. - aws/karpenter-provider-aws
github.com
helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "${KARPENTER_NAMESPACE}" \
--set "settings.clusterName=${CLUSTER_NAME}" \
--set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole-${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 > karpenter-setup.yaml
■ NodeAffinity 항목 수정
수정 후 yaml을 apply 하도록 하자.
vi karpenter.yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: karpenter.sh/nodepool
operator: DoesNotExist
- matchExpressions:
- key: eks.amazonaws.com/nodegroup
operator: In
values:
- ${NODEGROUP}
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
kubectl apply -f karpenter-setup.yaml
AS-IS

To-BE

■ Karpenter Controller pod 상태 확인

03. CRD 사용
Provisioner CRD를 사용하여 프로비저닝을 단순화 해보자.
💁♂️ CRD (Custom Resource Definition)란?
- Kubernetes의 확장기능이며, API를 사용자 정의 리소스로 확장하는데 사용된다.
- Karpenter는 Provisioners라는 CRD를 사용하여 프로비저닝 규칙과 정책 지정한다.
💁♂️ CRD 역할 및 특징
Karpenter는 kubernetes api에 아래와 같은 3가지 리소스 타입을 추가하여 사용한다.
- NodePool 리소스를 생성하여 프로비저닝 할 노드의 요구사항 Template 기준을 정의한다.
- Resource 제한, 노드 taints 등과 Kapenter가 어떤 EC2 인스턴스를 프로비저닝할지 결정하는데 사용되는 Template 기준을 정의
- EC2NodeClass 리소스를 생성하여 프로비저닝 될 EC2 인스턴스의 구체적인 세부사항을 정의한다.
- 세부사항에는 Tag, Key Pair, AMI, Security Group, 사용자 데이터, 인스턴스 타입 등을 포함한다.
- NodeClaim은 Pod가 필요로 하는 리소스 요구사항을 기반으로 노드를 프로비저닝 하는 요청한다.
- 즉, 파드의 요구사항을 충족하는 새로운 노드를 생성하거나, 이미 존재하는 노드를 찾는 역할
■ 동작 순서
- NodePool 생성 → NodePool에 EC2NodeClass 지정
- Pod가 스케줄링 되어야 할 때 Karpenter는 해당 Pod의 요구사항을 분석
- Karpenter는 해당 Pod의 요구사항에 맞는 NodeClaim을 생성
- NodeClaim은 요구사항을 충족하는 노드를 찾거나, 새로운 노드를 프로비저닝 하게 되는데 이때 NodePool과 EC2NodeClass를 참조하여 노드를 프로비저닝한다.
- 프로비저닝된 노드에 파드가 스케줄링 된다.
■ 참고 사항
Karpenter version v0.31.0 이후 : 최신버전이므로 NodePool, EC2NodeClass 로 명칭이 바뀌었음.
Karpenter version v0.30.0 이전 : 구 버전이므로 Provisioners, AWS Node Templates 라는 명칭을 쓰고있음.
3-1. Provisioner 생성
NodeGroup Provisining에 대한 사용자 정의 방법을 이해하기 위해 manifest 작성 예시는 아래와 같다.
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: karpenter-nodepool
spec:
template:
spec:
requirements:
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["t"]
# instance type을 정의 여러개를 한번에 정의할 수 있습니다.
- key: "node.kubernetes.io/instance-type"
operator: In
values: ["t2.medium", "t2.large"]
# WorkerNode를 생성할 Zone
- key: "topology.kubernetes.io/zone"
operator: In
values: ["ap-northeast-2a", "ap-northeast-2c"]
# on-demand, spot 중 원하는 인스턴스를 선언, 둘다 정의할 수 있습니다.
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand"]
- key: "kubernetes.io/os"
operator: In
values: ["linux"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
name: karpenter-ec2nodeclass
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: karpenter-ec2nodeclass
spec:
# WorkerNode AMI
amiFamily: AL2
# WorkerNode Role Name
role: "KarpenterNodeRole-${CLUSTER_NAME}"
# subnet, securityGroup tag
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}"
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}"
EOF
💁♂️ Karpenter Provisinoers & Node Templates에 대한 스펙 설명
spec.provider.subnetSelector: karpenter.sh/discovery 태그가 있는 서브넷, 보안그룹을 선택하도록 지시.
spec.ttlSecondsAfterEmpty: 노드가 비어 있으면 120초 후에 종료되도록 설정.
spec.limits.resources.cpu: 프로비저닝할 노드의 CPU 제한을 1000으로 설정.
spec.instanceProfile: 프로비저닝할 노드에 연결할 IAM 인스턴스 프로파일의 ARN을 지정.
spec.requirements: "on-demand" 인스턴스만 프로비저닝하도록 요구사항을 설정.
spec.topologyAwareHints: 노드를 여러 가용 영역에 분산시키도록 지시.
spec.labels: t2 인스턴스 유형만 프로비저닝하도록 지시.
spec.taints: 웹 서비스만 이 노드에 스케줄링되도록 노드에 테인트를 추가.
spec.ttlSecondsUntilExpired: 노드가 사용되지 않으면 180초 후에 삭제되도록 설정.
04. Node Scheduling
■ 테스팅을 위한 deployment yaml 생성 후 배포
vi scaleout.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: scaleout
spec:
replicas: 4
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: 0.5
kubectl apply -f scaleout.yaml
★★ 노드 스케줄링 변화 추이
- Replicas가 4인 Deployment를 실행시켰고, Pods 2개가 Pending 상태임을 확인

- Karpenter Controller가 새로운 Node가 필요한 Pods들을 찾음 → 새로운 Node가 성공적으로 생성

- 새로운 Node 한 개가 추가 되었고, 이로 인해 2개의 Pending 상태였던 Pods가 Running 상태로 변화

- Pods를 추가적으로 늘려도 Node가 생성되면서 자동으로 프로비저닝된다.
- kubectl scale deployment scaleout —replicas=8

- 반대로 Pods 수를 줄여볼까?
- kubectl scale deployment scaleout —replicas=3

💁♂️ 결과 확인
- Karpenter Controller가 리소스 사용을 최적화 하기 위해 특정 Node에 taint를 걸었다.
- 파드가 해당 Node에 스케줄링 되지 않게 동작함.
- Karpenter가 해당 Node를 삭제시킴 → 해당 Node에 대한 NodeClaim 삭제
- 클러스터 용량을 성공적으로 제어하였음.

이상 Karpenter에 대해서 소개해보았고, 신 기술인 만큼 Karpenter 친구에 대해 이해하기에 복잡하네요 ^^..
궁금하신점, 어려운점 같이 소통하면서 배워봐요!