카펜터에는 지켜야하는 여러 규칙이 존재합니다.
아래는 요약으로 정리하였으니 개발시에 확인하면 좋습니다.
카펜터는 ASG, MNG와 다르게 Kubernetes 기본 API에 더 가까운 scaling management를 사용합니다.
ASG와 MNG는 EC2 CPU 로드와 같은 AWS의 지표를 기반으로 scaling이 트리거 됩니다. Cluster Autoscaler는 kubernetes 추상화를 AWS 추상화로 연결하지만, 가용영역에 대한 scheduling과 같은 유연성이 일부 손실 됩니다.
카펜터는 이러한 AWS의 추상화 계층을 제거하였습니다. karpetner는 수요가 급증하거나 다양한 컴퓨팅 사이즈에 대한 요구사항이 있는 경우 사용하기 적합합니다. MNG와 ASG는 보다 정적인 작업을 실행하는 경우 적합하며, 필요에 따라서는 karpenter와 혼용해서 쓰는 것도 가능합니다.
Karpenter는 helm 차트로 배포됩니다. Helm 차트는 클러스터 확장에 컨트롤러를 사용하기 위해 Karpenter controller와 webhook 포드를 먼저 실행합니다. 적어도 하나의 worker node가 있는 하나의 노드 그룹에 띄우기를 권장합니다.
대안으로는, 위의 선행되야할 포드들을 karpenter라는 네임스페이스에 Fargate profile을 생성 후 EKS fargate에 띄울 수 있습니다. 이렇게 되면 모든 pod들은 karpenter라는 네임스페이스 아래에 EKS Fargate로 배포 됩니다.
절대 karpenter를 Karpenter가 관리하는 노드 위에 띄우지 마세요
카펜터는 사용자 지정 시작 템플릿을 사용하는 것을 권하지 않습니다. 사용자 지정 템플릿을 사용하게 되면 다중 아키텍처 지원, 노드 자동 업그레이드 기능 및 securityGroup 검색을 막습니다. 시작 템플릿을 사용하면 Karpenter의 provisioner에서 특정 필드가 중복되며, 서브넷 및 인스턴스 유형과 같은 다른 필드는 Karpenter에서 무시되기 때문에 혼란이 발생할 수도 있습니다.
사용자 지정 사용자 데이터를 사용하거나 AWS 노드 템플릿에서 사용자 지정 AMI를 직접 지정하면 시작 템플릿 사용을 피할 수 있는 경우가 많습니다. 이를 수행하는 방법에 대한 자세한 내용은 노드 템플릿 에서 확인할 수 있습니다.
클러스터에서 실행되는 워크로드에 필요하지 않은 경우 node.kubernetes.io/instance-type 키를 사용하여 특정 인스턴스 유형을 제외하는 것이 좋습니다 .
다음 예에서는 대규모 Graviton 인스턴스 프로비저닝을 방지하는 방법을 보여줍니다.
- key: node.kubernetes.io/instance-type
operator: NotIn
values:
'm6g.16xlarge'
'm6gd.16xlarge'
'r6g.16xlarge'
'r6gd.16xlarge'
'c6g.16xlarge'
Karpenter는 aws.interruptionQueue의 값을 통해 활성화되는 기본 중단 처리를 지원합니다. 다음과 같이 워크로드를 중단시킬 수 있는 비자발적 중단 이벤트에 대한 중단 처리를 감시입니다.
Karpenter는 이러한 이벤트 중 하나가 노드에 발생할 것을 감지하면 중단 이벤트가 발생하기 전에 자동으로 노드를 차단, 배수 및 종료하여 중단 전 워크로드 정리를 위한 최대 시간을 제공합니다. 아래에 설명된 대로 Karpenter와 함께 AWS 노드 종료 핸들러를 사용하는 것은 권장되지 않습니다.
체크포인트 또는 기타 형태의 정상적인 배수가 필요한 포드(종료 전 2분의 시간 필요)는 클러스터에서 Karpenter 중단 처리를 활성화해야 합니다.
인터넷 경로가 없는 VPC에 EKS 클러스터를 프로비저닝하는 경우 EKS 설명서에 표시된 프라이빗 클러스터 요구 사항 에 따라 환경을 구성했는지 확인해야 합니다 . 또한 VPC에 STS VPC 지역 엔드포인트를 생성했는지 확인해야 합니다. 그렇지 않은 경우 아래에 나타나는 것과 유사한 오류가 표시됩니다.
ERROR controller.controller.metrics Reconciler error {"commit": "5047f3c", "reconciler group": "karpenter.sh", "reconciler kind": "Provisioner", "name": "default", "namespace": "", "error": "fetching instance types using ec2.DescribeInstanceTypes, WebIdentityErr: failed to retrieve credentials\ncaused by: RequestError: send request failed\ncaused by: Post \"https://sts.<region>.amazonaws.com/\": dial tcp x.x.x.x:443: i/o timeout"}
Karpenter 컨트롤러는 서비스 계정에 대한 IAM 역할(IRSA)을 사용하기 때문에 비공개 클러스터에서는 이러한 변경이 필요합니다. IRSA로 구성된 포드는 AWS Security Token Service(AWS STS) API를 호출하여 자격 증명을 획득합니다. 아웃바운드 인터넷 액세스가 없는 경우 VPC에서 AWS STS VPC 엔드포인트를 생성하고 사용해야 합니다 .
또한 프라이빗 클러스터에서는 SSM용 VPC 엔드포인트를 생성해야 합니다 . Karpenter는 새 노드를 프로비저닝하려고 시도할 때 시작 템플릿 구성과 SSM 매개변수를 쿼리합니다. VPC에 SSM VPC 엔드포인트가 없으면 다음 오류가 발생합니다.
INFO controller.provisioning Waiting for unschedulable pods {"commit": "5047f3c", "provisioner": "default"}
INFO controller.provisioning Batched 3 pods in 1.000572709s {"commit": "5047f3c", "provisioner": "default"}
INFO controller.provisioning Computed packing of 1 node(s) for 3 pod(s) with instance type option(s) [c4.xlarge c6i.xlarge c5.xlarge c5d.xlarge c5a.xlarge c5n.xlarge m6i.xlarge m4.xlarge m6a.xlarge m5ad.xlarge m5d.xlarge t3.xlarge m5a.xlarge t3a.xlarge m5.xlarge r4.xlarge r3.xlarge r5ad.xlarge r6i.xlarge r5a.xlarge] {"commit": "5047f3c", "provisioner": "default"}
ERROR controller.provisioning Could not launch node, launching instances, getting launch template configs, getting launch templates, getting ssm parameter, RequestError: send request failed
caused by: Post "https://ssm.<region>.amazonaws.com/": dial tcp x.x.x.x:443: i/o timeout {"commit": "5047f3c", "provisioner": "default"}
Price List Query API 에는 VPC 엔드포인트 가 없습니다 . 결과적으로 가격 데이터는 시간이 지나면서 더 이상 유효하지 않게 됩니다. Karpenter는 바이너리에 주문형 가격 데이터를 포함하여 이 문제를 해결하지만 Karpenter가 업그레이드될 때만 해당 데이터를 업데이트합니다. 가격 데이터 요청이 실패하면 다음과 같은 오류 메시지가 표시됩니다.
ERROR controller.aws.pricing updating on-demand pricing, RequestError: send request failed
caused by: Post "https://api.pricing.us-east-1.amazonaws.com/": dial tcp 52.94.231.236:443: i/o timeout; RequestError: send request failed
caused by: Post "https://api.pricing.us-east-1.amazonaws.com/": dial tcp 52.94.231.236:443: i/o timeout, using existing pricing data from 2022-08-17T00:19:52Z {"commit": "4b5f953"}
요약하자면, 완전한 프라이빗 EKS 클러스터에서 Karpenter를 사용하려면 다음 VPC 엔드포인트를 생성해야 합니다.
com.amazonaws..ec2
com.amazonaws..ecr.api
com.amazonaws..ecr.dkr
com.amazonaws..s3 – For pulling container images
com.amazonaws..sts – For IAM roles for service accounts
com.amazonaws..ssm - If using Karpenter
메모
Karpenter(컨트롤러 및 웹훅 배포) 컨테이너 이미지는 Amazon ECR 프라이빗 또는 VPC 내부에서 액세스할 수 있는 다른 프라이빗 레지스트리에 있거나 복사되어야 합니다. 그 이유는 Karpenter 컨트롤러와 웹훅 포드가 현재 공용 ECR 이미지를 사용하기 때문입니다. VPC 내에서 또는 VPC와 피어링된 네트워크에서 이러한 이미지를 사용할 수 없는 경우 Kubernetes가 ECR 공개에서 이러한 이미지를 가져오려고 하면 이미지 가져오기 오류가 발생합니다.
여러팀이 하나의 클러스터를 공유할 경우, 각 팀의 요구사항에 따라 여러 타입의 워커노드를 운영해야 할때가 있습니다. 예를 들어 어떤 팀은 ARM을 쓰고 다른팀은 Bottlerocket을 쓰는 등, OS나 instance type이 다를 수 있죠. 이런 경우에는 여러 provisoner를 생성하여 각 팀에 필요한 워커노드를 생성하면 됩니다.
이렇게 하면 일관된 스케줄링 동작이 제공됩니다. 여러 Provisioners가 일치하는 경우 Karpenter는 어떤 Provisioner를 사용할지 무작위로 선택하여 예상치 못한 결과를 초래할 수 있습니다. 여러 Provisioners를 만드는 유용한 예는 다음과 같습니다:
GPU를 가진 Provisioner를 만들고 이러한 (비용이 많이 드는) 노드에서만 특수 워크로드를 실행할 수 있도록 하는 provisoner:
# Provisioner for GPU Instances with Taints
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: gpu
spec:
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values:
- p3.8xlarge
- p3.16xlarge
taints:
- effect: NoSchedule
key: nvidia.com/gpu
value: "true"
ttlSecondsAfterEmpty: 60
Deployment에 taint를 위한 toleration을 걸어주세요:
# Deployment of GPU Workload will have tolerations defined
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate-gpu
spec:
...
spec:
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
다른 팀을 위한 일반적인 배포를 진행할때는, provisioner에 nodeAffinity를 사용하여주세요. 그러면 Deployment는 nodeSelectorTerms를 사용하여 billing-team에 맞는 node를 선택합니다.
# Provisioner for regular EC2 instances
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: generalcompute
spec:
labels:
billing-team: my-team
requirements:
- key: node.kubernetes.io/instance-type
operator: In
values:
- m5.large
- m5.xlarge
- m5.2xlarge
- c5.large
- c5.xlarge
- c5a.large
- c5a.xlarge
- r5.large
- r5.xlarge
Deployment에 사용하는 nodeAffinity:
# Deployment will have spec.affinity.nodeAffinity defined
kind: Deployment
metadata:
name: workload-my-team
spec:
replicas: 200
...
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "billing-team"
operator: "In"
values: ["my-team"]
노드의 제공 시간이나 만료 시간에 도달한 워크로드 포드가 없는 노드를 삭제할 때 타이머를 사용할 수 있습니다. 노드 만료는 노드를 업그레이드하기 위한 수단으로 사용될 수 있으므로 노드가 은퇴되고 업데이트된 버전으로 교체됩니다. 노드 제공 해제에 관한 정보는 Karpenter 문서의 "Karpenter 노드가 어떻게 해제되는가"를 참조하여 ttlSecondsUntilExpired 및 ttlSecondsAfterEmpty를 사용하여 노드를 해제하는 방법을 확인하십시오.
Spot을 사용할 때 Karpenter는 EC2 인스턴스를 프로비저닝하기 위해 Price Capacity Optimized 할당 전략을 사용합니다.
이 전략은 EC2에게 귀하가 시작하려는 인스턴스 수에 대한 가장 깊은 풀에서 인스턴스를 프로비저닝하도록 지시하며 중단 위험이 가장 낮은 풀에서 인스턴스를 프로비저닝합니다.
그런 다음 EC2 Fleet은 이러한 풀 중에서 가장 저렴한 가격의 Spot 인스턴스를 요청합니다. Karpenter가 사용할 수 있는 인스턴스 유형이 더 많을수록 EC2는 Spot 인스턴스의 실행 시간을 최적화하는 데 더 효과적일 것입니다.
기본적으로 Karpenter는 귀하의 클러스터가 배포된 지역 및 가용 영역에서 EC2가 제공하는 모든 인스턴스 유형을 사용합니다.
Karpenter는 보류 중인 pod을 기반으로 모든 인스턴스 유형 세트 중에서 지능적으로 선택하여 pod이 적절한 크기와 구성의 인스턴스에 예약되도록 합니다.
예를 들어, 만약 귀하의 pod이 GPU를 필요로 하지 않는다면, Karpenter는 pod을 GPU를 지원하는 EC2 인스턴스 유형에 예약하지 않을 것입니다.
어떤 인스턴스 유형을 사용해야 하는지 확신이 없을 때는 Amazon ec2-instance-selector를 실행하여 계산 요구 사항과 일치하는 인스턴스 유형 목록을 생성할 수 있습니다.
예를 들어, CLI는 메모리 vCPU, 아키텍처 및 지역을 입력 매개변수로 사용하고 이러한 제약 조건을 충족시키는 EC2 인스턴스 목록을 제공합니다.
$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r ap-southeast-1
c5.large
c5a.large
c5ad.large
c5d.large
c6i.large
t2.medium
t3.medium
t3a.medium
Spot 인스턴스를 사용할 때 Karpenter에 너무 많은 제약 조건을 두지 않는 것이 중요합니다. 귀하의 응용 프로그램의 가용성에 영향을 미칠 수 있습니다. 예를 들어, 특정 유형의 모든 인스턴스가 회수되고 대체할 수 있는 적절한 대안이 없는 경우가 발생할 수 있습니다. 그런 경우에 pod은 구성된 인스턴스 유형의 Spot 용량이 보충될 때까지 대기 상태로 남을 것입니다.
서로 다른 가용 영역에 인스턴스를 분산 배치하여 부족한 용량 오류의 위험을 줄일 수 있습니다. 왜냐하면 가용 영역마다 Spot 풀이 다르기 때문입니다. 이에 따라 일반적으로 가장 좋은 방법은 Spot을 사용할 때 Karpenter가 다양한 인스턴스 유형 집합을 사용할 수 있도록 하는 것입니다.
Karpenter 문서의 "Topology Spread"를 참조하여 팟을 노드와 가용 영역에 분산 배치하는 방법에 대한 자세한 내용을 확인하십시오. Pod를 추방하거나 삭제하려는 시도가 있는 경우 유지해야 하는 최소한의 사용 가능한 pod을 설정하는 Disruption Budgets를 사용하십시오.
Karpenter의 layered 제약 조건 모델을 사용하면 pod 스케줄링에 최상의 노드를 배치할 수 있는 복합적인 provisioner 및 deployment 세트를 만들 수 있습니다. pod 사양으로 요청할 수 있는 제약 조건 예시는 다음과 같습니다:
특정 application만 사용 가능한 가용 영역에서 실행해야 하는 경우. 예를 들어, 특정 가용 영역에 있는 EC2 인스턴스에서 실행되는 다른 application과 통신해야 하는 pod이 있다고 가정해보십시오.
VPC에서 가용 영역 간 트래픽을 줄이려면 pod을 해당 EC2 인스턴스가 위치한 가용 영역에 공존시키고 싶을 것입니다. 이러한 대상 설정은 종종 node selctor를 사용하여 수행됩니다. node selector에 대한 자세한 내용은 Kubernetes 문서를 참조하십시오.
특정 종류의 프로세서 또는 다른 하드웨어가 필요한 경우. 예를들어 GPU에서 pod을 실행하는 법은 Karpenter 문서의 "Accelerators" 섹션을 참조하십시오.
자동 스케일을 구성한 클러스터의 경우, 지출이 임계값을 초과했을 때 경고하는 청구 알람을 생성해야 합니다. 또한 Karpenter 구성에 리소스 제한을 추가해야 합니다.
Karpenter를 사용하여 리소스 제한을 설정하는 방법은 AWS Auto Scaling 그룹의 최대 용량을 설정하는 것과 유사하며, Karpenter 프로비저너에서 인스턴스화할 수 있는 최대 컴퓨팅 리소스를 나타냅니다.
참고:
전체 클러스터에 대한 전역 제한을 설정하는 것은 불가능합니다. 제한은 특정 프로비저너에 적용됩니다.
아래 스니펫은 Karpenter에게 최대 1000 CPU 코어 및 1000Gi 메모리만 프로비저닝하도록 지시합니다. 제한이 충족되거나 초과되었을 때만 Karpenter는 용량을 추가하지 않습니다.
제한을 초과하면 Karpenter 컨트롤러는 메모리 리소스 사용량이 1000 제한을 초과했음을 나타내는 메시지를 컨트롤러 로그에 작성합니다.
컨테이너 로그를 CloudWatch 로그로 라우팅하는 경우 로그에서 특정 패턴 또는 용어를 검색하고 구성된 메트릭 임계값이 위반될 때 경고를 알리는 CloudWatch 알람을 생성할 수 있습니다.
spec:
limits:
resources:
cpu: 1000
memory: 1000Gi
Karpenter와 함께 제한을 사용하는 자세한 내용은 Karpenter 문서의 "Setting Resource Limits"를 참조하십시오.
만약 제한을 사용하지 않거나 Karpenter가 프로비저닝할 수 있는 인스턴스 유형을 제한하지 않는다면, Karpenter는 필요에 따라 클러스터에 계속해서 컴퓨팅 용량을 추가할 것입니다.
Karpenter를 이러한 방식으로 구성하면 클러스터가 자유롭게 확장될 수 있지만 비용에 중대한 영향을 가질 수 있습니다. 이러한 이유로 청구 알람을 구성하는 것을 권장합니다.
청구 알람을 사용하면 계정에서 계산된 예상 비용이 정의된 임계값을 초과할 때 경고 및 예방적인 알림을 받을 수 있습니다. 추가 정보는 "Amazon CloudWatch Billing Alarm 설정"을 참조하십시오.
또한 비용 이상 탐지(Cost Anomaly Detection)를 활성화하려면 AWS 비용 관리 기능을 사용하여 비용과 사용량을 계속 모니터링하고 비정상적인 지출을 감지합니다. 자세한 정보는 "AWS 비용 이상 탐지 시작 가이드"에서 찾을 수 있습니다.
AWS Budgets에서 예산을 만든 경우 특정 임계값을 초과했을 때 알림을 구성할 수도 있습니다. 예산 액션을 사용하면 이메일을 보내거나 SNS 주제에 메시지를 게시하거나 Slack과 같은 채팅 봇에 메시지를 보낼 수 있습니다. 자세한 내용은 "AWS Budgets 액션 구성"을 참조하십시오.
Karpenter가 프로비저닝한 노드에서 중요한 응용 프로그램을 실행 중인 경우, 예를 들어 긴 실행 배치 작업이나 상태를 유지해야 하는 응용 프로그램인 경우, 노드의 TTL이 만료되면 해당 인스턴스가 종료될 때 응용 프로그램이 중단될 수 있습니다. Pod에 karpenter.sh/do-not-evict 주석을 추가하면 Karpenter에게 해당 노드를 해당 Pod가 종료되거나 do-not-evict 주석이 제거될 때까지 유지하도록 지시합니다. 자세한 정보는 "Deprovisioning documentation"를 참조하십시오.
만약, deprovisioning 작업 중인 노드에 non-daemonset pod만 남아있다면 카펜터는 deprovisiong이 성공하거나 실패할때까지 해당 노드를 targeting할 것입니다.
일반적으로 consolidation 및 스케줄링은 pod의 리소스 요청과 노드의 할당 가능한 리소스 양을 비교하여 작동합니다. 리소스 제한은 고려되지 않습니다. 그렇지 않으면 Karpenter는 노드에 가능한 한 많은 포드를 배치하므로 노드에 메모리 압박이 발생할 수 있습니다. 이는 통합 모드에서 특히 중요합니다.
동일한 노드의 여러 팟이 동시에 터지는 경우 메모리 부족 (OOM) 상태로 인해 일부 팟이 종료될 수 있습니다. consolidation은 요청만을 고려하여 팟을 노드에 패킹하기 때문에 이러한 상황이 발생할 가능성이 있습니다.
https://kyverno.io/policies/karpenter/set-karpenter-non-cpu-limits/set-karpenter-non-cpu-limits/
Kubernetes는 기본 요청(requests) 또는 제한(limits)을 설정하지 않기 때문에, 컨테이너가 기본 호스트의 CPU 및 메모리 리소스를 사용하는 것에 제한이 없습니다. Kubernetes 스케줄러는 팟의 총 요청을 보고 (팟의 컨테이너 또는 Init 컨테이너에서의 총 요청 중 더 큰 값) pod을 스케줄할 작업자 노드를 결정합니다. 마찬가지로 Karpenter는 pod의 request를 고려하여 프로비저닝할 인스턴스 유형을 결정합니다.
어떤 pod에서는 리소스 request가 지정되지 않은 경우, 네임스페이스에 합리적인 기본값을 적용하기 위해 Limit Range를 사용할 수 있습니다.
Karpenter는 귀하의 작업 요구 사항에 대한 정보가 정확한 경우에 가장 적합한 노드를 시작할 수 있습니다. 특히 Karpenter의 consolidation 기능을 사용하는 경우에 중요합니다.