DevOps
Karpenter 0.3x
Jensen여기어때
2024년 4월 2일
원문에서 보기 ↗안녕하세요. 여기어때컴퍼니 SRE팀에서 EKS(Elastic Kubernetes Service, AWS의 관리형 Kubernetes 서비스)를 담당하고 있는 젠슨입니다.
기존에 Karpenter 사용 방법에 대한 블로그를 작성한 적이 있습니다. 최근 신규로 Karpenter를 설치할 일이 있어 최신 버전을 찾아보니 API Version이 변경 되면서 사용 방법 또한 조금 변경된 점이 있어 해당 내용을 보충 하려고 블로그를 작성하게 되었습니다.

Karpenter 0.3x
그럼 하나 씩 살펴 보도록 하겠습니다.
1. 변경 사항
가장 중요한 변경 사항으로 아래 세 가지를 말씀 드리고 싶습니다. 물론 공식 Git에 더 자세하고 많은 내용이 나와 있습니다.
API Version 변경
Drift 기본 활성화
spotToSpotConsolidation 기능
우선 API Version이 기존까지 karpenter.sh/v1alpha5였는데 이제 karpenter.sh/v1beta1으로 변경 되었습니다. 곧 정식 v1을 출시 한다고 하니 조금만 더 기다려 보시면 좋을 것 같습니다.
다음으로는 Drift 입니다. Drift는 Karpenter에 의해서 생성된 WorkerNode 에 새로운 변경사항을 담은 Manifest가 배포 되면 해당 내용을 감지하고 바로 현재 운영 중인 WorkerNode에 변경된 내용을 적용해 주는 기능입니다. 예를 들어 현재 c5.large Type의 WorkerNode를 운영 중일 때 m5.large로 변경된 Manifest를 배포 하면, Drift 기능이 활성화 되어 있지 않을 경우 기존에 운영 중이던 c5.large Type의 WorkerNode들은 영향을 받지 않고 신규로 생성되는 WorkerNode들만 m5.large Type으로 생성될 것입니다.
만약 Drift가 활성화 되어 있다면 Manifest가 배포되는 순간 이미 운영중이던 모든 WorkerNode들의 Type을 m5.large로 변경해 줍니다. Instance Type 에 대한 예를 들었지만 Tag, AMI, SG등 여러가지 변경 사항들을 바로 적용해 주는 기술 입니다. 이 Drift 기능이 0.3x부터는 Default 값이 true 입니다. 만약 해당 기술이 운영에 문제를 일으킬 수 있다면 false로 변경해 주셔야 합니다. 아래와 같은 상황이 발생할 수 있기 때문입니다.
만약 amiFamily와 같은 설정을 이용하여 AL2023, AL2와 같은 값을 넣어 놓았다고 가정해 보겠습니다. 이렇게 되면 해당 Family의 최신 버전 AMI ID를 자동으로 가져오게 되는데, 현재 운영 중이던 WorkerNode의 AMI ID 값과 Manifest의 ID 값이 다르다면 Drift에 의해 WorkerNode가 변경될 것입니다. Drift 활성화로 운영 중 WorkerNode의 교체가 이루어져서 장애를 발생시킬 수 있기 때문에 주의 하셔야 합니다.
이제 마지막으로 spotToSpotConsolidation 기능 입니다. 이미 기존에 Consolidation 기능에 대해서 설명해 드린 적이 있는데 WorkerNode에 배포되어 있는 Pod를 효율적으로 Scheduling하면서 불필요한 리소스의 낭비를 최소한으로 줄여주는 기능입니다. 해당 기능이 기존에는 Spot Instance를 사용할 때 적용되지 않았습니다. 이제 0.3x 버전부터는 해당 기능을 활성화 시켜서 Spot Instance에서도 Consolidation 기능을 사용할 수 있습니다.
대략 위의 기능들을 적용 한 상태로 설치하게 된다면 아래와 같을 것입니다.
helm template karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "${KARPENTER_NAMESPACE}" \
--set "settings.clusterName=${EKS_CLUSTER_NAME}" \
--set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${KARPENTER_SERVICEACCOUNT_ARN}" \
--set nodeSelector."eks\.amazonaws\.com/nodegroup"=${NODEGROUP_NAME} \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--set controller.resources.limits.cpu=1 \
--set settings.featureGates.drift=true \
--set settings.featureGates.spotToSpotConsolidation=true \
--set controller.resources.limits.memory=1Gi > karpenter.yaml
kubectl apply -f karpenter.yaml
2. NodePool
처음에 NodePool 이라는 단어를 보고 굉장히 생소 했는데 Karpenter Docs에서 해당 내용을 읽어본 후 곧 이해가 되었습니다. 간단히 말해서 기존과 사용법은 동일하고 이름만 변경된 것이었습니다. 기존에 사용하던 이름과 대입하면 아래와 같습니다.
Provisioner -> NodePool
AWSNodeTemplate -> EC2NodeClass
그럼 일단 변경된 NodePool에 대해서 알아보겠습니다.
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: test
spec:
template:
spec:
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
name: test
requirements:
- key: "node.kubernetes.io/instance-type"
operator: In
values: [ c5.large, m5.large, r5.large ]
- key: "topology.kubernetes.io/zone"
operator: In
values: [ ap-northeast-2a, ap-northeast-2c ]
- key: karpenter.sh/capacity-type
operator: In
values: [ spot ]
disruption:
# WhenUnderutilized / WhenEmpty 둘 중 선택
consolidationPolicy: WhenUnderutilized
# WhenEmpty 일때만 consolidateAfter 사용
# consolidateAfter: 30s
expireAfter: 720h
사용 방법은 대략 위와 같습니다. v1alpha5 때와 크게 달라진 것은 없습니다. 변경된 이름이 맞춰서 nodeClassRef 라는 것이 생겼고 하위에 연결할 EC2NodeClass 이름을 명시해 주면 됩니다. requirements는 잘 아시는 내용으로 생성할 WorkerNode의 제약사항을 의미합니다. 해당 링크에서 사용할 수 있는 AWS용 Well-Known Label을 확인 할 수 있습니다.
조금 눈에 띄는 것은 disruption 부분입니다. 어딘가 익숙하면서도 생소할 수 있는데 기존에 사용하시던 것들과 이렇게 대입하시면 됩니다.
consolidation -> consolidationPolicy
ttlSecondsAfterEmpty -> consolidateAfter
ttlSecondsUntilExpired -> expireAfter
굉장히 익숙한 옵션들 입니다. consolidationPolicy의 경우 기존에는 true/false 로 활성화 비활성화 했었지만 이제는 WhenUnderutilized / WhenEmpty 로 활성화, 비활성화를 해주셔야 합니다.
기존과 동일 하게 consolidationPolicy가 활성화 되어 있다면 consolidateAfter는 사용하실 수 없습니다. consolidationPolicy를 WhenEmpty로 선언 하셨을 경우에만 consolidateAfter를 사용하실 수 있습니다. DaemonSet을 제외한 모든 Pod가 삭제된 후 몇 초 후에 해당 Node를 삭제할 것인지에 대한 옵션입니다.
마지막으로 expireAfter는 생성된 WorkerNode를 어떤 주기로 교체할 것인지에 대한 옵션으로 기존에는 2592000과 같은 값을 넣어줬던 것이 이제는 h로 넣을 수 있게 변경되어 30일 기준으로 720h 와 같이 넣어줄 수 있습니다.
3. EC2NodeClass
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: test
spec:
amiFamily: AL2023
subnetSelectorTerms:
- tags:
${subnet-tag-key}: ${subnet-tag-value}
securityGroupSelectorTerms:
- tags:
${securityGroup-tag-key}: ${securityGroup-tag-value}
instanceProfile: ${WorkerNode-Role-Name}
tags:
Name: ${Name-tag-value}
# metadata option
metadataOptions:
httpEndpoint: enabled
httpProtocolIPv6: disabled
httpPutResponseHopLimit: 2
httpTokens: required
# EBS Option
blockDeviceMappings:
- deviceName: /dev/xvda
ebs:
volumeSize: 50Gi
volumeType: gp3
iops: 3000
throughput: 125
deleteOnTermination: true
detailedMonitoring: true
userData: ${userData-value}
EC2NodeClass는 기존에 사용하던 AWSNodeTemplate 과 변경된 점이 없습니다. 기존과 같은 방식으로 사용하시면 됩니다.
지금까지 Karpenter의 변경사항에 대해서 알아보았습니다. API Version이 변경되면서 용어가 조금씩 변경 되었지만 기존에 Karpenter를 사용해 보셨던 분들이라면 무리 없이 금방 사용 가능 하실 것입니다.
이것으로 Karpenter 0.3x에 대한 설명을 마치며 다음 시간에는 여기어때에서 적용한 NTH(NodeTerminationHandler)에 대해서 알아보도록 하겠습니다.
감사합니다.