本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
Karpenter
提示
通过亚马逊 EKS 研讨会探索
Karpenter
-
监控 Kubernetes 调度器由于资源限制而无法调度的容器。
-
评估不可调度的 Pod 的调度需求(资源请求、节点选择器、亲和力、容忍度等)。
-
配置满足这些 Pod 要求的新节点。
-
在不再需要节点时将其移除。
使用 Karpenter,您可以定义节点配置 NodePools 的限制,例如污点、标签、要求(实例类型、区域等)以及对总预置资源的限制。部署工作负载时,您可以在 pod 规范中指定各种调度约束,例如资源 requests/limits、节点选择器、 node/pod 亲和力、容差和拓扑分布约束。然后,Karpenter 将根据这些规格配置大小合适的节点。
使用 Karpenter 的原因
在Karpenter推出之前,Kubernetes用户主要依靠亚马逊EC2自动扩展组和 Kubernetes集群自动调节器
Karpenter 将实例编排职责整合到一个系统中,该系统更简单、更稳定且具有集群意识。Karpenter 旨在通过提供简化的方法来克服集群 Autoscaler 带来的一些挑战:
-
根据工作负载要求配置节点。
-
使用灵活的NodePool 选项,按实例类型创建不同的节点配置。Karpenter 可以让您灵活地管理不同的工作负载容量,而不是管理许多特定的自定义节点组。 NodePool
-
通过快速启动节点和调度 Pod,实现大规模改进 Pod 调度。
有关使用 Karpenter 的信息和文档,请访问 karpenter.sh
建议
最佳实践分为有关 Karpenter 本身和 pod 调 NodePools度的章节。
Karpenter 最佳实践
以下最佳实践涵盖了与 Karpenter 本身相关的主题。
锁定生产集群中的 AMI
我们强烈建议您固定 Karpenter 在生产集群中使用的知名亚马逊系统映像 (AMI)。amiSelector使用别名设置为@latest,或者使用其他方法导致在未经测试的 AMI 发布时进行部署,会带来生产集群中工作负载故障和停机的风险。因此,我们强烈建议您在非生产集群中测试新版本时,为您的生产集群固定经过测试的 AMI 工作版本。例如,您可以按 NodeClass 如下方式在中设置别名:
amiSelectorTerms - alias: al2023@v20240807
有关在 Karpenter 中管理和锁定 AMI 的信息,请参阅 Karpenter 文档中的管理 AMI
使用 Karpenter 处理容量需求不断变化的工作负载
与自动缩放组
Karpenter 删除了 AWS 抽象层,以便将部分灵活性直接带入 Kubernetes。Karpenter 最适合具有工作负载的集群,这些集群会遇到高峰需求或具有不同的计算需求。MNG 和 ASG 适用于运行往往更加静态和一致的工作负载的集群。根据您的要求,您可以混合使用动态和静态管理的节点。
在... 时考虑其他自动缩放项目
你需要在 Karpenter 中仍在开发的功能。由于Karpenter是一个相对较新的项目,如果您需要尚未包含在Karpenter中的功能,请暂时考虑其他自动缩放项目。
在 EKS Fargate 或属于节点组的工作节点上运行 Karpenter 控制器
Karpenter 是使用 Helm Chart 安装的。karpenter这样做将导致部署到该命名空间中的所有 pod 在 EKS Fargate 上运行。不要在由 Karpenter 管理的节点上运行 Karpenter。
Karpenter 不支持自定义启动模板
v1 API 不支持自定义启动模板。您可以使用自定义用户数据 and/or 直接在中指定自定义 AMI。 EC2NodeClass有关如何执行此操作的更多信息,请访问NodeClasses
排除不适合您的工作负载的实例类型
如果集群中运行的工作负载不需要特定的实例类型,可以考虑使用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
使用 Spot 时启用中断处理
Karpenter 支持原生中断处理--interruption-queue CLI 参数。不建议将 Karpenter 中断处理与节点终止处理程序一起使用,如下所述。
需要检查点或其他形式的正常排空的 Pod 需要等待 2 分钟才能关闭,则应在其集群中启用 Karpenter 中断处理功能。
没有出站互联网访问权限的亚马逊 EKS 私有集群
在没有互联网路由的 VPC 中配置 EKS 集群时,必须确保已按照 EKS 文档中显示的私有集群要求配置环境。此外,您需要确保在您的 VPC 中创建了 STS VPC 区域终端节点。否则,您将看到与下面显示的错误类似的错误。
{"level":"FATAL","time":"2024-02-29T14:28:34.392Z","logger":"controller","message":"Checking EC2 API connectivity, WebIdentityErr: failed to retrieve credentials\ncaused by: RequestError: send request failed\ncaused by: Post \"https://sts.<region>.amazonaws.com/\": dial tcp 54.239.32.126:443: i/o timeout","commit":"596ea97"}
这些更改在私有集群中是必要的,因为 Karpenter 控制器使用服务账户 (IRSA) 的 IAM 角色。配置了 IRSA 的 Pod 通过调用 AWS 安全令牌服务 (AWS STS) API 来获取证书。如果没有出站互联网接入,则必须在您的 VPC 中创建和使用 AWS STS VPC 终端节点。
私有集群还要求您为 SSM 创建 VPC 终端节点。当 Karpenter 尝试配置新节点时,它会查询启动模板配置和 SSM 参数。如果您的 VPC 中没有 SSM VPC 终端节点,则会导致以下错误:
{"level":"ERROR","time":"2024-02-29T14:28:12.889Z","logger":"controller","message":"Unable to hydrate the AWS launch template cache, RequestCanceled: request context canceled\ncaused by: context canceled","commit":"596ea97","tag-key":"karpenter.k8s.aws/cluster","tag-value":"eks-workshop"} ... {"level":"ERROR","time":"2024-02-29T15:08:58.869Z","logger":"controller.nodeclass","message":"discovering amis from ssm, getting ssm parameter \"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id\", RequestError: send request failed\ncaused by: Post \"https://ssm.<region>.amazonaws.com/\": dial tcp 67.220.228.252:443: i/o timeout","commit":"596ea97","ec2nodeclass":"default","query":"/aws/service/eks/optimized-ami/1.27/amazon-linux-2/recommended/image_id"}
价目表查询 API 没有 VPC 终端节点。结果,定价数据将随着时间的推移而过时。Karpenter通过在二进制文件中包含按需定价数据来解决这个问题,但只有在升级Karpenter时才会更新该数据。定价数据请求失败将导致以下错误消息:
{"level":"ERROR","time":"2024-02-29T15:08:58.522Z","logger":"controller.pricing","message":"retreiving on-demand pricing data, RequestError: send request failed\ncaused by: Post \"https://api.pricing.<region>.amazonaws.com/\": dial tcp 18.196.224.8:443: i/o timeout; RequestError: send request failed\ncaused by: Post \"https://api.pricing.<region>.amazonaws.com/\": dial tcp 18.185.143.117:443: i/o timeout","commit":"596ea97"}
请参阅此文档
正在创建 NodePools
以下最佳实践涵盖了与创建相关的主题 NodePools。
在... NodePools 时创建多个
当不同的团队共享一个集群并需要在不同的工作节点上运行工作负载,或者有不同的操作系统或实例类型要求时,请创建多个 NodePools。例如,一个团队可能想使用Bottlerocket,而另一个团队可能想使用亚马逊Linux。同样,一个团队可能会获得另一个团队不需要的昂贵的 GPU 硬件。使用多项 NodePools 可确保每个团队都有最合适的资产可用。
创建相互排 NodePools 斥或加权的
NodePools 建议创建相互排斥或加权的,以提供一致的调度行为。如果不是,并且匹配了多个 NodePools ,Karpenter 将随机选择使用哪个,从而导致意想不到的结果。创建多个的有用示例 NodePools 包括:
NodePool 使用 GPU 创建,仅允许特殊工作负载在以下(昂贵的)节点上运行:
# NodePool for GPU Instances with Taints apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu spec: disruption: consolidateAfter: 1m consolidationPolicy: WhenEmptyOrUnderutilized template: metadata: {} spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: default expireAfter: Never requirements: - key: node.kubernetes.io/instance-type operator: In values: - p3.8xlarge - p3.16xlarge - key: kubernetes.io/os operator: In values: - linux - key: kubernetes.io/arch operator: In values: - amd64 - key: karpenter.sh/capacity-type operator: In values: - on-demand taints: - effect: NoSchedule key: nvidia.com/gpu value: "true"
对污点进行容忍部署:
# 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"
对于另一个团队的常规部署,该 NodePool 规范可能包括 NodeAffinity。然后,部署可以使用节点SelectorTerms 进行匹配billing-team。
# NodePool for regular EC2 instances apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: generalcompute spec: template: metadata: labels: billing-team: my-team spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: default expireAfter: Never 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 - key: kubernetes.io/os operator: In values: - linux - key: kubernetes.io/arch operator: In values: - amd64 - key: karpenter.sh/capacity-type operator: In values: - on-demand
使用 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"]
使用计时器 (TTL) 自动从集群中删除节点
您可以在预置节点上使用计时器来设置何时删除没有工作负载 Pod 或已达到过期时间的节点。节点到期可用作升级手段,因此节点将被停用并替换为更新的版本。有关使用配置节点到期的信息,请参阅 Karpenter 文档spec.template.spec到期。
避免过度限制 Karpenter 可以预置的实例类型,尤其是在使用 Spot 时
使用 Spot 时,Karpenter 使用价格容量优化分配策略来配置 EC2 实例。此策略指示 EC2 根据您正在启动的实例数量从最深的池中预置实例,并将中断风险降至最低。然后,EC2 队列请求这些池中价格最低的竞价型实例。您允许 Karpenter 使用的实例类型越多,EC2 可以更好地优化您的竞价型实例的运行时间。默认情况下,Karpenter 将使用部署集群的区域和可用区域中的 EC2 提供的所有实例类型。Karpenter 会根据待处理的 pod 从所有实例类型中智能地进行选择,以确保将您的 pod 调度到大小和装备合适的实例上。例如,如果您的容器不需要 GPU,Karpenter 不会将您的容器调度到支持 GPU 的 EC2 实例类型。当您不确定要使用哪种实例类型时,可以运行 Amazon ec2-instance-selector
$ 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
使用竞价型实例时,不应对 Karpenter 施加太多限制,因为这样做会影响应用程序的可用性。例如,某一特定类型的所有实例都被回收了,没有合适的替代方案可以替换它们。在补充已配置实例类型的竞价容量之前,您的 pod 将保持待处理状态。您可以通过将实例分布在不同的可用区来降低容量不足错误的风险,因为各可用区的竞价池不同。也就是说,一般的最佳实践是允许 Karpenter 在使用 Spot 时使用不同的实例类型。
调度 Pod
以下最佳实践与使用 Karpenter 进行节点配置在集群中部署 pod 有关。
遵循 EKS 最佳实践以实现高可用性
如果您需要运行高可用应用程序,请遵循一般 EKS 最佳实践建议。有关如何跨节点和区域分布 Pod 的详细信息,请参阅 Karpenter 文档中的
使用分层约束来限制云提供商提供的计算功能
Karpenter 的分层约束模型允许你创建一组复杂的 Pod 部署约束条件,以获得与 Pod 调度的最佳匹配。 NodePool Pod 规范可以请求的约束条件示例包括以下内容:
-
需要在只有特定应用程序可用的可用区域中运行。例如,假设你有一个 pod 必须与运行在位于特定可用区的 EC2 实例上的另一个应用程序进行通信。如果您的目标是减少 VPC 中的跨可用区流量,则可能需要将 Pod 放置在 EC2 实例所在的可用区中。这种定位通常使用节点选择器来完成。有关节点选择器的更多信息
,请参阅 Kubernetes 文档。 -
需要某些类型的处理器或其他硬件。有关要求 pod 在 GPU 上运行的 pod 规格示例,请参阅 Karpenter 文档的加速器
部分。
创建账单警报以监控您的计算支出
将集群配置为自动扩展时,应创建账单警报,在支出超过阈值时向您发出警告,并向 Karpenter 配置添加资源限制。使用 Karpenter 设置资源限制与设置 AWS 自动扩展组的最大容量类似,因为它表示 Karpenter 可以实例化的最大计算资源量。NodePool
注意
无法为整个集群设置全局限制。限制适用于特定情况 NodePools。
以下片段告诉 Karpenter 最多只能配置 1000 个 CPU 内核和 1000Gi 内存。只有在达到或超过限制时,Karpenter 才会停止添加容量。当超过限制时,Karpenter 控制器将在控制器的日志中写入memory resource usage of 1001 exceeds limit of 1000或类似的消息。如果您要将容器日志路由到 CloudWatch 日志,则可以创建指标筛选器来查找日志中的特定模式或术语,然后创建CloudWatch警报,在超过配置的指标阈值时提醒您。
有关在 Karpenter 中使用限制的更多信息,请参阅 Karpenter 文档中的
spec: limits: cpu: 1000 memory: 1000Gi
如果您不使用限制或限制 Karpenter 可以预置的实例类型,Karpenter 将继续根据需要为您的集群增加计算容量。虽然以这种方式配置 Karpenter 可以让您的集群自由扩展,但它也可能带来重大成本影响。正是出于这个原因,我们建议配置账单警报。账单警报允许您在账户中计算的预估费用超过规定的阈值时收到提醒并主动通知您。有关更多信息,请参阅设置亚马逊 CloudWatch 账单警报以主动监控预估费用
您可能还需要启用成本异常检测,这是一项 AWS 成本管理功能,它使用机器学习来持续监控您的成本和使用情况,以检测异常支出。更多信息可以在 AWS 成本异常检测入门指南中找到。如果您已经在 AWS 预算中创建了预算,您还可以配置一项操作,在突破特定阈值时通知您。通过预算操作,你可以发送电子邮件、向 SNS 主题发布消息,或者向 Slack 等聊天机器人发送消息。有关更多信息,请参阅配置 AWS 预算操作。
使用 karpenter。sh/do-not-disrupt 注解以防止 Karpenter 取消配置节点
如果您在 Karpenter-provisioned节点上运行关键应用程序,例如长时间运行的批处理作业或有状态应用程序,并且该节点的 TTL 已过期,则该实例终止时该应用程序将中断。通过向 pod 添加karpenter.sh/do-not-disrupt注解,即指示 Karpenter 保留该节点,直到 Pod 终止或karpenter.sh/do-not-disrupt注解被移除。有关更多信息,请参阅 Distruption
如果节点上唯一剩下的非守护程序集的 pod 是那些与任务关联的 pod,则只要任务状态为成功或失败,Karpenter 就可以瞄准和终止这些节点。
使用整合时为所有非 CPU 资源配置请求数=限制
通常,整合和调度是通过比较 pod 资源请求与节点上可分配资源的数量来完成的。不考虑资源限制。举个例子,内存限制大于内存请求的 Pod 可能会突破请求。如果同一个节点上的多个 Pod 同时爆裂,这可能会导致部分 Pod 由于内存不足 (OOM) 情况而终止。整合会使这种情况更有可能发生,因为它只考虑节点的请求就可以将 pod 打包到节点上。
LimitRanges 用于配置资源请求和限制的默认值
由于 Kubernetes 未设置默认请求或限制,因此容器对底层主机、CPU 和内存资源的消耗是不受限制的。Kubernetes 调度器查看容器的总请求数(来自容器的总请求数或来自容器初始容器的总资源中的较高者),以确定将容器调度到哪个工作节点。同样,Karpenter 会考虑 pod 的请求来确定其配置的实例类型。如果某些 Pod 未指定资源请求,您可以使用限制范围为命名空间应用合理的默认值。
将准确的资源请求应用于所有工作负载
当有关您的工作负载要求的信息准确时,Karpenter 能够启动最适合您的工作负载的节点。如果使用Karpenter的合并功能,这一点尤其重要。
请参见为所有工作负载配置和调整资源 Requests/Limits 大小
CoreDNS 推荐
更新 CoreDNS 的配置以保持可靠性
在由 Karpenter 管理的节点上部署 CoreDNS 容器时,考虑到 Karpenter terminating/creating 在新节点中的动态特性以适应需求,建议遵循以下最佳实践:
这将确保 DNS 查询不会被定向到尚未准备就绪或已终止的 CoreDNS Pod。
Karpenter 蓝图
由于 Karpenter 采用应用程序优先的方法为 Kubernetes 数据平面预置计算容量,因此您可能想知道如何正确配置这些常见的工作负载场景。Karpenter 蓝图