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