4. Static 策略深入:CPU 亲和性与独占核心的实现机制

Static CPU 管理策略的核心价值在于为 Guaranteed QoS 的 Pod 提供独占物理核心,消除同核争抢与上下文切换抖动。本节深入剖析 Static 策略从 Pod 调度到 CPU 绑核的完整链路,重点拆解 CPU 亲和性(Affinity)与独占核心(Exclusive Core)的实现细节。

4.1 从 kubelet 到 cpuset:Static 策略的决策流程

当 kubelet 启用 --cpu-manager-policy=static 且 Pod 满足 Guaranteed QoS(所有容器 request == limit)且整数核申请时,触发以下决策流程:

步骤 组件 动作 关键判断
1 kubelet 准入 校验 Pod 是否满足 Guaranteed + 整数核 非整数核或 Burstable/BestEffort 直接走 Default 策略
2 CPU Manager State 读取当前 cpuset 分配拓扑 检查 NUMA 节点剩余核心数是否满足请求
3 拓扑管理器(可选) 协调 CPU、设备、内存的 NUMA 对齐 若启用 TopologyManager,需通过最佳匹配
4 cpuset 分配器 从可用核心池中分配独占核心 优先分配物理核,避免超线程兄弟核
5 cgroup 写入 将分配的核心列表写入 cpuset.cpus 同时设置 cpuset.mems 绑定 NUMA 节点
关键约束:Static 策略只对 整数核 的 Guaranteed Pod 生效。若容器请求 1.5 核,即使 QoS 为 Guaranteed,也会回退到 Default 策略,使用共享 CPU 池。

4.2 CPU 亲和性:物理核与逻辑核的绑定规则

CPU 亲和性在 Linux 内核层面通过 sched_setaffinity 系统调用实现。Kubernetes 的 Static 策略在此基础上增加了两层抽象:

  • 共享池(Shared Pool):所有未分配的 CPU 核心组成共享池,Default 策略 Pod 和 Burstable/BestEffort Pod 从中获取时间片。
  • 独占池(Exclusive Pool):已分配给 Static Pod 的核心从共享池中移除,仅该 Pod 的容器线程可运行。

亲和性绑定的核心规则:

  1. 物理核优先:分配时优先选择完整的物理核,避免将同一个物理核的两个超线程分给不同 Pod。
  2. NUMA 节点内分配:若 Pod 请求核数不超过单个 NUMA 节点容量,则全部核心分配在同一 NUMA 节点内。
  3. 跨 NUMA 兜底:当请求核数超过单 NUMA 节点容量时,允许跨节点分配,但会触发拓扑管理器告警。
性能提示:在 Intel/AMD 服务器上,建议通过 lscpu -e 查看 CPU 拓扑,确保 Static Pod 的核数分配不会导致跨 NUMA 访问。跨 NUMA 的内存延迟通常比本地 NUMA 高 20%-40%。

4.3 独占核心的实现:cpuset 与 cgroup 层级

独占核心的最终落地依赖 Linux cgroup v1 的 cpuset 子系统。Kubernetes 在容器 cgroup 目录下写入以下关键文件:

# 容器 cgroup 路径示例
/sys/fs/cgroup/cpuset/kubepods/besteffort/pod<uid>/<container-id>/

# 关键文件内容
cpuset.cpus        = "2,4,6"        # 独占核心列表
cpuset.mems        = "0"            # 绑定的 NUMA 节点
cpuset.cpu_exclusive = 1            # 标记为独占(Static 策略特有)

cpuset.cpu_exclusive=1 是 Static 策略与 Default 策略的核心区别。该标志告诉内核:这些核心仅允许当前 cgroup 内的任务使用,其他 cgroup 的任务即使通过 sched_setaffinity 也无法抢占。

4.4 核心分配与回收的生命周期

Static 策略的 CPU 核心分配不是静态不变的,而是随 Pod 生命周期动态管理:

