View a markdown version of this page

Karpenter - Amazon EKS

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

Karpenter

Karpenter 是一个开源项目,旨在增强 Kubernetes 集群中的节点生命周期管理。它根据 Pod 的特定调度需求自动配置和取消配置节点,从而实现高效的扩展和成本优化。它的主要功能是:

  • 监控 Kubernetes 调度器由于资源限制而无法调度的容器。

  • 评估不可调度的 Pod 的调度需求(资源请求、节点选择器、亲和力、容忍度等)。

  • 配置满足这些 Pod 要求的新节点。

  • 在不再需要节点时将其移除。

使用 Karpenter,您可以定义节点配置 NodePools 的限制,例如污点、标签、要求(实例类型、区域等)以及对总预置资源的限制。部署工作负载时,您可以在 pod 规范中指定各种调度约束,例如资源 requests/limits、节点选择器、 node/pod 亲和力、容差和拓扑分布约束。然后,Karpenter 将根据这些规格配置大小合适的节点。

使用 Karpenter 的原因

在Karpenter推出之前,Kubernetes用户主要依靠亚马逊EC2自动扩展组 Kubernetes集群自动调节器(CAS)来动态调整其集群的计算容量。使用 Karpenter,您无需创建数十个节点组即可实现 Karpenter 所获得的灵活性和多样性。与 CAS 不同,Karpenter 与 Kubernetes 版本的耦合不那么紧密,也不需要你在 AWS 和 Kubernetes API 之间切换。

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 处理容量需求不断变化的工作负载

自动缩放组(ASG)和托管节点组(MNG)相比,Karpenter 使扩展管理更接近于 Kubernetes 原生 API。ASG 和 MNG 是 AWS-native 抽象概念,其扩展是基于 AWS 级别的指标(例如 EC2 CPU 负载)触发的。集群 Autoscaler 将 Kubernetes 抽象与 AWS 抽象相桥接,但因此失去了一些灵活性,例如针对特定可用区进行调度。

Karpenter 删除了 AWS 抽象层,以便将部分灵活性直接带入 Kubernetes。Karpenter 最适合具有工作负载的集群,这些集群会遇到高峰需求或具有不同的计算需求。MNG 和 ASG 适用于运行往往更加静态和一致的工作负载的集群。根据您的要求,您可以混合使用动态和静态管理的节点。

在... 时考虑其他自动缩放项目

你需要在 Karpenter 中仍在开发的功能。由于Karpenter是一个相对较新的项目,如果您需要尚未包含在Karpenter中的功能,请暂时考虑其他自动缩放项目。

在 EKS Fargate 或属于节点组的工作节点上运行 Karpenter 控制器

Karpenter 是使用 Helm Chart 安装的。Helm 图表将 Karpenter 控制器和一个 webhook pod 作为部署进行安装,需要先运行该容器,然后才能使用控制器扩展集群。我们建议至少有一个包含至少一个工作节点的小型节点组。作为替代方案,你可以通过为 EKS Fargate 命名空间创建 Fargate 配置文件来在 EKS Fargate 上运行这些 pod。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 支持原生中断处理,可以处理非自愿中断事件,例如竞价型实例中断、计划维护事件、可能会中断您的工作负载的实例termination/stopping 事件。当 Karpenter 检测到节点的此类事件时,它会自动污染、排空和提前终止受影响的节点,以便在中断之前开始正常清理工作负载。对于提醒 2 分钟后出现的竞价中断,Karpenter 会快速启动一个新节点,这样可以在回收实例之前移动 pod。要启用中断处理,请使用为此目的配置的 SQS 队列的名称配置 --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"}

请参阅此文档,在完全私有的 EKS 集群中使用 Karpenter,并了解要创建哪些 VPC 终端节点。

正在创建 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 来生成符合您的计算要求的实例类型列表。例如,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

使用竞价型实例时,不应对 Karpenter 施加太多限制,因为这样做会影响应用程序的可用性。例如,某一特定类型的所有实例都被回收了,没有合适的替代方案可以替换它们。在补充已配置实例类型的竞价容量之前,您的 pod 将保持待处理状态。您可以通过将实例分布在不同的可用区来降低容量不足错误的风险,因为各可用区的竞价池不同。也就是说,一般的最佳实践是允许 Karpenter 在使用 Spot 时使用不同的实例类型。

调度 Pod

以下最佳实践与使用 Karpenter 进行节点配置在集群中部署 pod 有关。

遵循 EKS 最佳实践以实现高可用性

如果您需要运行高可用应用程序,请遵循一般 EKS 最佳实践建议。有关如何跨节点和区域分布 Pod 的详细信息,请参阅 Karpenter 文档中的拓扑分布。使用中断预算来设置需要维护的最低可用容器,以防有人试图驱逐或删除 Pod。

使用分层约束来限制云提供商提供的计算功能

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 在新节点中的动态特性以适应需求,建议遵循以下最佳实践:

CoreDNS lameduck 持续时间

CoreDNS 就绪情况调查

这将确保 DNS 查询不会被定向到尚未准备就绪或已终止的 CoreDNS Pod。

Karpenter 蓝图

由于 Karpenter 采用应用程序优先的方法为 Kubernetes 数据平面预置计算容量,因此您可能想知道如何正确配置这些常见的工作负载场景。Karpenter 蓝图是一个存储库,其中包含遵循此处描述的最佳实践的常见工作负载场景列表。您将拥有所需的所有资源,甚至可以创建配置了 Karpenter 的 EKS 集群,并测试存储库中包含的每个蓝图。您可以组合不同的蓝图,最终创建工作负载所需的蓝图。

其他资源