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 节点 |
4.2 CPU 亲和性:物理核与逻辑核的绑定规则
CPU 亲和性在 Linux 内核层面通过 sched_setaffinity 系统调用实现。Kubernetes 的 Static 策略在此基础上增加了两层抽象:
- 共享池(Shared Pool):所有未分配的 CPU 核心组成共享池,Default 策略 Pod 和 Burstable/BestEffort Pod 从中获取时间片。
- 独占池(Exclusive Pool):已分配给 Static Pod 的核心从共享池中移除,仅该 Pod 的容器线程可运行。
亲和性绑定的核心规则:
- 物理核优先:分配时优先选择完整的物理核,避免将同一个物理核的两个超线程分给不同 Pod。
- NUMA 节点内分配:若 Pod 请求核数不超过单个 NUMA 节点容量,则全部核心分配在同一 NUMA 节点内。
- 跨 NUMA 兜底:当请求核数超过单 NUMA 节点容量时,允许跨节点分配,但会触发拓扑管理器告警。
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 文件恢复分配状态 | 重新构建共享池与独占池映射 |
/var/lib/kubelet/cpu_manager_state)损坏或丢失,所有 Static Pod 的 cpuset 会被重置,导致已运行的 Pod 失去独占核心。生产环境应定期备份该文件。
4.5 Static 策略下的 CPU 亲和性拓扑图
下图展示了一个 2 NUMA 节点、每节点 4 物理核(共 16 逻辑核)的服务器上,Static 策略分配后的 CPU 拓扑:
4.6 超线程场景下的亲和性陷阱
当 CPU 启用超线程(Hyper-Threading)时,Static 策略的亲和性分配需要额外注意:
- 兄弟核干扰:若 Pod-A 独占物理核 0 的 HT0(CPU0),而 Pod-B 独占同一物理核的 HT1(CPU1),两个 Pod 仍会共享该物理核的执行单元,导致性能隔离失效。
- 分配策略:Kubernetes 默认不会自动感知超线程拓扑。管理员需通过
cpuManagerPolicyOptions配置full-pcpus-only=true,强制分配完整物理核。
--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 内存访问延迟 |