本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 资源限制的案例:可预测性与效率
已经有很多文章建议不使用 Kubernetes 资源限制可能相当有用(例如,《看在上帝的份上,别再在 Kubernetes 上使用 CPU 限制了》或 《Kubernetes:通过移除 CPU 限制让你的服务更快》)。这些文章中的观点完全有效——为那些因限制而无法使用的计算能力付费,或者人为增加延迟,确实没有多大意义。本文旨在论证限制也有其合法用途。
作为 Grafana Labs 平台团队的站点可靠性工程师(SRE),我所在的团队负责维护和改进产品团队使用的内部基础设施和工具,我的主要工作是尽力让 Kubernetes 升级尽可能平滑。但我也花了很多时间深入研究各种有趣的 Kubernetes 问题。本文反映了我的个人观点,社区中的其他人可能持有不同意见。
让我们把问题反过来看。Kubernetes 集群中的每个 Pod 都有其固有的资源限制——即它所运行的机器的实际 CPU、内存和其他资源。如果 Pod 达到了这些物理限制,它将经历类似于达到 Kubernetes 限制所引起的节流。
问题所在
没有限制(或限制宽松)的 Pod 可以轻易消耗节点上的额外资源。然而,这有一个隐藏的成本——可用额外资源的数量通常严重依赖于调度到特定节点上的 Pod 及其当时的实际负载。这些额外资源使得每个 Pod 在实际资源分配方面都成了一个特殊的“雪花”。更糟糕的是,要弄清楚 Pod 在任何给定时刻可用的资源相当困难——如果不通过对特定节点上运行的 Pod、它们的资源消耗等进行繁琐的数据挖掘,几乎不可能做到。最后,即使我们克服了这个障碍,我们也只能以一定的频率采样数据,并且只能为我们的一部分调用获取性能剖析。虽然这可以扩展,但生成的可观测性数据量很容易达到收益递减的程度。因此,没有简单的方法可以判断一个 Pod 是否经历了快速的峰值,并在短时间内使用了两倍于平时的内存来处理请求突发。
现在,随着“黑色星期五”和“网络星期一”的临近,企业预计流量会激增。良好的历史性能数据/基准测试使企业能够为一些额外的容量进行规划。但是,关于没有限制的 Pod 的数据可靠吗?由于内存或 CPU 的瞬时峰值由额外资源处理,根据过去的数据,一切可能看起来都很好。但一旦 Pod 的装箱(bin-packing)方式改变,额外资源变得更加稀缺,一切可能就会变得不同——从请求延迟的微不足道的上升,到请求缓慢地滚雪球并导致 Pod OOM(内存不足)被杀死。虽然几乎没人关心前者,但后者是一个需要立即增加容量的严重问题。
配置限制
不使用限制是一种权衡——它在有额外可用资源时机会性地提高了性能,但降低了性能的可预测性,这可能会在未来带来反噬。有几种方法可以用来再次提高可预测性。让我们选择其中两种进行分析:
- 将工作负载的 limits 配置为比 requests 高一个固定的(且较小的)百分比——我称之为固定比例的余量。这允许使用一些额外的共享资源,但保持了每个节点的超售(overcommit)是有界的,并且可以用来指导工作负载的最坏情况估算。请注意,limits 的百分比越大,工作负载之间可能出现的性能差异就越大。
- 将工作负载配置为
requests
=limits
。从某种角度来看,这相当于为每个 Pod 提供一个资源受限的微型虚拟机;性能相当可预测。这也将 Pod 置于 Guaranteed QoS 类别中,这使得它只有在 BestEffort 和 Burstable 类的 Pod 被资源压力下的节点驱逐后才会被驱逐(参见 Pod 的服务质量)。
可能还会考虑其他一些情况,但这两种可能是最简单的讨论案例。
集群资源经济学
请注意,在上述两种情况中,我们实际上是阻止工作负载使用其拥有的一些集群资源,以换取更高的可预测性——这听起来为了获得更稳定的性能而付出的代价可能很高。让我们试着量化其影响。
装箱与集群资源分配
首先,让我们讨论一下装箱(bin-packing)和集群资源分配。这里存在一些固有的集群低效率——在 Kubernetes 集群中很难实现 100% 的资源分配。因此,总会有一部分资源未被分配。
当配置固定比例余量的 limits 时,相应比例的未分配资源将可供 Pod 使用。如果集群中未分配资源的百分比低于我们用于设置固定比例余量限制的常数(见图,第 2 行),所有 Pod 理论上能够用完节点的所有资源;否则,就会有一些资源不可避免地被浪费(见图,第 1 行)。为了消除不可避免的资源浪费,固定比例余量限制的百分比应该配置为至少等于预期的未分配资源百分比。
对于 requests = limits(见图,第 3 行),情况并非如此:除非我们能够分配节点的所有资源,否则总会有一些不可避免的资源被浪费。在 requests/limits 方面没有任何可调参数的情况下,唯一合适的方法是通过配置正确的机器规格来确保节点上的高效装箱。这可以手动完成,也可以使用各种云服务提供商的工具——例如 EKS 的 Karpenter 或 GKE 节点自动扩缩。
优化实际资源利用率
空闲资源也以其他 Pod 未使用的资源(预留的 CPU 利用率与实际的 CPU 利用率等)的形式存在,并且它们的可用性无法以任何合理的方式预测。配置 limits 使得利用这些资源几乎不可能。从另一个角度看,如果一个工作负载浪费了它所请求的大量资源,那么重新审视其自身的资源请求可能是一件合理的事情。查看历史数据并选择更合适的资源请求可能有助于使装箱更紧凑(尽管代价是性能变差——例如增加长尾延迟)。
结论
优化资源请求和限制是很困难的。虽然在设置 limits 时更容易出问题,但这些问题可能通过提供更多关于工作负载在边界条件下如何行为的洞察,帮助防止以后发生灾难。在某些情况下,设置 limits 的意义不大:批处理工作负载(对延迟不敏感——例如非实时视频编码)、尽力而为(best-effort)的服务(不需要那么高的可用性,可以被抢占)、设计上就有大量备用资源的集群(各种特殊工作负载的情况——例如设计用于处理峰值的服务)。
另一方面,不应不惜一切代价避免设置 limits——尽管找出 limits 的“正确”值更难,并且配置错误的值会导致更不宽容的情况。配置 limits 有助于你了解工作负载在极端情况下的行为,并且在推断正确值时有一些简单的策略可以帮助。这是在高效资源使用和性能可预测性之间的一种权衡,应该这样来看待。
具有资源使用峰值的工作负载还有一个经济方面的问题。手头总是有“免费”资源并不能激励产品团队去提高性能。足够大的峰值很容易引发效率问题,甚至在试图捍卫产品的服务等级协议(SLA)时出现问题——因此,在评估任何风险时,这可能是一个值得提及的候选因素。