如何合理使用 CPU 管理策略,提升容器性能?
作者:张佐玮(佑祎)
前言
在云原生时代下,应用工作负载都是以容器的形式部署在宿主机,共享各类物理资源。随着宿主机硬件性能的增强,单节点的容器部署密度进一步提升,由此带来的进程间 CPU 争用,跨 NUMA 访存等问题也更加严重,影响了应用性能表现。如何分配和管理宿主机的 CPU 资源,保障应用可以获得最优的服务质量,是衡量容器服务技术能力的关键因素。
节点侧容器 CPU 资源管理
Kubelet 的 CPU 分配策略
Kubernetes 为容器资源管理提供了 request(请求)和 limit(约束)的语义描述,当容器指定了 request 时,调度器会利用该信息决定 Pod 应该被分配到哪个节点上;当容器指定了 limit 时,Kubelet 会确保容器在运行时不会超用。
CPU 是一种典型的分时复用型资源,内核调度器会将 CPU 分为多个时间片,轮流为各进程分配一定的运行时间。Kubelet 默认的 CPU 管理策略会通过 Linux 内核的 CFS 带宽控制器(CFS Bandwidth Controller)来控制容器 CPU 资源的使用上限。在多核节点下,进程在运行过程中经常会被迁移到其不同的核心,考虑到有些应用的性能对 CPU 上下文切换比较敏感,Kubelet 还提供了 static 策略,允许 Guaranteed 类型 Pod 独占 CPU 核心。
内核 CPU 资源调度
内核 CFS 调度是通过 cfs_period 和 cfs_quota 两个参数来管理容器 CPU 时间片消耗的,cfs_period 一般为固定值 100 ms,cfs_quota 对应容器的 CPU Limit。例如对于一个 CPU Limit = 2 的容器,其 cfs_quota 会被设置为 200ms,表示该容器在每 100ms 的时间周期内最多使用 200ms 的 CPU 时间片,即 2 个 CPU 核心。当其 CPU 使用量超出预设的 limit 值时,容器中的进程会受内核调度约束而被限流。细心的应用管理员往往会在集群 Pod 监控中的 CPU Throttle Rate 指标观察到这一特征。
容器 CPU 性能问题现状
让应用管理员常常感到疑惑的是,为什么容器的资源利用率并不高,但却频繁出现应用性能下降的问题?从 CPU 资源的角度来分析,问题通常来自于以下两方面:一是内核在根据 CPU Limit 限制容器资源消耗时产生的 CPU Throttle 问题;二是受 CPU 拓扑结构的影响,部分应用对进程在 CPU 间的上下文切换比较敏感,尤其是在发生跨 NUMA 访问时的情况。
CPU Throttle 问题详解
受内核调度控制周期(cfs_period)影响,容器的 CPU 利用率往往具有一定的欺骗性,下图展示了某容器一段时间的 CPU 使用情况(单位为0.01核),可以看到在 1s 级别的粒度下(图中紫色折线),容器的 CPU 用量较为稳定,平均在 2.5 核左右。根据经验,管理员会将 CPU Limit设置为 4 核。本以为这已经保留了充足的弹性空间,然而若我们将观察粒度放大到 100ms 级别(图中绿色折线),容器的 CPU 用量呈现出了严重的毛刺现象,峰值达到 4 核以上。此时容器会产生频繁的 CPU Throttle,进而导致应用性能下降、RT 抖动,但我们从常用的 CPU 利用率指标中竟然完全无法发现!
为了避免 CPU Throttle 的问题,我们只能将容器的 CPU Limit 值调大。然而,若想彻底解决 CPU Throttle,通常需要将 CPU Limit 调大两三倍,有时甚至五到十倍,问题才会得到明显缓解。而为了降低 CPU Limit 超卖过多的风险,还需降低容器的部署密度,进而导致整体资源成本上升。
CPU 拓扑结构的影响
在 NUMA 架构下,节点中的 CPU 和内存会被切分成了两部分甚至更多(例如图中 Socket0,Socket1),CPU 被允许以不同的速度访问内存的不同部分,当 CPU 跨 Socket 访问另一端内存时,其访存时延相对更高。盲目地在节点为容器分配物理资源可能会降低延迟敏感应用的性能,因此我们需要避免将 CPU 分散绑定到多个 Socket 上,提升内存访问时的本地性。如下图所示,同样是为两个容器分配 CPU、内存资源,显然场景B中的分配策略更为合理。
某 CPU Limit = 2 的容器,其应用在 100ms 时间点收到了 4 个请求需要处理,在 Kubelet 提供的 static 模式下,容器会被固定在 CPU0 和 CPU1 两个核心,各线程只能排队运行,而在 Default 模式下,容器获得了更多的 CPU 弹性,收到请求后各线程可以立即处理。可以看出,绑核策略并不是“银弹”,Default 模式也有适合自己的应用场景。
使用 CPU Burst 机制提升容器性能
我们使用 Apache HTTP Server 作为延迟敏感型在线应用,通过模拟请求流量,评估 CPU Burst 能力对响应时间(RT)的提升效果。以下数据分别展示了 CPU Burst 策略开启前后的表现情况:
- 在开启 CPU Burst 能力后,应用的 RT 指标的 p99 分位值得到了明显的优化。
- 对比 CPU Throttled 及利用率指标,可以看到开启 CPU Burst 能力后,CPU Throttled 情况得到了消除,同时 Pod 整体利用率基本保持不变。
使用拓扑感知调度提升容器性能
虽然 Kubelet 提供了单机的资源管理策略(static policy,single-numa-node),可以部分解决应用性能表现受 CPU 缓存、NUMA 亲和性影响的问题,但该策略尚有以下不足之处:
- static policy 只支持 QoS 为 Guaranteed 的 Pod,其他 QoS 类型的 Pod 无法使用
- 策略对节点内所有 Pod 全部生效,而我们通过前面的分析知道,CPU 绑核并不是”银弹“
- 中心调度并不感知节点实际的 CPU 分配情况,无法在集群范围内选择到最优组合
阿里云容器服务 ACK 基于 Scheduling framework 实现了拓扑感知调度以及灵活的绑核策略,针对 CPU 敏感型的工作负载可以提供更好的性能。ACK 拓扑感知调度可以适配所有 QoS 类型,并支持在 Pod 维度按需开启,同时可以在全集群范围内选择节点和 CPU 拓扑的最优组合。
通过对 Nginx 服务进行的评测,我们发现在 Intel(104核)、AMD(256核)的物理机上,使用 CPU 拓扑感知调度能够将应用性能提升 22%~43%。
总结
CPU Burst、拓扑感知调度是阿里云容器服务 ACK 提升应用性能的两大利器,它们解决了不同场景下的 CPU 资源管理,可以共同使用。
CPU Burst 解决了内核 BWC 调度时针对 CPU Limit 的限流问题,可以有效提升延时敏感型任务的性能表现。但 CPU Burst 本质并不是将资源无中生有地变出来,若容器 CPU 利用率已经很高(例如大于50%),CPU Burst 能起到的优化效果将会受限,此时应该通过 HPA 或 VPA 等手段对应用进行扩容。
拓扑感知调度降低了工作负载 CPU 上下文切换的开销,特别是在 NUMA 架构下,可以提升 CPU 密集型,访存密集型应用的服务质量。不过正如前文中提到的,CPU 绑核并不是“银弹”,实际效果取决于应用类型。此外,若同一节点内大量 Burstable 类型 Pod 同时开启了拓扑感知调度,CPU 绑核可能会产生重叠,在个别场景下反而会加剧应用间的干扰。因此,拓扑感知调度更适合针对性的开启。
本文为阿里云原创内容,未经允许不得转载。