嘿,最近我们遇到了一个EKS集群上的小麻烦:(EKS集群可能需要额外解释)
我们发现,我们预配的资源中有一大部分没有被使用。查看CPU使用情况后,发现我们可以在EKS计算成本上省下不少钱。
因为我们有许多具有不同计算需求的工作负载(有些工作负载对CPU需求较高;而其他工作负载则主要依赖内存),我们的大部分节点只有一部分被使用。因此,我们决定尝试一下Karpenter,这是一个不同的节点调度机制。
先剧透一下:用了 Karpenter 后,我们来聊聊我们的资源使用情况吧
这两个图表的绝对的CPU请求数量相同。
作为Karpenter迁移准备的一部分,我们审核了现有资源的请求和限制。特别是将它们添加到Daemon Sets时,这影响了最终资源使用图的客观性。
简而言之,Karpenter 是 Amazon EKS 该链接解释了为什么应该从节点终止处理器迁移 的 cluster-autoscaler 和 aws-node-termination-handler 的替代方案,并提供一系列强大的功能来帮助管理集群中的节点。
先说一下:Karpenter for AKS (Azure) 已于2023年11月7日上线。然而,本文主要介绍在 EKS 集群中使用 Karpenter 的经历。
一旦安装了 Karpenter,它会留意集群中任何不可调度的 Pod。要添加新节点,你需要至少一个 NodePool,NodePool 实际上是指 NodeClass 这个概念。
NodeClass 是一组 AWS 特定的节点配置:AMI、资源标签、EBS 映射等英文术语。
NodePool 是一组节点要求:可用区、CPU 数量、实例家族等。它还描述了污点(taints)和标签(labels)以及 kubelet 配置。你可以创建多个 NodePools 以确保能充分利用购买的预留实例和节省计划(https://karpenter.sh/preview/concepts/scheduling/#savings-plans-and-reserved-instances)或在不使用污点和容忍的情况下维持多架构环境(https://karpenter.sh/preview/concepts/scheduling/#fallback)。
#提示:请注意正确使用权重参数。它让您能够设定NodePool选择的优先级,但不保证Pod一定会被调度到权重最高的节点上。请参见本节的说明.
以下图示是参照Karpenter文档制作的。(注:Karpenter是相关领域的专有名词。)
为了配置新的节点,Karpenter 通过与 EC2 Fleet API 通信,并试图根据基于价格和容量优化的分配策略来配置实例。
为了处理突发中断事件,Karpenter 使用默认的 EventBridge 事件总线和一个用于接收 Instances 中断事件的 SQS 队列。这种方式让 Karpenter 能够监控潜在的实例终止事件,并为优雅终止的 Pods 准备替代节点。
该突发中断处理功能可以通过指定_Helm图表配置的settings.interruptionQueue 来启用。
Karpenter 最佳实践 特别强调 两次 检查您的工作负载的资源请求和限制。必须准确设置内存资源的请求量等于限制量,以避免在迁移到 Karpenter 后内存不足导致的任务失败。
设置EKS组件工作负载的资源时,使用组件配置中的值。
当然,你希望在已经准备好的节点上运行Karpenter,这些节点不管怎样都能正常运行。因为没有其他节点的情况下,Karpenter无法自己创建节点,所以你需要创建一个EKS节点组。
#小贴士:您可以使用AWS Graviton实例族来创建Karpenter EKS节点组,以节省一些成本,同时保持Karpenter的功能不变。
为了简化,我们使用了EKS模块中的Terraform Karpenter子模块。该子模块包含了用于在集群中部署Karpenter所必需的必要AWS资源。
使用 karpenter.sh/discovery 标签指定目标安全组和VPC子网,这样Karpenter就知道在哪里部署节点了。
现在,先在你的集群中安装Karpenter Helm chart,并将 toleration 和 nodeSelector 设置为 Helm 图表的值,让 Karpenter 在 Karpenter NodeGroup 上运行。
在安装过程中,你可能会遇到Helm图表的拉取、探索Karpenter标签等问题。这个优秀的Karpenter部署示例(请参见链接)可以快速帮助你解决这些问题。
我们将NodePools修改为仅使用经过测试且能够处理我们工作负载的实例类别。
我们用新的内存资源更新了工作负载,因此请求和限制相等。
原因是我们达到了节点池的资源上限。必须设置合理的资源上限以限制和控制集群中的资源数量。这是必须的。
尽管 Karpenter 还未直接由 Uniskai 支持,但 Cloudsitter 功能可以帮助你安排让 Kubernetes 命名空间(命名空间)进入休眠。
这就是我们做事的方式哦:
假设你有一个安装了 Karpenter 的 EKS 集群。在该集群中,命名空间可以划分为几个逻辑分组:
我们希望 Karpenter 能够一直正常运行,因此我们将为它创建一个全天候运行的 Cloudsitter 策略。
我们希望所有的管理工具从周一到周五的特定时间都能正常运作,因此,我们打算制定一个相应的策略。
我们希望根据实际情况手动启动工作负载,因此将创建一个24/7休眠策略。当实际需要时,我们只唤醒所需的命名空间。
不过,这种方法有个问题,无法完全保证Karpenter在禁用了所有Namespace后释放所有节点。但通常情况下,只会有少量实例未被释放。
随着时间的推移,我们发现这种方法在较小的集群中效果不佳。我们也遇到了在处理难以轻松缩减的负载量时的挑战。尽管如此,这个解决方案在多种场景下仍然很有优势——关键是根据每个集群及其特定负载特性进行调整。
借助 Karpenter 和 Uniskai Cloudsitter,我们创建了一个集群,该集群让我们按需控制哪些工作负载,并在最省钱的实例上运行它们。
在采用Karpenter为我们的EKS集群时,我们见证了一种变革性的转变,这种转变体现在云资源优化和成本效益方面。Karpenter的NodePools所提供的控制的精细程度,加上其快速的资源分配和智能处理突发性中断的能力,大幅度提升了资源利用效率。
尽管遇到了一些挑战,比如调整 NodePools 以适应特定实例类别和精确设置资源限制,Karpenter 和 Cloudsitter 的集成使我们能够动态管理 Kubernetes 命名空间,从而实现了可用性和成本效益之间的微妙平衡。