阶段 动作 cpuset 状态变化
Pod 启动 从共享池移除核心,写入容器 cpuset 共享池核心数减少,独占池增加
Pod 运行 内核强制亲和性,禁止其他 Pod 使用 独占核心保持锁定
Pod 终止 清理容器 cgroup,核心归还共享池 共享池核心数恢复,独占池减少
kubelet 重启 从 checkpoint 文件恢复分配状态 重新构建共享池与独占池映射
注意:kubelet 重启时,如果 checkpoint 文件(默认 /var/lib/kubelet/cpu_manager_state)损坏或丢失,所有 Static Pod 的 cpuset 会被重置,导致已运行的 Pod 失去独占核心。生产环境应定期备份该文件。

4.5 Static 策略下的 CPU 亲和性拓扑图

下图展示了一个 2 NUMA 节点、每节点 4 物理核(共 16 逻辑核)的服务器上,Static 策略分配后的 CPU 拓扑:

Static 策略 CPU 亲和性拓扑 Static 策略 CPU 亲和性拓扑示例 NUMA 0 NUMA 1 物理核 0 HT0: CPU0 | HT1: CPU1 物理核 1 HT0: CPU2 | HT1: CPU3 物理核 2 HT0: CPU4 | HT1: CPU5 物理核 3 HT0: CPU6 | HT1: CPU7 独占池:物理核 2,3 → Pod-A (4 核) 共享池:物理核 0,1 → Default Pod 物理核 4 HT0: CPU8 | HT1: CPU9 物理核 5 HT0: CPU10 | HT1: CPU11 物理核 6 HT0: CPU12 | HT1: CPU13 物理核 7 HT0: CPU14 | HT1: CPU15 独占池:物理核 6,7 → Pod-B (4 核) 共享池:物理核 4,5 → Default Pod Pod-A 独占 NUMA 0 的物理核 2,3(4 个逻辑核),Pod-B 独占 NUMA 1 的物理核 6,7(4 个逻辑核) 共享池中的 Default Pod 只能使用剩余核心的时间片,无法抢占独占核心

4.6 超线程场景下的亲和性陷阱

当 CPU 启用超线程(Hyper-Threading)时,Static 策略的亲和性分配需要额外注意:

  • 兄弟核干扰:若 Pod-A 独占物理核 0 的 HT0(CPU0),而 Pod-B 独占同一物理核的 HT1(CPU1),两个 Pod 仍会共享该物理核的执行单元,导致性能隔离失效。
  • 分配策略:Kubernetes 默认不会自动感知超线程拓扑。管理员需通过 cpuManagerPolicyOptions 配置 full-pcpus-only=true,强制分配完整物理核。
最佳实践:在延迟敏感型工作负载(如 Redis、DPDK)场景下,建议在 kubelet 配置中设置:
--cpu-manager-policy-options=full-pcpus-only=true
同时配合 --topology-manager-policy=single-numa-node,确保 Pod 独占完整物理核且不跨 NUMA 节点。

4.7 性能验证:Static vs Default 的上下文切换对比

通过 perf stat 工具可以直观对比两种策略下的上下文切换开销:

# 在 Static 策略 Pod 的容器内执行
perf stat -e context-switches,cpu-migrations,cycles -a -- sleep 10

# 在 Default 策略 Pod 的容器内执行(相同负载)
perf stat -e context-switches,cpu-migrations,cycles -a -- sleep 10

# 典型输出对比
# Static:   context-switches: 12    cpu-migrations: 0     cycles: 2.1G
# Default:  context-switches: 847   cpu-migrations: 23    cycles: 3.8G

Static 策略的上下文切换次数通常比 Default 策略低 1-2 个数量级,CPU 迁移次数接近零,这意味着 L1/L2 缓存命中率显著提升。

4.8 本节小结

核心概念 实现机制 性能影响
CPU 亲和性 cpuset.cpus + sched_setaffinity 消除线程迁移,提升缓存局部性
独占核心 cpuset.cpu_exclusive=1 + 共享池隔离 消除同核争抢,降低调度延迟
超线程隔离 full-pcpus-only 策略选项 避免兄弟核干扰,保障确定性
NUMA 对齐 cpuset.mems + TopologyManager 减少跨 NUMA 内存访问延迟