控制节点上的 CPU 管理策略
Kubernetes v1.26 [稳定]
Kubernetes 将 Pod 在节点上执行方式的许多方面对用户隐藏。这是有意为之的。但是,某些工作负载需要更强的延迟和/或性能保证才能正常运行。kubelet 提供方法来启用更复杂的工作负载放置策略,同时使抽象不受显式放置指令的影响。
有关资源管理的详细信息,请参阅Pod 和容器的资源管理 文档。
开始之前
您需要有一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与您的集群通信。建议在至少有两个节点的集群上运行本教程,这些节点不充当控制平面主机。如果您还没有集群,可以使用minikube 创建一个集群,或者可以使用以下 Kubernetes 游乐场之一
您的 Kubernetes 服务器必须至少为 v1.26 版本。要检查版本,请输入kubectl version
。如果您正在运行旧版本的 Kubernetes,请查看您实际运行的版本的文档。
CPU 管理策略
默认情况下,kubelet 使用CFS 配额 来执行 Pod CPU 限制。当节点运行许多 CPU 密集型 Pod 时,工作负载可能会迁移到不同的 CPU 内核,具体取决于 Pod 是否被节流以及调度时哪些 CPU 内核可用。许多工作负载对这种迁移不敏感,因此在没有任何干预的情况下也能正常运行。
但是,在 CPU 缓存亲和性和调度延迟会严重影响工作负载性能的工作负载中,kubelet 允许使用其他 CPU 管理策略来确定节点上的一些放置偏好。
配置
CPU 管理器策略使用 --cpu-manager-policy
kubelet 标志或 KubeletConfiguration 中的 cpuManagerPolicy
字段设置。支持两种策略
CPU 管理器定期通过 CRI 编写资源更新,以便将内存中的 CPU 分配与 cgroupfs 进行协调。协调频率通过新的 Kubelet 配置值 --cpu-manager-reconcile-period
设置。如果没有指定,则默认为与 --node-status-update-frequency
相同的持续时间。
静态策略的行为可以使用 --cpu-manager-policy-options
标志进行微调。该标志接受以逗号分隔的 key=value
策略选项列表。如果您禁用了 CPUManagerPolicyOptions
功能网关,则无法微调 CPU 管理器策略。在这种情况下,CPU 管理器仅使用其默认设置运行。
除了顶级 CPUManagerPolicyOptions
功能网关之外,策略选项还分为两组:alpha 质量(默认情况下隐藏)和 beta 质量(默认情况下可见)。这些组分别由 CPUManagerPolicyAlphaOptions
和 CPUManagerPolicyBetaOptions
功能网关保护。与 Kubernetes 标准不同,这些功能网关保护选项组,因为为每个单独选项添加功能网关会过于繁琐。
更改 CPU 管理器策略
由于 CPU 管理器策略只能在 kubelet 生成新 Pod 时应用,因此简单地从“none”更改为“static”不会应用于现有 Pod。因此,为了在节点上正确更改 CPU 管理器策略,请执行以下步骤
- 排空 节点。
- 停止 kubelet。
- 删除旧的 CPU 管理器状态文件。该文件的路径默认情况下为
/var/lib/kubelet/cpu_manager_state
。这将清除 CPUManager 维持的状态,以便新策略设置的 cpu-sets 不会与之冲突。 - 编辑 kubelet 配置以将 CPU 管理器策略更改为所需的值。
- 启动 kubelet。
对需要更改其 CPU 管理器策略的每个节点重复此过程。跳过此过程会导致 kubelet 陷入循环,并出现以下错误
could not restore state from checkpoint: configured policy "static" differs from state checkpoint policy "none", please drain this node and delete the CPU manager checkpoint file "/var/lib/kubelet/cpu_manager_state" before restarting Kubelet
None 策略
none
策略明确启用现有的默认 CPU 亲和性方案,除了操作系统调度程序自动执行的操作之外,不提供任何亲和性。使用 CFS 配额来强制执行保证型 Pod 和突发型 Pod 的 CPU 使用限制。
Static 策略
static
策略允许 Guaranteed
Pod 中的容器使用整数 CPU requests
在节点上访问独占 CPU。此独占性使用cpuset cgroup 控制器 强制执行。
注意
系统服务(如容器运行时和 kubelet 本身)可以继续在这些独占 CPU 上运行。独占性仅扩展到其他 Pod。注意
CPU 管理器不支持在运行时离线和联机 CPU。此外,如果节点上的在线 CPU 集发生变化,则必须排空节点并通过删除 kubelet 根目录中的状态文件cpu_manager_state
来手动重置 CPU 管理器。此策略管理一个共享 CPU 池,该池最初包含节点中的所有 CPU。可独占分配的 CPU 数量等于节点中的 CPU 总数减去 kubelet --kube-reserved
或 --system-reserved
选项对 CPU 的任何保留。从 1.17 开始,可以使用 kubelet --reserved-cpus
选项显式指定 CPU 保留列表。--reserved-cpus
显式指定的 CPU 列表优先于 --kube-reserved
和 --system-reserved
指定的 CPU 保留。通过这些选项保留的 CPU 以整数数量从初始共享池中按物理核心 ID 升序获取。该共享池是 BestEffort
和 Burstable
Pod 中任何容器运行的 CPU 集。Guaranteed
Pod 中的容器(其 CPU requests
为小数)也运行在共享池中的 CPU 上。只有既是 Guaranteed
Pod 的一部分又具有整数 CPU requests
的容器才会被分配独占 CPU。
注意
当启用静态策略时,kubelet 需要使用--kube-reserved
和/或 --system-reserved
或 --reserved-cpus
进行大于零的 CPU 保留。这是因为零 CPU 保留将允许共享池变空。当满足静态分配要求的容器所在的 Guaranteed
Pod 被调度到节点时,CPU 将从共享池中移除并放置到容器的 cpuset 中。CFS 配额不用于限制这些容器的 CPU 使用,因为它们的用法受调度域本身的限制。换句话说,容器 cpuset 中的 CPU 数量等于 Pod 规范中指定的整数 CPU limit
。此静态分配提高了 CPU 亲和性并减少了 CPU 密集型工作负载由于节流而导致的上下文切换。
考虑以下 Pod 规范中的容器
spec:
containers:
- name: nginx
image: nginx
上面的 Pod 在 BestEffort
QoS 类中运行,因为没有指定资源 requests
或 limits
。它运行在共享池中。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
上面的 Pod 在 Burstable
QoS 类中运行,因为资源 requests
不等于 limits
,并且未指定 cpu
数量。它运行在共享池中。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "100Mi"
cpu: "1"
上面的 Pod 在 Burstable
QoS 类中运行,因为资源 requests
不等于 limits
。它运行在共享池中。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"
上面的 Pod 在 Guaranteed
QoS 类中运行,因为 requests
等于 limits
。并且容器的 CPU 资源限制是大于或等于 1 的整数。nginx
容器被授予 2 个独占 CPU。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "1.5"
requests:
memory: "200Mi"
cpu: "1.5"
上面的 Pod 在 Guaranteed
QoS 类中运行,因为 requests
等于 limits
。但容器的 CPU 资源限制为小数。它运行在共享池中。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
上面的 Pod 在 Guaranteed
QoS 类中运行,因为只指定了 limits
,并且在未明确指定时 requests
设置为等于 limits
。并且容器的 CPU 资源限制是大于或等于 1 的整数。nginx
容器被授予 2 个独占 CPU。
Static 策略选项
您可以使用以下功能网关根据选项的成熟度级别来打开和关闭选项组
CPUManagerPolicyBetaOptions
默认启用。禁用以隐藏 beta 级别的选项。CPUManagerPolicyAlphaOptions
默认禁用。启用以显示 alpha 级别的选项。您仍然需要使用CPUManagerPolicyOptions
kubelet 选项来启用每个选项。
以下列出了静态 CPUManager
策略的可用选项。
full-pcpus-only
(测试版,默认可见)(1.22 或更高版本)distribute-cpus-across-numa
(Alpha 版,默认隐藏)(1.23 或更高版本)align-by-socket
(Alpha 版,默认隐藏)(1.25 或更高版本)distribute-cpus-across-cores
(Alpha 版,默认隐藏)(1.31 或更高版本)
如果指定了 full-pcpus-only
策略选项,静态策略将始终分配完整的物理核心。默认情况下,在没有此选项的情况下,静态策略使用拓扑感知的最佳拟合分配来分配 CPU。在启用 SMT 的系统上,该策略可以分配单个虚拟核心,这些核心对应于硬件线程。这会导致不同的容器共享相同的物理核心;这种行为反过来会加剧 噪声邻居问题。启用该选项后,只有在所有容器的 CPU 请求都能够通过分配完整的物理核心来满足的情况下,pod 才能被 kubelet 接受。如果 pod 未通过接受,它将进入失败状态,并显示消息 SMTAlignmentError
。
如果指定了 distribute-cpus-across-numa
策略选项,则静态策略将在需要多个 NUMA 节点才能满足分配的情况下,将 CPU 均匀分布到 NUMA 节点。默认情况下,CPUManager
会将 CPU 打包到一个 NUMA 节点,直到该节点被填满,任何剩余的 CPU 只会溢出到下一个 NUMA 节点。这会导致依赖于屏障(以及类似同步原语)的并行代码中出现不必要的瓶颈,因为这种类型的代码往往只运行得与最慢的 worker 一样快(最慢的 worker 会因为至少在一个 NUMA 节点上可用的 CPU 更少而变慢)。通过将 CPU 均匀分布到 NUMA 节点,应用程序开发人员可以更轻松地确保没有单个 worker 受 NUMA 影响大于其他 worker,从而提高此类应用程序的整体性能。
如果指定了 align-by-socket
策略选项,则在决定如何将 CPU 分配给容器时,CPU 将被视为在套接字边界对齐。默认情况下,CPUManager
在 NUMA 边界处对齐 CPU 分配,这可能会导致性能下降,因为如果需要从多个 NUMA 节点中提取 CPU 来满足分配,则 CPU 会发生性能下降。虽然它试图确保所有 CPU 都从最少数量的 NUMA 节点分配,但不能保证这些 NUMA 节点在同一个套接字上。通过指示 CPUManager
在套接字边界而不是 NUMA 边界处显式对齐 CPU,我们能够避免此类问题。请注意,此策略选项与 TopologyManager
single-numa-node
策略不兼容,并且不适用于套接字数量大于 NUMA 节点数量的硬件。
如果指定了 distribute-cpus-across-cores
策略选项,则静态策略将尝试跨不同的物理核心分配虚拟核心(硬件线程)。默认情况下,CPUManager
倾向于将 CPU 打包到尽可能少的物理核心上,这会导致同一个物理核心上的 CPU 之间发生争用,并导致性能瓶颈。通过启用 distribute-cpus-across-cores
策略,静态策略确保 CPU 分布到尽可能多的物理核心上,从而减少同一个物理核心上的争用,从而提高整体性能。但是,重要的是要注意,当系统负载很重时,这种策略可能不太有效。在这种情况下,减少争用的好处会减少。相反,默认行为可以帮助减少核心间通信开销,在高负载条件下可能提供更好的性能。
可以通过在 CPUManager 策略选项中添加 full-pcpus-only=true
来启用 full-pcpus-only
选项。类似地,可以通过在 CPUManager 策略选项中添加 distribute-cpus-across-numa=true
来启用 distribute-cpus-across-numa
选项。当两者都被设置时,它们是“累加的”,这意味着 CPU 将以完整的 pcpus 为单位在 NUMA 节点之间分布,而不是单个核心。可以通过在 CPUManager
策略选项中添加 align-by-socket=true
来启用 align-by-socket
策略选项。它也是 full-pcpus-only
和 distribute-cpus-across-numa
策略选项的累加项。
可以通过在 CPUManager
策略选项中添加 distribute-cpus-across-cores=true
来启用 distribute-cpus-across-cores
选项。目前不能将其与 full-pcpus-only
或 distribute-cpus-across-numa
策略选项一起使用。