Karpenter를 활용한 Kubernetes Node 관리
Karpenter?
Karpenter는 Kubernetes 클러스터의 노드 수명 주기를 효율적으로 관리하기 위해 설계된 오픈 소스 프로젝트로, 애플리케이션의 요구 사항에 따라 노드를 자동으로 프로비저닝하고, 불필요한 노드를 제거하여 클러스터의 효율성과 비용 최적화가 가능하다.
- 📖 공식 문서
Install
Karpenter의 설치는 비교적 간단하며, 공식 가이드를 따라 클러스터에 배포할 수 있다.
설치의 경우, 사용하는 환경에 따라 다르기 때문에 해당 문서에서는 아래 가이드로 대체
- 📖 설치 가이드
Concept
Karpenter의 주요 기능은 다음과 같다.
NodeClass
nodeClass를 정의하여, 실행되는 Kubernetes Node (EC2)가 어떤 구성을 가지고 실행되어야 하는지 설정할 수 있다.
구성 할 수 있는 스펙들은 EC2에 설정되어야 하는 설정들과 비슷하다. 주요 설정은 다음과 같음.
- Subnet
- Security Group
- Instance Profile
- AMI
- Block Device
- User-data
NodeClass 예제
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}"
environment: test
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: "${CLUSTER_NAME}"
environment: test
amiFamily: AL2
instanceProfile: KarpenterNodeInstanceProfile-${CLUSTER_NAME}
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
encrypted: true
tags:
Name: ${CLUSTER_NAME}-karpenter-node
Environment: Production
spec.kubelet 구성
node에서 실행되는 kubelet 구성을 사용자 정의할 수 있다. (이전 버전에서는 nodePool에서 정의할 수 있었던 것이 최신 버전에서 nodeClass로 변경되었다.)
spec:
...
kubelet:
clusterDNS: ["10.0.1.100"]
systemReserved:
cpu: 100m
memory: 100Mi
ephemeral-storage: 1Gi
kubeReserved:
cpu: 200m
memory: 100Mi
ephemeral-storage: 3Gi
evictionHard:
memory.available: 5%
nodefs.available: 10%
nodefs.inodesFree: 10%
evictionSoft:
memory.available: 500Mi
nodefs.available: 15%
nodefs.inodesFree: 15%
evictionSoftGracePeriod:
memory.available: 1m
nodefs.available: 1m30s
nodefs.inodesFree: 2m
evictionMaxPodGracePeriod: 60
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
cpuCFSQuota: true
podsPerCore: 2
maxPods: 20
systemReserved
- 파드가 아닌 os 시스템에 사용되는 데몬 (sshd, udev 등) 을 위해 리소스를 예약해두는 옵션
kubeReserved
kubeReserved
,container runtime
,node problem detector
을 위해 자원을 예약해두는 것
evictionHard
/evictionSoft
- 가용 용량이 해당 설정 아래로 떨어지면 커널 OOM 등을 방지하기 위해 파드를 제거 함
- 하드는 즉시 제거, 소프트는 GracePeriod 시간동안 기다려 줌.
evictionSoftGracePeriod
- 소프트 이빅션에 대한 유예 시간 설정
podsPerCore
- 파드의 밀도를 vcpu로 제한
maxPods
- 파드의 밀도를 개수로 제한
AMI
AMI는 특정 AMI를 선택하거나, 혹은 AMI Family를 지정할 수 있다.
AMI Family는 아래 항목 지원
AL2
- AWS에서 관리하는 amazon linux 2로, ami 관리에 대한 부담이 적음
Bottlerocket
- 컨테이너 환경에 최적화된 리눅스 기반 오픈소스 운영체제로, 최소 소프트웨어로 구성되어있어, 보안이 강화되고 가볍기 때문에 빠르게 프로비저닝이 가능
Ubuntu
Custom
AMI Update
managed node group을 사용하면 cluster upgrade 시, node도 rolling update가 되지만, karpenter node는 그렇지 않다. karpenter node의 ami를 최신 버전으로 변경하는 방법은 아래 2가지가 있다.
- nodepool의
expireAfter
사용- 예를 들어
expireAfter
를 720h로 설정했을 때, 720h에 한번씩 노드가 만료되고, 재 생성되기 때문에 생성 시점에 최신 버전으로 구성된다.
- 예를 들어
- drift
- 현재 클러스터에서 ami 업데이트를 감지하고 자동으로 해당 노드를 drift 한다.
NodePool
NodePool을 통해 아래와 같은 작업 설정이 가능
- taint를 통해 카펜터가 생성한 노드에서 실행 될 수 있는 파드/네임스페이스를 제한 (gpu node 등)
- 특정 가용영역으로 노드 생성
- 온디맨드, 스팟으로 생성
- 특정 아키텍처로 생성
- 레이블을 통해 분리하여 노드 관리
- 특정 시간 동안 비어있는 노드가 있다면 삭제
- 더 효율적이고 저렴한 노드로 통합
NodePool 예제
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
taints:
- key: key: example.com/special-taint
effect: NoSchedule
expireAfter: 720h
requirements:
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["m"]
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand", "spot"]
- key: "kubernetes.io/os"
operator: In
values: ["linux"]
- key: "kubernetes.io/arch"
operator: In
values: ["amd64"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
budgets:
- nodes: 10%
- schedule: "0 9 * * mon-fri"
duration: 8h
nodes: "0"
limits:
cpu: "1000"
memory: "1000Gi"
weight: 10
Nodepool은 nodeClassRef
지정을 통해 NodeClass를 선택할 수 있다.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements
requirements를 정의하여, Node의 요구사항을 정의할 수 있다.
- e.g) 특정 인스턴스 유형 (t3, m5 등), 특정 아키텍처, OS
apiVersion: karpenter.sh/v1alpha5
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["t", "m"]
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand"]
- key: karpenter.k8s.aws/instance-generation
operator: In
values: ["3", "5"]
- key: kubernetes.io/os
operator: In
values: ["linux"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
지원되는 requirement key는 크게 아래가 있음 (참고 : Scheduling )
- az
- instance-type
- os
- arch
- capacity-type (spot, on-demand)
- karpenter.k8s.aws/instance-category (t, m, c 등)
- karpenter.k8s.aws/instance-generation (t3, t2 등)
- karpenter.k8s.aws/instance-family (g4dn 등의 좀더 구체적인)
- karpenter.k8s.aws/instance-size (8xlarge 등의 리소스는 비슷하지만 카테고리가 다른)
- karpenter.k8s.aws/instance-cpu (cpu 수에 맞는 인스턴스 타입)
- etc
limits
아래와 같이 resource limit 을 설정하는 경우, limit에 다 다르면 더 이상 노드를 생성하지 않는다.
이때, 파드는 다른 요구 사항에 맞는 노드 풀을 통해 스케쥴링 되거나, 맞는게 없다면 계속 pending 상태로 남게 된다.
[!NOTE]
이 Limit 설정은 생성된 노드(Instance) 단위가 아닌, NodePool 단위이다. 즉 해당 Nodepool을 통해 생성된 모든 노드의 리소스가 Limit을 넘을 수 없다. RI 구매한 것을 활용할 때 유용하다.)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
limits:
cpu: 1000
memory: 1000Gi
Disruption
Karpenter는 불필요한 노드를 자동으로 제거하여 리소스 효율성을 높인다. 주요 전략은 다음과 같다.
- 비어 있는 노드(Empty): 일정 시간 동안 파드가 없는 노드를 자동으로 삭제한다.
- 만료된 노드(Expire): 설정된 시간이 지난 노드를 교체하여 최신 상태를 유지한다.
- 통합(consolidation): 클러스터의 리소스 사용률을 분석하여 노드를 통합하거나 교체한다.
- resource request, node-selector, node/pod affinity, pod anti-affinity, topology, aws cni 등을 고려해서 가장 효율적인 type 중 low price type을 provisioning 한다.
[!TIP]
Karpenter Node에 Consolidation이 발생할 때, 기존 활용도가 떨어지는 노드의 Pod들은 모두 eviction 된다. 당연하지만 이때 Rolling Update 형태로 동작하지 않는다. 실제 동작하는 프로세스는 다음과 같다.
- 새로운 Node 실행
- 기존 Node 삭제 (이때 Pod Eviction 되면서 각 Pod는 kubescheduler에 의해 적절한 Node로 배치된다.)
즉, PDB와 Replicas를 적절하게 설정해두어야 서비스 중단이 발생되지 않는다.
댓글남기기