本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
Karpenter
Karpenter
-
监控 Kubernetes 调度器由于资源限制而无法调度的容器。
-
评估不可调度 Pod 的调度要求(资源请求、节点选择器、亲和力、容忍度等)。
-
配置满足这些 Pod 要求的新节点。
-
不再需要节点时将其移除。
使用 Karpenter,您可以定义 NodePools 对节点配置的限制,例如污点、标签、要求(实例类型、区域等),以及对总预配置资源的限制。部署工作负载时,您可以在 Pod 规格中指定各种调度限制,例如资源requests/limits, node selectors, node/pod亲和力、容忍度和拓扑分布限制。然后,Karpenter 将根据这些规格配置大小合适的节点。
使用 Karpenter 的理由
在 Karpenter 推出之前,Kubernetes 用户主要依靠 Amazon Auto Scaling 组和 Kubernetes 集群EC2 自动扩缩器 (CAS) 来动态调整其集群
Karpenter 将实例编排职责整合到一个系统中,该系统更简单、更稳定且支持集群。Karpenter 旨在通过提供简化的方法来克服集群自动扩缩器带来的一些挑战:
-
根据工作负载要求配置节点。
-
使用灵活的NodePool 选项,按实例类型创建不同的节点配置。Karpenter 可以让您通过单个灵活的方式管理不同的工作负载容量,而不是管理许多特定的自定义节点组。 NodePool
-
通过快速启动节点和调度 Pod,实现大规模改进 Pod 调度。
有关使用 Karpenter 的信息和文档,请访问 karpenter.sh
建议
最佳实践分为关于 Karpenter 本身和 pod 调 NodePools度的部分。
Karpenter 最佳实践
以下最佳实践涵盖了与 Karpenter 本身相关的主题。
锁定 AMIs 生产集群
我们强烈建议您固定 Karpenter 用于生产集群的知名亚马逊系统映像 (AMIs)。将别名设置为@latest
,或者使用其他方法导致在发布时部署未经测试 AMIs ,会给生产集群带来工作负载故障和停机的风险。amiSelector
因此,我们强烈建议您在非生产集群中测试较新版本时, AMIs 为生产集群固定经过测试的工作版本。例如,您可以按 NodeClass 如下方式在中设置别名:
amiSelectorTerms - alias: al2023@v20240807
有关在 Karpenter AMIs 中管理和锁定的信息,请参阅 Karpenter 文档 AMIs中的管理
使用 Karpenter 处理容量需求不断变化的工作负载
与自动扩缩组 () 和托管节点组 (ASGs) APIs 相比,Karpenter 使扩展
Karpenter 删除了一层 AWS 抽象,将一些灵活性直接带入 Kubernetes 中。Karpenter 最适合工作负载处于高峰期、高峰期或计算要求不同的集群。 MNGs 并且 ASGs 适用于运行往往更具静态性和一致性的工作负载的集群。根据您的要求,您可以混合使用动态和静态管理的节点。
当... 时,可以考虑其他自动缩放项目
你需要在 Karpenter 中仍在开发的功能。由于 Karpenter 是一个相对较新的项目,因此如果您需要尚未包含在 Karpenter 中的功能,请暂时考虑其他自动缩放项目。
在 EKS Fargate 或属于节点组的工作节点上运行 Karpenter 控制器
Karpenter 是使用 [Helm chart] 安装的 (https://karpenter。 sh/docs/getting-started/getting-started-with-karpenter/#4-install-karpenkarpenter
这样做会导致部署到这个命名空间中的所有 pod 都在 EKS Fargate 上运行。不要在由 Karpenter 管理的节点上运行 Karpenter。
Karpenter 不支持自定义启动模板
v1 APIs 不支持自定义启动模板。您可以使用自定义用户数据和/或直接 AMIs 在中指定 custom EC2 NodeClass。有关如何执行此操作的更多信息,请访问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 中断处理与节点终止处理程序一起使用,如下所述。
需要检查点或其他形式的优雅排水、需要在关闭前 2 分钟的 Pod 应在集群中启用 Karpenter 中断处理。
没有出站互联网访问权限的 Amazon EKS 私有集群
将 EKS 集群配置到没有互联网路由的 VPC 时,必须确保已根据 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 Security Token Service (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"}
要在完全私有的 EKS 集群中使用 Karpenter,并了解要创建哪些 VPC 终端节点,请参阅此文档
正在创建 NodePools
以下最佳实践涵盖了与创建相关的主题 NodePools。
在... NodePools 时创建多个
当不同的团队共享一个集群并需要在不同的工作节点上运行工作负载,或者有不同的操作系统或实例类型要求时,可以创建多个集群 NodePools。例如,一个团队可能想使用 Bottlerocket,而另一个团队可能想要使用 Amazon 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。然后可以用部署 nodeSelectorTerms 来匹配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 或已达到过期时间的节点。节点过期可用作升级手段,这样节点就会停用并替换为更新的版本。有关使用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
在使用 Spot 实例时,不应对 Karpenter 设置太多限制,因为这样做会影响应用程序的可用性。例如,假设特定类型的所有实例都被回收了,没有合适的替代方案可以替换它们。在配置的实例类型的竞价容量补充完毕之前,您的 Pod 将保持待处理状态。您可以通过将实例分散到不同的可用区来降低容量不足错误的风险,因为竞价池各不相同 AZs。也就是说,一般的最佳做法是允许 Karpenter 在使用 Spot 时使用不同的实例类型。
调度 Pod
以下最佳实践与使用 Karpenter 进行节点配置的集群中部署 pod 有关。
遵循 EKS 最佳实践以实现高可用性
如果您需要运行高可用性应用程序,请遵循一般的 EKS 最佳实践建议
使用分层约束限制云提供商提供的计算功能
Karpenter 的分层约束模型允许您创建一组复杂的 pod 部署约束,以获得最匹配的 pod 调度。 NodePool Pod 规范可以请求的约束条件示例包括以下内容:
创建账单警报以监控您的计算支出
当您将集群配置为自动扩展时,您应该创建账单警报,以便在支出超过阈值时向您发出警告,并在 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 可以让您的集群自由扩展,但也可能带来显著的成本影响。正是出于这个原因,我们建议配置账单警报。账单警报允许您在账户中计算出的预估费用超过定义的阈值时收到提醒和主动通知。有关更多信息,请参阅设置 Amazon CloudWatch 账单警报以主动监控预估费用
您可能还需要启用成本异常检测,这是一项 AWS 成本管理功能,它使用机器学习来持续监控您的成本和使用情况,以检测异常支出。更多信息可在 AWS 成本异常检测入门指南中找到。如果您已经在 AWS Budgets 中创建预算,还可以配置一项操作,以便在突破特定阈值时通知您。通过预算操作,您可以发送电子邮件、向 SNS 主题发布消息或向 Slack 等聊天机器人发送消息。有关更多信息,请参阅配置 AWS Budgets 操作。
使用 karpenter.sh/ do-not-disrupt 注解来防止 Karpenter 取消配置节点
如果您在 Karpenter 配置的节点上运行关键应用程序,例如长时间运行的批处理作业或有状态的应用程序,并且该节点的 TTL 已过期,则该应用程序将在实例终止时中断。通过向 Pod 添加karpenter.sh/do-not-disrupt
注解,您就是在指示 Karpenter 保留该节点,直到 Pod 终止或karpenter.sh/do-not-disrupt
注释被移除。有关更多信息,请参阅 Distruption
如果节点上唯一剩下的非守护程序集容器是与任务关联的 pod,那么只要任务状态为成功或失败,Karpenter 就可以瞄准和终止这些节点。
使用整合时为所有非 CPU 资源配置 requess=limits
整合和调度通常是通过比较节点上的 pod 资源请求和可分配的资源量来进行的。不考虑资源限制。例如,内存限制大于内存请求的 Pod 可能会突增到请求之上。如果同一节点上的多个 Pod 同时爆裂,则可能导致某些 Pod 因内存不足 (OOM) 情况而终止。整合可以使这种情况更有可能发生,因为只有考虑节点的请求才能将 pod 打包到节点上。
LimitRanges 用于配置资源请求和限制的默认值
由于 Kubernetes 不设置默认请求或限制,因此容器对底层主机、CPU 和内存的资源消耗不受限制。Kubernetes 调度器会查看 Pod 的总请求数(来自 Pod 容器的请求总数或容器的 Init 容器的总资源中较高者),以确定将 Pod 调度到哪个工作节点上。同样,Karpenter 会考虑 pod 的请求,以确定其提供的实例类型。你可以使用限制范围为命名空间应用合理的默认值,以防某些 pod 未指定资源请求。
将准确的资源请求应用于所有工作负载
当有关您的工作负载要求的信息准确时,Karpenter 能够启动最适合您的工作负载的节点。如果使用 Karpenter 的合并功能,这一点尤其重要。
CoreDNS 推荐
更新 CoreDNS 的配置以保持可靠性
在由 Karpenter 管理的节点上部署 CoreDNS 容器时,鉴于 Karpenter 在快速终止/创建新节点以适应需求方面的动态特性,建议遵循以下最佳实践:
这将确保 DNS 查询不会被定向到尚未准备就绪或已终止的 CoreDNS Pod。
Karpenter 蓝图
由于 Karpenter 采用应用程序优先的方法为 Kubernetes 数据平面配置计算容量,因此您可能想知道如何正确配置常见的工作负载场景。Karpenter Blueprin