原文链接:
https://medium.com/adevinta-tech-blog/the-karpenter-effect-redefining-our-kubernetes-operations-80c7ba90a599
编译:CloudPilot AI
在上一篇文章中,我们介绍了 Adevinta 迁移至 Karpenter 后如何使用这一开源工具为运维团队减负、增强应用稳定性以及实现成本优化(月省21万)。点击下方链接查看文章详情:
环球最大分类广告商的Karpenter实践:减负运维、减少中断、每月省21万(上)
本文将介绍 Adevinta 在迁移之路上踩过的坑以及他们是如何解决的。
以防没看过前文的你不了解本文的“主角”,以下是关于Adevinta的介绍:
Adevinta 是天下最大的在线分类广告商之一,其业务遍布环球9个国家及地区,每个月吸引超过 1.2 亿用户和 100 万家企业,2023 财年总业务额达 18 亿欧元。
面对的挑战
没有一段旅程是没有挑战的,Adevinta 的迁移之路也不例外。
正确设置 Pod Disruption Budget (PDB) 的紧张性
尽管 Karpenter 通过遵循 Pod Disruption Budgets(PDBs)增强了可靠性,但这一优势的发挥依赖于客户正确设置 PDB。假如设置不当,可能会带来以下风险:
风险 1:未设置 PDB 时的中断增加
假如客户没有设置 PDB,Karpenter 在节点合并等操作期间可能会同时中断多个 Pod。由于 Karpenter 经常举行优化,这可能导致更多的服务中断。
风险 2:过于严格的 PDB 阻碍维护
相反,假如客户设置了过于严格的 PDB(比方,指定 maxUnavailable: 0),则会阻止任何自愿的 Pod 驱逐,包括必要的节点排空以举行维护。这会影响必要的操作,并低沉集群康健状态。
缓解策略
为了平衡可靠性与操作机动性,Adevinta 实施了以下策略:
设置合理的默认 PDB
Adevinta 为缺少 PDB 设置的应用提供默认 PDB 设置,确保在不限制过多的情况下,保护应用免受中断。这有助于在维护期间防止不测的服务影响,同时在必要时答应举行必要的节点中断。
使用 Kyverno 强制执行策略
通过 Kyverno,Adevinta 强制执行策略,避免设置那些可能妨碍集群维护的 PDB。
比方:
避免重复的 PDB Selector
禁止在定名空间中创建或更新与现有 PDB 选择器相同的 PDB。这样可以避免冲突,确保 PDB 的有效性。- apiVersion: kyverno.io/v1
- kind: ClusterPolicy
- metadata:
- name: forbid-duplicate-pdb-selectors
- spec:
- validationFailureAction: Enforce
- background: false
- rules:
- - name: check-duplicate-pdb
- match:
- any:
- - resources:
- kinds:
- - PodDisruptionBudget
- operations:
- - CREATE
- - UPDATE
- context:
- - name: existingPDBs
- apiCall:
- urlPath: /apis/policy/v1/namespaces/{{request.namespace}}/poddisruptionbudgets
- jmesPath: "items[?to_string(@.spec.selector.matchLabels) == to_string(`{{ request.object.spec.selector.matchLabels }}`) && @.metadata.name != '{{ request.object.metadata.name }}']"
- validate:
- message: "A PodDisruptionBudget with the same selector already exists."
- deny:
- conditions:
- any:
- - key: "{{ existingPDBs | length(@) }}"
- operator: GreaterThan
- value: 0
复制代码 确保合理的 maxUnavailable 值
Adevinta 强制要求,假如 PDB 指定了 maxUnavailable,则必须大于零。将其设置为零会阻止所有自愿的驱逐,从而妨碍维护使命的执行。- apiVersion: kyverno.io/v1
- kind: ClusterPolicy
- metadata:
- name: pdb-maxunavailable
- annotations:
- policies.kyverno.io/title: PodDisruptionBudget maxUnavailable Non-Zero
- policies.kyverno.io/subject: PodDisruptionBudget
- policies.kyverno.io/description: >-
- A PodDisruptionBudget which sets its maxUnavailable value to zero prevents
- all voluntary evictions including Node drains which may impact maintenance tasks.
- This policy enforces that if a PodDisruptionBudget specifies the maxUnavailable field
- it must be greater than zero.
- spec:
- validationFailureAction: Enforce
- background: false
- rules:
- - name: pdb-maxunavailable
- match:
- any:
- - resources:
- kinds:
- - PodDisruptionBudget
- preconditions:
- any:
- - key: '{{ regex_match(''^[0-9]+$'', ''{{ request.object.spec.maxUnavailable || ''''}}'') }}'
- operator: Equals
- value: true
- validate:
- message: "The maxUnavailable field, when specified as an integer, must be greater than zero."
- pattern:
- spec:
- =(maxUnavailable): ">0"
复制代码 限制 karpenter.sh/do-not-disrupt 注解的使用
Adevinta 阻止用户在没有充分理由的情况下,向工作负载添加 karpenter.sh/do-not-disrupt 注解,因为过度使用该注解可能会妨碍必要的维护工作。
通过实施这些措施,Adevinta 在确保应用可靠性的同时,保持了执行关键集群操作的能力。
过度设置和调度延迟
尽管 Karpenter 显著提高了资源使用率和成本效益,Adevinta 还是遇到了一些与调度延迟相关的挑战,特别是在快速扩展的阶段。Karpenter 对节点的优化和密集化(densification)意味着资源得到更高效的使用,但这也可能在某些情况下无意中导致扩展相应时间的延迟,影响客户体验。
调度延迟的挑战
在 Adevinta 高并发环境中,经常会遇到必要快速扩展 Pods 的突发情况。由于 Karpenter 对节点举行了密集优化,任何时候可用的空闲资源相对较少。当突发需求出现时,可能没有立刻足够的容量来调度新的 Pods,导致 Pods 进入待处置惩罚状态,直到 Karpenter 完成额外节点的设置。
此外,使用 startupTaints 进一步加剧了这一延迟。当 Karpenter 启动一个新节点时,会应用一个污点,防止 Pods 在该节点上调度,直到我们内部的 Kubernetes Operator 验证节点状态并移除污点。这个验证步骤对于确保节点康健和符合操作策略至关紧张,但也因此增加了额外的延迟。
终极,Adevinta 观察到 Pod 调度时间的服务级别指标(SLI)有所增加,且客户投诉了由于延迟扩展而带来的性能影响。
实施低优先级 Pod 的超额预留策略
为了缓解调度延迟,Adevinta 采用了一种超额预留策略,与 codecentric 提出的 Cluster Overprovisioner 和 Karpenter Overprovisioning Blueprint 中的方案类似。
以下是具体的实施方式:
1. 创建低优先级类
定义一个自定义的 Kubernetes PriorityClass,优先级低于所有标准工作负载。这一 PriorityClass 专用于调度占位 Pod,可以在必要时被更高优先级的 Pod 抢占。
2. 摆设占位 Pod
使用低优先级类摆设一组占位 Pod,这些 Pod 哀求的资源少少,但足以保证节点的供应和就绪。它们可以缓冲工作负载,占用容量但不会消耗大量资源。
3. 为高优先级 Pod 提供即时调度能力
当新的高优先级 Pod 必要调度时,它们会抢占这些低优先级的占位 Pod。调度器会驱逐占位 Pod,从现有节点上释放资源,而无需等待新的节点启动,从而显著减少调度延迟。
4. 动态节点预留
Karpenter 连续监控集群状态,当检测到占位 Pod 被驱逐导致缓冲容量减少时,会自动创建新的节点,以维持超额预留状态,并将新的占位 Pod 调度到新节点上。
通过这种策略,Adevinta 实现了在突发负载时的快速相应,显著缩短了调度时间,同时确保资源使用的高效性与系统的稳定性。- apiVersion: scheduling.k8s.io/v1
- kind: PriorityClass
- metadata:
- name: overprovisioning
- value: -1
- globalDefault: false
- description: "Priority class for overprovisioning pods."
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: overprovisioning-placeholder
- spec:
- replicas: 10
- selector:
- matchLabels:
- app: overprovisioning-placeholder
- template:
- metadata:
- labels:
- app: overprovisioning-placeholder
- spec:
- priorityClassName: overprovisioning
- containers:
- - name: pause
- image: k8s.gcr.io/pause:3.2
- resources:
- requests:
- cpu: X
- memory: X
复制代码 未来改进方向
Adevinta 正在积极探索进一步优化当前策略的方式,包括以下两方面:
1. 超额预留容量的动态调整
计划引入自动化机制,根据汗青使用模式和预测性扩容需求动态调整占位 Pod 的数量,从而进一步优化资源使用率。当前方案在集群规模较小时可能显得成本过高,因此动态调整将有助于低沉小规模集群中的开销。
2. 社区协作
Adevinta 正积极关注 Karpenter 社区中的相关议题,讨论如何通过 Karpenter 原生支持超额预留功能。通过参与社区互助,Adevinta 盼望推动这一功能的完善,为行业提供更高效的解决方案。
这些改进不仅有助于提拔资源使用效率,还能增强系统在不同场景下的弹性和稳定性。
“最低代价优先”陷阱
尽管 Karpenter 的成本优化功能通常能带来显著效益,但在某些情况下并不完全理想。比方,Adevinta 发现 m5a 实例与 m6a 实例在代价上略有差别,但性能差距显著:
- m5a.4xlarge(eu-west-1 区域):$0.7680/小时
- m6a.4xlarge(eu-west-1 区域):$0.7704/小时
尽管代价仅相差 $0.0024/小时,m6a 实例的性能却显着优于 m5a。由于 Karpenter 默认选择最自制的实例,这导致某些 CPU 密集型应用的性能未能到达最佳水平,反而因扩展更多副本而增加了整体成本。
某客户投诉称,自从迁移到 Karpenter 后,即使未举行任何代码更改,其 CPU 密集型应用的副本数量显著增加。这一现象表明,简朴的“最低代价优先”策略在某些场景下可能适得其反。
同一应用程序的副本数量因基于 CPU 性能的自动扩展而激增
不仅仅是副本数量的激增成为问题,服务的延迟也显著增加,尽管代码并未举行任何更改。这表明,选择性能较低的实例不仅会导致资源消耗上升,还会对服务质量造成严重影响,尤其是在对延迟敏感的应用中。
即使不修改代码,延迟也会大幅增加
经过调查发现,由于 m5a 实例代价更低,它渐渐成为 Karpenter 的首选,代替了此前使用的 m6a 实例。这一自动化选择虽然节省了成本,却导致了性能的降落,进而影响了整体服务质量。
迁移后,实例类型开始从 m6a 转移到 m5a
为了解决这一问题,团队通过定义新的节点池并赋予更高权重(优先级),优先选择第六代实例,同时将第五代实例降级为备用选项。这种策略确保了性能和成本之间的平衡,有效提拔了服务质量。- - key: karpenter.k8s.aws/instance-generation
- operator: Gt
- values:
- - "5"
- weight: 15
- - key: karpenter.k8s.aws/instance-generation
- operator: Gt
- values:
- - "4"
- weight: 10
复制代码 这一调整不仅确保了性能和成本的最佳平衡,还在第六代实例资源耗尽时,提供了备用节点池来承载工作负载。
此外,团队已与维护者睁开讨论,探讨在 Karpenter 中原生支持类似场景解决方案的可行性。
v1 升级中的兼容性处置惩罚
从 Karpenter v0.37.x 升级到 v1 时,团队遇到了一些兼容性问题。升级过程中的切换和向后兼容性并不如预期顺畅,这导致了一些困惑。尽管这些问题终极都得以解决,但它们也提醒团队在采用新版本时必须保持谨慎,尤其是在使用 ArgoCD 管理 Karpenter 安装时,必要举行充分的测试和验证。
Karpenter 升级是社区的常驻问题。基于这一现状,CloudPilot AI 推出 Karpenter 自动升级功能,将原本必要一周的升级时间缩短至3小时,并且为客户解决兼容性问题。
展望未来
Karpenter 的使用之旅远未结束,团队正在积极探索以下领域:
将 Karpenter 控制器迁移出托管节点组
目前,Karpenter 控制器运行在专用于控制平面组件的托管节点组上。然而,团队正在考虑将其迁移至 AWS Fargate,这一做法已被部分行业团队实践。通过这一迁移,团队可以摆脱对托管节点组的依赖,从而进一步简化基础设施管理。
增强超额预置能力
团队密切关注 Karpenter 社区关于引入容量预留和超额预置功能的讨论。同时,也在投入精神优化现有的超额预置工具,使其更加机动,并减少对硬编码设置的依赖,以适应多变的资源需求。
总 结
迁移到 Karpenter 对 Adevinta 来说是一次庞大的改变。通过使用 Karpenter,Adevinta 不仅实现了更顺畅的集群升级,还在实例选择上获得了更大的机动性,提拔了工作负载隔离能力,实现了自动化的安全更新,并节省了大量成本。尽管仍然面对一些挑战,但整体收益远超这些问题。
对于必要大规模管理 Kubernetes 的团队来说,Karpenter 提供了许多实着实在的好处,可以简化运维,减少管理负担。
推荐阅读
环球最大分类广告商的Karpenter实践:减负运维、减少中断、每月省21万(上)
Karpenter v1.0.0对K8s弹性伸缩意味着什么?
Grafana如何使用Karpenter消除50%的云资源浪费?|落地案例
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |