Grafana 团队在他们的官方博客中介绍了他们从采用 Cluster Autoscaler 转向 Karpenter 的历程,本文将介绍他们如何进行选型,以及使用 Karpenter 的实践经历。
Grafana 是一款业界最为流行的可视化分析工具。2022 年,Grafana 首次登陆 AWS 并开始使用亚马逊弹性 Kubernetes 服务(Amazon EKS)时,选择了Cluster Autoscaler(以下简称“CA”)作为自动扩展工具。它开放且简单,而且在此之前,它已经过无数人的实战检验。然而,没过多久,Grafana 团队就意识到它并不适合自己。
这篇文章将深入探讨为什么 CA 不是长期解决方案,Grafana 考虑过哪些替代方案,以及为什么最终选择了Karpenter。此外,还将探讨一些相关的得失和经验教训,以及 Karpenter 如何帮助 Grafana 降低了成本和复杂性,进而帮助他们为使用 AWS 的用户提供了更好的服务。
免责声明:本文使用的是 Karpenter v1alpha5 API 规范。该项目最近在 v1beta1 规范中引入了一些突破性的变化,同时对核心组件进行了重命名。例如,Provisioners 已更名为 NodePools。为了保持一致性,本文决定保留 v1alpha5 版的命名,因为撰写本文时(2023.11)正在运行的是这个版本,并且掌握相关经验。
采用 CA 时面临的挑战
Grafana 开始使用 CA 时遇到了许多阻碍,它们限制 EKS 集群效率和灵活性。在了解如何通过采用 Karpenter 来解决其中的许多问题之前,让我们先来了解一下导致 Grafana 遇到这些问题的原因。
请求的容量与实际获取的容量(CPU requested vs. obtained)
CA 通过扩展和缩减 Kubernetes 节点组来工作。它监控那些无法调度现有节点的待定 Pod,并根据节点组的定义和其底层的自动扩展组(Auto Scaling Groups)来提供新节点,以便这些 Pod 可以调度到新的节点上。
在 AWS 上,你可以为节点组定义多种实例类型。然而,如果为节点组列出多个实例类型,CA 只会对其中一种实例类型进行计算,以确定需要扩展的节点数量。 然后,它会向 AWS 请求相应数量的实例,但你无法控制实际获取的实例类型。这可能会导致获得的容量与实际需求不匹配,从而需要进行更多的调整。
为了解决这个问题,CA 建议在定义节点组时,尽量使用相同规格的实例。这是一个相对简单的解决方案。
举个例子,与其在同一组中同时包含 m5.2xlarge
和 m5.4xlarge
实例,不如定义两个独立的节点组,分别包含每种实例规格。这样一来,CA 就可以专注于每个节点组的特定实例规格,并根据该规格进行资源计算。
这种方法的缺点在于,CA 在选择最佳实例时的选项有限。最终,你会在两个节点组之间进行随机选择,这可能会导致节点资源未被充分利用,进而引发频繁的节点更替。
假设你需要三台 m5.2xlarge
实例的容量。如果 CA 选择了 m5.2xlarge
节点组,它会计算需要三个实例并发出请求。但如果它选择了 m5.4xlarge
节点组,它会请求两个实例,其中至少有一个实例会被低效利用。根据你设定的阈值,这个实例可能会被标记为待删除,因为其利用率低于设定的利用率阈值。
云妙算的智能节点选择功能,为用户的工作负载自动匹配多样化的实例类型,以减少资源浪费,提升计算性能,增强应用稳定性。
基础设施的复杂性和僵化性增加
由于上述原因,基础设施团队不得不定义越来越多的专用节点组,以满足工作负载的需求。这反过来使开发人员在不同实例类型上运行工作负载实验变得更加困难,因为每次实验都意味着要创建一个新的节点组并停用之前的节点组。
CA 无法稳定高效地管理 Spot 实例
当 Grafana 尝试使用 AWS 的 Spot 实例时,这一点给其带来了很大困扰。Grafana 希望利用这种高度波动且价格低廉的计算选项,但不希望它干扰其正常的按需扩展计划。然而,CA 在选择节点组时并不考虑像 preferredDuringSchedulingIgnoredDuringExecution
这样的“软”约束。
如果一个工作负载偏好使用 Spot 节点,并且该节点有空间,Kubernetes 调度器将尝试将其放置在该节点上。但如果没有空间,CA 在决定扩展哪个组时不会考虑这一点。在本例中,这意味着他们只能通过设置 requiredDuringSchedulingIgnoredDuringExecution
属性,将 Spot 节点作为硬性要求,才能确保工作负载使用 Spot 实例。
接下来,他们遇到了非常棘手的资源短缺问题——这是使用必须依赖 Spot 实例时常见的挑战。原因在于 CA 只会向 AWS 请求扩容,但不会检查这些实例是否真的可用。 如果 Spot 实例不够用,无法让 CA 自动切换任何其他可用的Spot实例或者切换到按需实例,结果就陷入了扩容的“死循环”。
如果工作负载只是偏向使用 Spot 实例,这个问题倒不大;但当工作负载必须使用 Spot 实例时,就可能导致资源短缺的情况。本来,一个成熟的平台应该能自动解决这个问题,但在 Grafana 的配置中,它最终给开发人员带来了麻烦,使得他们在尝试使用 Spot 实例时要面对更复杂的配置。
云妙算提供的 Spot automation 功能可以完美解决这一问题,它既能精准预测 Spot 实例的中断,又能自动将应用回退至稳定运行的实例中(既有Spot也有按需实例),保证业务稳定运行,同时降低云成本。而这一切,无需 DevOps 或 SRE 工程师的手动操作,均交由云妙算自动化运行。
不尽人意的 bin-packing 和可靠性问题
这个问题最终让 Grafana 放弃了当前方案。他们一直在监控集群的内存和 CPU 综合利用率,结果发现 AWS 集群的表现远不如在其他云提供商上的集群。这意味着他们一直在烧钱,有些集群的空闲率甚至超过了 40%!
此外,Grafana 还遇到了 Amazon EKS 的 Pod 数量上限问题。对于那些需要放在一起但不需要大量资源的工作负载,当达到允许的最大 Pod 数量时,其节点容量才刚使用了一半左右。甚至有一次,CA 因为认为节点未被充分利用,主动缩减了一个节点组,而此时一些 Pod 却因为没有足够的 IP 无法调度到其他地方,导致它们一直处于待定状态。
于是,Grafana 开始寻找其他解决方案。
评估新的Autoscaling方案
Grafana 团队研究了 CA 的配置选项,想知道是否是团队没有正确使用这个工具。不过,现有的设置和选项并不足以解决他们面临的问题。看起来,这个工具并不能满足 Grafana 的需求。
Grafana 团队开始怀疑是否他们的需求真的和其他人如此不同,并考虑开始自己定制一款调度器。但花了一些时间研究这个想法之后,很快意识到这个项目的规模实在太大,成本也会非常高,而且这肯定无法实现标准化。如果有更好的替代方案,他们不想选择这种做法。
Karpenter:它真的能像听起来那么好吗?
Grafana 团队对 Karpenter 早有耳闻,它是开源的,这与 Grafana 的核心价值观之一相符(开源是其 DNA)。它得到了强大的社区支持,并且还申请成为 CNCF 项目。
相对 CA 来说,Karpenter 是一个重大的范式转变。它通过所谓的 provisioner 和节点模板(node templates)按需创建节点,抛弃了预定义节点组的概念(之前是通过 Terraform 代码编写的)。Karpenter 的 provisioner 充当了一组节点应符合的要求。不可调度的工作负载会与这些 provisioner 匹配,从而选择最佳节点进行运行,并将其启动。
这种基于 Kubernetes 原生资源的智能容量管理不仅意味着更大的灵活性,还将使开发人员更容易使用。开发人员可以根据自己的需求调整这些资源,并以他们已经熟悉的方式进行操作,从而使实验变得更加简单。此外,文档中提到,这将使 Grafana 的集群准确运行在所需的资源上——既不会多也不会少,这有望将其空闲比例控制在合理范围内。
它还宣传了通过容量类型选择从 Spot 到 On-Demand 实例的原生回退(fallback),以及向 Provisioner 添加权重的可能性,这样团队就可以更精细地控制自己的回退选项。此外,Karpenter 直接与 AWS 的 EC2 API 进行交互,这意味着它可以快速响应资源短缺的情况,立即回退到其他定义的选项。
此外,Karpenter 性能明显优于 CA,它可以在15秒内将 Pod 从1个扩展至1000个。
它似乎满足了我们的所有需求,但也与之前的设置大相径庭。因此,该团队开始着手评估它是否真的能满足需求。
在不影响用户的情况下迁移到 Karpeneter
在 Grafana Labs 部署 Karpenter 时,使用 Jsonnet 配置了它的部署,并用 Terraform 定义了它所需的基础设施,例如 IAM 角色和 SQS 队列等。Karpenter 控制器不应该在 Karpenter 提供的节点上运行,因为这样会导致其自身被调度失败,无法恢复。
在本例中,Grafana 团队为静态的关键集群组件运行了一个专用的节点组,但如果他们想去除所有节点组,它也可以在 AWS Fargate 上运行。
在验证了 Karpenter 可以正常运行后,Grafana 团队还得想办法进行切换。如前所述,他们的设置相当复杂,需要一种方法来切换自动扩展工具,这样就不会对用户造成任何干扰。此外,还不能同时运行这两个工具,因为它们在职责上会产生冲突。
团队需要想办法让 Karpenter 接替 CA 的工作,绞尽脑汁,终于想出了一个可行的解决方案。关键在于按照正确的顺序进行操作。
Karpenter 不会在没有 Provisioner 的情况下进行任何操作。只有当 Kubernetes 调度器将一个 Pod 标记为不可调度,并且能够将该 Pod 匹配到其 Provisioner 之一时,它才会启动节点。这意味着你可以在没有 Provisioner 的情况下部署 Karpenter 控制器,它不会干扰 CA。
然后,你只需在关闭 CA 的同时给 Karpenter 提供 Provisioner 和节点模板,它就可以接管工作。此时,只需将旧的 CA 管理的节点组清空,然后让 Karpenter 开始工作即可。一旦所有工作负载都迁移到 Karpenter 提供的节点上,你就可以彻底删除所有旧的节点组。
通过这种策略,加上 Grafana 自己开发的一些工具,他们成功地在生产环境中透明地迁移了这个核心基础设施组件,既对开发人员,也对最终用户都没有造成干扰。如今,Grafana 在所有 AWS 集群中只需运行一个节点组,专门用于关键集群组件,并通过污点(taints)进行保护,而 Karpenter 则处理所有常规业务工作负载。
CA 与 Karpenter 的对比结果
Karpenter 被寄予很高的期望,还好结果并没有让人失望。
闲置率和总成本降低
闲置率平均下降了 50%。 Grafana 团队给自己设定了一个挑战性的目标,力争降到 15%。虽然并不是所有的集群都达到了这个目标,但这已经是迁移后的结果。团队仍在探索可以调整的各种参数。
仅此一项就意味着相应的成本降低;更好的资源利用率意味着不必为过多的闲置容量付费。此外,Karpenter 最强大的卖点之一是其整合(Consolidation)能力。
Karpenter 会不断计算集群工作负载的整体需求以及当前运行这些工作负载的基础设施。如果它确定有更便宜的节点配置可用,并且满足其提供者的约束条件以及工作负载的需求,它将逐步撤销并替换节点,直到整个集群的成本得到优化。
给 Karpenter 提供的选项越多(实例类型、系列、大小等),它就越能根据 AWS 的实时可用性来优化你的成本。由于 Karpenter 为你完成了繁重的计算,你不必花太多精力去决定哪种实例类型最适合你的集群;它可以自行处理。在本例中,它最终偏向于一个被忽视了几个月的特定实例类型。
与 CA 不同,Karpenter 能够进行复杂的计算,并请求满足你确切需求的实例类型组合,因为它与 EC2 API 持续交互,能够按需及时提供节点,并且不受限制于严格定义的实例池。
请注意,整合仅适用于按需实例。Karpenter 对于 Spot 实例采取了略有不同的方法。
Grafana 最大的集群工作负载的利用率和分布情况如下:
变成这个状态:
该可视化图表展示了内存和 CPU 的利用率比。团队努力将两者尽可能接近 1(右上角),这意味着完全利用。然而,由于 CPU 是 Grafana 主要的成本驱动因素,因此使用点的大小来直观地表示这些节点的核心数量。点的颜色则表示价格——绿色代表较便宜的节点,而红色则表示较昂贵的节点。
如前所述,CPU 是主要的成本驱动因素,这意味着节点的大小在某种程度上与价格相关:点越大,通常情况下价格越高。这意味着团队最关心的是最大的、最红的点,而且更关注将它们移动到图表的右侧(最大化 CPU 利用率),而不是移动到顶部(最大化内存利用率)。
在这里可以看到,最相关的节点从顶部中间移动到了顶部右侧,并且它们聚集得更加紧密。意味着这次实验成功了!
提高可靠性
如果某项变更可以节省你的成本,往往意味着你需要在可靠性上付出代价。然而,在本例中,情况正好相反。
Karpenter 本身支持指定容量类型,并优先使用 Spot 实例。如果没有可用的 Spot 实例,它会自动回退到按需实例。
此外,它解决了Grafana 团队在使用 CA 时面临的一个主要问题:Karpenter 在决定是否可以删除节点时会考虑最大可用 pod 数量,确保不仅有足够的资源,还有可用的 IP 地址供被驱逐的 pod 使用。
通过简单地用一个工具替换另一个工具,它解决了 Grafana 面临的两个最严重的可靠性问题。
如何处理节省计划?
在这一点上,团队遇到了一个没有考虑到的问题。Karpenter 的表现越好,提供的选项就越多。但在 AWS 中,只有计算节省计划(Compute Savings Plans)才能给你这样的灵活性。Grafana 被锁定在一些预先存在的 EC2 实例节省计划中,需要先完成这些计划,然后才能在财务上合理地切换到完全灵活的方案。
结果,Karpenter 恰好满足了 Grafana 的需求。我们通过利用 Provisioner 的两个功能来解决这个问题:权重和限制。根据 EC2 节省计划承诺计算所需的 CPU 数量,进而创建一个权重较高的Provisioner ,并将其限制在所需的 CPU 数量内。
一旦该 Provisioner 处理的 CPU 达到其限制的上限,Karpenter 就会转向第二个权重较低的 Provisioner,充分利用 AWS 提供的所有灵活性。与此同时,Grafana 还签订了一些计算节省计划(Compute Savings Plans),此时这些计划开始发挥作用。这使在履行合同义务的同时,仍然能够利用 Karpenter 的优化潜力。
更好的开发者体验
由于 Provisioner 是 Kubernetes 对象,而 Karpenter 可以处理任何 Provisioner 的各种实例类型,因此开发人员现在可以少走很多弯路。他们只需设置一个与其工作负载匹配的 Provisioner,然后进行调整即可。
要查看对 Provisioner 的更改是否生效,有几种选择。由集群相对动态,常规的整合周期通常足以看到更改的效果,尤其是在调整现有 Provisioner 时。旧节点被去除,新节点则根据新的配置启动。但在早期实验阶段,通常的做法是开发人员通过清空旧节点或增加更多副本来启动新配置的节点。
不过需要特别提醒的是,删除一个 Provisioner 将会删除它所管理的所有节点。因此,务必让开发人员了解这一风险,这样他们就可以放心地进行实验。
灾难恢复
Karpenter 的即时节点配置使其非常适合灾难恢复场景。用户只依赖于一个静态节点池,即运行 Karpenter 及其所有关键组件的节点池,以确保集群能够正常运行。在灾难发生的情况下,这意味着我们可能需要干预以重建的组件数量最少。与之前基于节点组的 AWS 集群方法相比,这种方式更加高效,所需的人力干预大大减少,并且在紧急情况下重建整个集群时出错的可能性也更小。
简化基础设施运维:简化 Kubernetes 升级
Karpenter 的设计带来了另一个显而易见的好处。对 Provisioner 和控制平面的更改会自然地通过常规集群流程传播。这意味着,一旦控制平面升级,Karpenter 所配置的所有新节点将自动使用新的 Kubernetes 版本。无需创建新的节点组或标记旧的节点组。理论上,你只需让系统自行运行即可。
为了保持更精细的控制,Grafana 使用内部工具来触发对所有旧节点的有序排空,但这完全是可选的。同时,确保没有节点在系统中停留过久,通过设置 Karpenter 使其对旧节点施加 30 天的生存时间限制(TTL)。到期后,Karpenter 会将这些节点标记为过期,并优雅地将其删除。通过这种方式,我们确保底层基础设施不会与代码定义的基础设施产生太大偏差。
权衡利弊
每个决策都有其权衡。在本例中,由于在撰文时 Karpenter 主要支持 AWS(针对阿里云的支持,云妙算团队正联合阿里云团队紧锣密鼓地适配中,即将推出,敬请期待),采用 Karpenter 意味着放弃了在所有云厂商中实现统一扩展解决方案的愿景。目前而言,仅能为其中一个提供商提供独特的扩展解决方案。
尽管 Karpenter 已经被证明是一种非常强大的解决方案,但它仍在快速发展中,距离成熟项目还有一段距离。整合(Consolidation)是其最重要的特性之一,但撰文时尚不支持 Spot 实例,也没有简单的解决方案可供参考。这是一个项目尚未解决的难题。
总结
从 Cluster Autoscaler 切换到 Karpenter 对 Grafana 来说是一次巨大的成功。Karpenter 的设置比 CA 稍微复杂一些,但结果却是非常积极的,它最终实际上简化了我们的基础架构。对于更大、更复杂的 EKS 集群来说,它似乎是最合适的工具。
作为一家公司,Grafana Labs 一直以来都很注重成本,因此,闲置率是他们始终关注的一个指标,这样就能充分利用现有资源,并提供最佳服务。这一变化显著降低了 AWS 集群中的闲置率,使其变得更加高效。
选择可用的 AWS 实例并在不同实例类型之间灵活切换,不仅提高了成本效率,还改善了服务的可靠性。额外的好处是,它增强了在潜在灾难恢复场景中的应对能力,因此如果真的发生了,团队可以更快地恢复正常操作。
迁移过程本身也相对简单,没有遇到任何重大问题。这一过程可以在不出现停机的情况下进行,并且可以根据需要调整速度。
完成迁移后,关键在于让信息透明,让开发者熟悉这一新范式。在本例中,Grafana 团队成员从最初对变革的抵制到很快就被易用性和实验性所折服。
标签:CA,50%,Spot,Grafana,实例,Karpenter,节点 From: https://www.cnblogs.com/cloudpilot-ai/p/18534888/granafa-karpenter