原文链接:
https://medium.com/adevinta-tech-blog/the-karpenter-effect-redefining-our-kubernetes-operations-80c7ba90a599
编译:CloudPilot AI
Adevinta 是世界最大的在线分类广告商之一,其业务遍布全球9个国家及地区,每个月吸引超过 1.2 亿用户和 100 万家企业,2023 财年总营业额达 18 亿欧元。本文将介绍 Adevinta 迁移至 Karpenter 的落地实践。
在 Adevinta 内部,已经对 Karpenter 的特性和它解决关键运维难题的潜力感到兴奋已久。如今,Adevinta 已经完成了从 Amazon EKS 托管节点组到 Karpenter 的全面迁移,现在正是总结这段历程并分享其中经验的最佳时机。
问题概述
管理一个由 2,000 多个 Kubernetes 节点、30 个集群组成的庞大系统,并服务于 25 个不同的市场,这绝非易事。虽然一开始使用 Kubernetes Cluster Autoscaler 和 Amazon EKS 托管节点组表现良好,但随着时间推移,Adevinta 逐渐遇到了阻碍效率和扩展性的运维难题。
集群升级的复杂性、实例类型选择的局限性,以及用例灵活性不足等问题,越来越成为 Adevinta 的负担。团队迫切需要一个能够正面解决这些挑战的解决方案。
引入 Karpenter
Karpenter 是一款开源的高性能 Kubernetes 自动扩缩容工具,目前已捐献给 CNCF。与传统的自动扩缩不同,Karpenter 能够动态地在实时环境中为集群工作负载提供所需的计算资源。它通过观察未调度 pod 的资源请求总量,智能决策并启动精准匹配需求的新节点。
Karpenter 项目地址:
集群升级与维护变得轻松自如
过去,升级 Kubernetes 集群是一件令人头疼的事,尤其是在使用 EKS 托管节点组并通过 AWS CDK 进行资源配置时。控制平面与节点组升级之间的紧密耦合,使整个过程容易报错。任何问题,比如配置错误或实例资源短缺,都会导致升级失败并陷入回滚的循环。
对于大型集群,这一挑战更加艰巨。升级数百个节点同时将影响降到最低可能需要好几天,工程师必须全程密切监控。这不仅耗费时间和资源,而且常常由于实例可用性等外部因素导致失败,使升级过程更加复杂。
更新节点组时的硬依赖会导致整个升级回滚并卡住
失败原因不可控
为缓解这些风险,Adevinta 曾在执行 Kubernetes 升级前实施容量预留。然而,这种方法效率低下且缺乏可扩展性。
引入 Karpenter 后,升级流程变得更加简单。控制平面与节点池升级实现了解耦,使控制平面的升级变得更加简单,**大多数升级可在 15 到 30 分钟内完成。**Karpenter 异步管理工作节点的升级,当控制平面版本更新时,它会检测到 AMI(Amazon Machine Image)的变化,并通过标记为"漂移"的方式识别需要升级的节点。
{"level":"INFO","EC2NodeClass":{"name":"default"},"parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-arm64/recommended/image_id","value":"ami-0d494a2874a2e7ec1"}
{"level":"INFO","EC2NodeClass":{"name":"default"},"namespace":"","name":"default","reconcileID":"b99dbc2a-5112-4121-ab64-7e512bb0399f","parameter":"/aws/service/eks/optimized-ami/1.30/amazon-linux-2-gpu/recommended/image_id","value":"ami-01389330bfd276054"}
一旦检测到新的 AMI,Karpenter 就会将现有版本标记为漂移,并开始自动升级。
漂移的节点会逐步被新版本替换,无需人工干预。这种分离极大地降低了升级所需的工作量,并且不再需要持续监控。
对中断的精细化控制
Karpenter 对 Pod 中断预算(PDB)的严格遵守,为 Adevinta 的运维带来了显著改进。此前,即便启用了序列化选项(serialised option),Cluster Autoscaler 仍会在短时间超时后强制移除节点,导致服务频繁中断。而现在,Karpenter 能在设定的中断范围内从容应对,极大地减少了服务影响。
Adevinta 曾在使用托管节点组时遇到问题,例如重命名节点组或调整副本最小数量,这些操作经常引发比预期更严重的中断。这种情况让升级过程变得"鸡飞狗跳",还经常引发故障,影响了客户的正常使用。这种不可预测性让 Adevinta 对升级望而却步,因为它们通常会导致计划外的停机并增加额外的工作负担。
更多节点同时中断,导致应用程序停机
由于多种原因,受管理的节点群受到的干扰可能比我们预期的要大
通过 Karpenter,Adevinta 在节点中断管理方面获得了更大的掌控力 ,尤其是在升级过程中。**其核心优势之一是可以灵活配置中断预算(Disruption Budget),精准控制节点更新的频率。**例如,可以设置每 15 分钟仅更新一个节点的策略,大幅降低对运行服务的影响。这种渐进、可控的方式确保了服务稳定性,将升级过程中的停机时间降到最低,解决了过去的一大难题。
Karpenter 的另一大优势在于其在不同节点池类型间配置中断预算的灵活性。Adevinta 现在可以根据工作负载的重要性分配不同级别的可靠性要求。对于关键任务工作负载,严格限制节点中断;而对于非关键任务工作负载,则采取更宽松的限制,从而在集群内实现资源的最优管理与服务的高可靠性。
中断预算配置允许在 15 分钟窗口期内最多中断一个实例
Karpenter 的中断预算与用户配置的 Pod 中断预算(PDB)紧密配合,确保升级过程中的高可靠性。例如,在设定的 15 分钟窗口内,如果 PDB 要求不允许任何节点中断,Karpenter 将跳过该窗口,确保服务的持续稳定。这种机制在多租户场景下尤为重要,因为它能够完全满足用户对可靠性的严格要求,确保服务始终处于高可用状态。
一个节点每 15 分钟中断一次,但如果 PDB 所需的时间超过 15 分钟,Karpenter 会等待下一个窗口再进行尝试。
此外,在 v1 版本中,Karpenter 支持针对不同类型的中断原因(如节点合并、节点过期或规格变更)指定不同的时间窗口。这种精细化管理使 Adevinta 能在保持高可用性的同时,顺利完成必要的维护工作,从而在稳定性与维护效率之间实现了良好的平衡。
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
expireAfter: 720h # 30 * 24h = 720h
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
budgets:
- nodes: "20%"
reasons:
- "Empty"
- "Drifted"
- nodes: "5"
- nodes: "0"
schedule: "@daily"
duration: 10m
reasons:
- "Underutilized"
Karpenter 还支持一种名为"双跳"(double jump)的升级策略。在"双跳"过程中,Adevinta 可以连续升级 EKS 控制平面两个版本,只需进行一次集群重建。具体做法是,将 Karpenter 配置为在控制平面升级期间允许节点数为零,待控制平面连续升级两个版本后,再移除配置。这种方式有效简化了升级流程,大幅提升了升级效率,同时减少了中断风险。
disruption:
budgets:
{{- if has .Values.clusterName .Values.featureFlags.blockDisruption }}
- nodes: "0"
{{- end }}
当 Karpenter 检测到 Kubernetes 新版本时,它会自动将所有节点标记为漂移状态。因此,如果某个节点被标记为漂移节点,替换的节点将使用 Karpenter 在启动时发现的最新 AMI。例如,版本为 1.28 的节点将被升级到 1.30 版本的 AMI。
这一简单的功能大大节省了运维时间,因为通常进行一次全面集群升级需要几周的规划和执行。通过"双跳"策略,即使在严格的限制条件下,我们也能避免不必要的全体节点重建。尽可能避免中断 Pod,减少对用户的干扰,始终是最佳选择。
灵活选择实例类型,解决资源瓶颈
过去,选择实例类型是一个繁琐的过程。每个节点组都需要明确指定实例类型,并且必须严格满足资源需求。虽然使用云服务提供商意味着可以动态调度机器,但并不代表始终拥有无限的资源,尤其是在较小的区域,当出现实例资源耗尽的情况时,升级操作往往会失败。
实例耗尽的报错提示
在 AutoScalingGroup 中,存在辅助实例类型(secondary instance types)的概念,允许用户提供替代实例类型的列表以及节点组规格。然而,这个过程依然是手动的,容易出错且具有许多限制。例如,替代实例类型必须与主实例类型在 CPU 和内存上保持一致。随着规模的扩大,管理这一过程几乎成了一场噩梦。
添加辅助实例类型
Karpenter 通过让用户定义实例需求而非指定实例类型,成功抽象化了这种复杂性。Karpenter 会自动选择最合适且最经济的实例类型。这种灵活性不仅简化了管理工作,还有效降低了实例资源耗尽的风险,这对于 Adevinta 这样的规模来说尤为重要。
CloudPilot AI 的智能节点选择功能可以进一步优化 Karpenter 的动态实例选择,智能匹配超过750种实例类型,为用户的工作负载自动匹配多样化的实例类型,以减少资源浪费,提升计算性能,增强应用稳定性。
另一个额外的好处是,当有更好、更便宜或性能更强的实例类型可用时,Adevinta 不再需要时刻监控或进行 2000 多台机器的全体重建。Karpenter 会自动、渐进地完成这一过程,确保资源的持续优化。
针对不同工作负载定制节点池
在 Adevinta 的环境中,Karpenter 最重要的优势之一就是能够创建定制化的节点池,以满足不同工作负载的需求。对于管理一个多样化的应用生态系统来说,每个应用都有独特的资源需求和操作特性,这种灵活性至关重要。
通过 Kubernetes CRD 简化节点池创建
Karpenter 利用 Kubernetes 自定义资源定义(CRD)来定义资源调度行为,使节点池的创建变得更加简单,尤其是对于熟悉 Kubernetes 的用户。通过定义 Provisioner CRD,Adevinta 可以指定约束条件和优先级,例如:
-
实例类型与系列: 选择广泛的 EC2 实例类型或系列,以匹配不同工作负载的需求。
-
节点标签与污点: 为节点分配标签和污点,以控制 Pod 调度,确保工作负载部署到合适的节点。
-
Kubelet 配置: 自定义 kubelet 参数,以优化节点性能,满足特定应用的需求。
这种方法使 Adevinta 能以声明的方式管理节点配置,减少了维护多个 Auto Scaling Groups 或托管节点组的复杂性,尤其是在处理不同工作负载时。
满足特定工作负载需求
以下是 Adevinta 如何利用 Karpenter 来适应不同工作负载的几个实际示例:
1. GPU 密集型机器学习任务
Adevinta 的数据科学团队经常运行需要 GPU 加速的机器学习模型。过去,Adevinta 需要维护专门的 GPU 节点组,但这些节点组往往未得到充分利用且成本较高。借助 Karpenter,Adevinta 可以定义一个 Provisioner,设置如下规格:
-
**资源需求:**节点必须具备 GPU 能力(例如,NVIDIA Tesla V100)。
-
**实例类型:**优先选择 g4dn 实例系列。
-
**污点与标签:**应用污点,确保只有 GPU 工作负载被调度到这些节点上。
当有 Pod 请求 GPU 资源,并且该 Pod 满足我们指定的污点容忍条件时,Karpenter 会动态地为其配置一个 GPU 支持的节点。一旦工作负载完成,如果不再需要该节点,该节点可以被释放。这种动态扩展方式优化了资源的利用率,降低了成本,同时,通过污点和标签的配置,确保工作负载能够被调度到与所需资源匹配的节点类型上。
2. 专用节点用于监控和日志收集
Adevinta 的监控栈,包括 Prometheus 和日志收集器,受益于与其他工作负载的隔离,以避免资源争用、Pod 更替频繁和可能出现的"邻居噪声"效应(即当同一物理服务器上的其他用户突然占用更多的资源,如CPU、内存或I/O,可能会导致整个服务器性能下降,进而影响到所有用户)。通过使用 Karpenter,Adevinta 能够设置一个 Provisioner,具体配置如下:
-
**计算优化:**选择针对内存密集型工作负载优化的实例类型(例如,r5 系列)。
-
**应用特定标签和污点:**确保只有监控和日志收集的 Pod 被调度到这些节点。
-
**自定义 kubelet 配置:**自动调整设置,例如 kubelet 最大 Pod 数量和驱逐策略。
这种隔离有效稳定了 Adevinta 的监控服务,并减少了"邻居噪声"效应,避免了资源密集型工作负载影响同一节点上其他工作负载的性能。例如,将 Prometheus 工作负载隔离到专用节点上,不仅提升了性能,还带来了额外的成本节省。
增强稳定性的附加功能
像定义 startupTaints
这样的功能也非常宝贵。它们让 Adevinta 在调度 Pod 之前,能够提前准备好节点上的关键组件------例如 IAM 代理。这大大减少了由于应用程序被调度到尚未完全启动的节点上而导致的间歇性故障。通过确保节点在接受工作负载之前完全准备好,Adevinta 提升了服务的整体稳定性和可靠性。
通过 Kyverno 策略提供灵活性
为了无缝地向客户提供这种灵活性,Adevinta 使用了 Kyverno 策略。这些策略根据节点选择器自动为 Pod 分配容忍性,简化了用户的操作,并最小化了配置错误的风险。
例如,考虑以下 Kyverno 策略:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: enforce-gpu-taint
spec:
validationFailureAction: Enforce
background: false
rules:
- name: enforce-gpu-t4-taint
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.spec.nodeSelector.\"alpha.gpu.node.x.io/nvidia-gpu-name\" || '' }}"
operator: Equals
value: "t4"
mutate:
patchesJson6902: |-
- path: "/spec/tolerations/-"
op: add
value:
key: "alpha.gpu.node.schip.io/nvidia-gpu-name"
operator: "Equal"
value: "t4"
effect: "NoSchedule"
- name: enforce-gpu-a10g-taint
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.spec.nodeSelector.\"alpha.gpu.node.x.io/nvidia-gpu-name\" || '' }}"
operator: Equals
value: "a10g"
mutate:
patchesJson6902: |-
- path: "/spec/tolerations/-"
op: add
value:
key: "alpha.gpu.node.schip.io/nvidia-gpu-name"
operator: "Equal"
value: "a10g"
effect: "NoSchedule"
- name: enforce-gpu-taint
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.object.spec.nodeSelector.\"accelerator.node.x.io/gpu\" || '' }}"
operator: Equals
value: "true"
mutate:
patchesJson6902: |-
- path: "/spec/tolerations/-"
op: add
value:
key: "accelerator.node.schip.io/gpu"
operator: "Exists"
effect: "NoSchedule"
优势与挑战
Kyverno 为 Adevinta 带来了显著的优势:
-
简化用户体验: 通过 Kyverno 自动根据节点选择器分配容忍性,用户无需深入了解 Kubernetes 调度的复杂性即可部署工作负载。用户只需指定需求,策略会自动处理剩余部分。
-
一致且正确的调度: 自动化容忍性分配确保 Pod 被调度到正确的节点,并具备相应的污点。这保持了我们已建立的隔离和性能优化,减少了 Pod 被调度到不适当节点的风险。
-
降低配置错误风险: 通过自动处理容忍性,减少了人为错误的可能性,例如忘记包含必要的容忍性,这可能导致 Pod 无法调度或被错误调度。
通过将 Kyverno 策略与 Karpenter 的资源配置能力结合,Adevinta 使客户能够轻松利用 Kubernetes 的高级功能。这种协同作用提升了用户体验,优化了资源利用,同时保持了 Adevinta 的高性能和高可靠性标准。
自动化安全更新
安全至关重要,而保持我们的 Amazon Machine Images(AMIs)更新曾是一个繁琐且耗时的手动过程。借助 Karpenter,AMI 更新现在可以无缝处理。当新的 AMI 发布时,Karpenter 会识别出漂移的节点并逐步替换它们,同时遵守 PDB(Pod Disruption Budget)和中断策略。这一自动化过程确保了我们的集群始终保持最新的安全补丁,而无需人工干预。
过去,由于无法持续检查更新,Adevinta 并未积极更新 AMI。同时,之前的架构每次更新都需要进行全量集群重建,这会带来更多的中断。
通过实例优化实现显著的成本节省
今年,Adevinta 团队的一个关键目标是成本优化。他们发现,将实例从 Intel 迁移到 AMD 实例,可以在不影响性能的前提下,每月节省多达 30,000 欧元。
然而,手动在数千个节点之间执行此迁移是不可行的。
Karpenter 让这一迁移变得轻松无比。通过选择符合 Adevinta 需求的最具成本效益的实例,Karpenter 自然偏向于使用 AMD 实例,而不是 Intel 实例,从而立即带来了成本节省。
这一优势是在 Adevinta 迁移到 Karpenter 的过程中实现的,展示了它在无需额外干预的情况下,如何同时提供运维效率和财务收益。
Karpenter 迁移后,使用 AMD CPU 可节省的费用数据
增强的度量指标与洞察
Karpenter 提供了大量以前难以获取的度量指标。从漂移检测和节点中断次数,到合并事件和 Pod 调度时间,这些洞察信息非常宝贵。它们使 Adevinta 能够做出数据驱动的决策,并在问题升级之前主动解决潜在的问题。
Karpenter 公开了许多对决策和可观察性至关重要的指标
在使用托管节点组时,Adevinta 只能通过 AutoScalingGroups 获取关于节点的度量指标,而这些指标并非集群本身的原生指标。因此,这些数据并不足以帮助 Adevinta 理解在诸如升级或合并等事件期间,节点的行为和变化。
本文为 Adevinta 落地实践的上半部分,介绍了他们如何利用 Karpenter 为运维团队减负、增强应用稳定性以及实现成本优化。下周我们将发布下半部分,介绍 Adevinta 在迁移之路上踩过的坑以及他们是如何解决的。敬请期待!
关注**「CloudPilot AI」**,获取 Karpenter 落地实践不迷路。
推荐阅读
CloudPilot AI携手阿里云发布Karpenter阿里云 Provider,优化ACK集群自动扩展
1000+节点、200+集群,Slack如何利用Karpenter降本增效?
公司介绍
云妙算(CloudPilot.ai)是一家全球领先的 Karpenter 托管云服务提供商,致力于通过智能化、自动化的云资源调度和编排技术,帮助企业最大化云资源利用率。我们秉持**"让客户在云中花费的每一分钱都物超所值"**的使命,为客户提升10倍的资源效率,同时将云成本降低50%以上。
目前,开源K8s弹性伸缩器 Karpenter 已为全球超500家知名企业在生产环境中提供服务,包括阿迪达斯、Anthropic、Slack、Figma等。云妙算已为数十家全球顶尖科技公司提供服务,累计为客户节省超过30万美金,平均节省67%。 选择云妙算,让每一笔支出都更智慧。
免费试用,2步5分钟,降低50%云成本: