4. cgroups v2 CPU 子系统:架构、权重、上限与绑核

cgroups v2 是 Linux 内核统一控制组接口,在容器运行时中逐步取代 v1。本章聚焦其 CPU 子系统的核心设计:扁平化架构、cpu.weight 与 cpu.max 的调度语义,以及 cpuset 控制器的绑核实现。理解这些机制是后续配置 containerd CPU 管理策略的基础。

4.1 cgroups v2 统一架构

cgroups v2 最显著的变化是采用单一层级树(Unified Hierarchy),所有控制器挂载在同一个虚拟文件系统下,不再允许 v1 中每个控制器独立挂载不同层级。这一设计消除了 v1 中因多层级导致的资源竞争与状态不一致问题。

关键变化: v2 中所有进程属于同一个层级树,控制器通过 cgroup.subtree_control 文件按需启用,子 cgroup 继承父级控制器状态。
cgroups v2 统一层级架构 cgroups v2 统一层级架构 /sys/fs/cgroup (根) cpu 控制器 memory 控制器 io 控制器 cpuset 控制器 容器 A (cgroup.slice) 容器 B (cgroup.slice) 进程 P1 进程 P2 进程 P3 说明: • 所有控制器挂载在同一个根目录下,通过 subtree_control 按需启用。 • 子 cgroup 继承父级控制器状态,但可独立配置权重与上限。

4.2 cpu.weight:权重分配机制

cpu.weight 是 cgroups v2 中控制 CPU 时间分配的核心参数,取代 v1 的 cpu.shares。它定义 cgroup 在 CPU 竞争时的相对权重,仅在系统 CPU 资源紧张时生效。

参数 cgroups v1 (cpu.shares) cgroups v2 (cpu.weight)
取值范围 2 – 262144 1 – 10000
默认值 1024 100
语义 相对份额,值越大获得 CPU 时间越多 相对权重,值越大获得 CPU 时间越多
空闲 CPU 处理 可超用(burst) 可超用,配合 cpu.max 限制上限
实践建议: 在 containerd 中,cpu.weight 对应 Kubernetes 的 requests.cpu 语义。例如两个容器权重分别为 100 和 200,在 CPU 满载时前者获得 1/3 时间,后者获得 2/3 时间。

4.3 cpu.max:硬上限与带宽控制

cpu.max 是 cgroups v2 中设置 CPU 使用硬上限的文件,格式为 $MAX $PERIOD。它限制 cgroup 在每个周期内最多使用的 CPU 时间,防止容器过度占用宿主机资源。

# 格式:cpu.max = <quota> <period>
# 示例:限制容器在 100ms 周期内最多使用 50ms CPU 时间(即 0.5 核)
echo "50000 100000" > /sys/fs/cgroup/<container>/cpu.max

# 示例:无限制(默认)
echo "max 100000" > /sys/fs/cgroup/<container>/cpu.max
参数 说明 典型值
quota 每个周期内允许的最大 CPU 时间(微秒) 50000(0.5 核)、100000(1 核)
period 周期长度(微秒),默认 100000 100000(100ms)
max 表示无上限,允许超用 max
注意: cpu.max 与 cpu.weight 协同工作:权重决定竞争时的分配比例,上限决定最大值。设置过小的 quota 可能导致容器频繁被节流(throttling),影响延迟敏感型应用。

4.4 cpuset 控制器:绑核与内存亲和性

cpuset 控制器在 cgroups v2 中负责将 cgroup 绑定到特定 CPU 核心和内存节点。它通过两个核心文件实现绑核:

  • cpuset.cpus:允许使用的 CPU 核心列表(如 0-3,7)。
  • cpuset.mems:允许使用的内存节点列表(如 0 或 0-1)。

绑核的核心价值在于减少 CPU 缓存失效和上下文切换,提升性能敏感型工作负载的确定性。

cpuset 绑核与 CPU 拓扑 cpuset 绑核与 CPU 拓扑 NUMA Node 0 Core 0 Core 1 Core 2 Core 3 L3 Cache (共享) NUMA Node 1 Core 4 Core 5 Core 6 Core 7 L3 Cache (共享) 容器 A: cpuset.cpus=0-1 容器 B: cpuset.cpus=4-7 绑核策略: • 容器 A 绑定到 NUMA Node 0 的 Core 0-1,保持 L3 缓存亲和性。 • 容器 B 绑定到 NUMA Node 1 的 Core 4-7,避免跨 NUMA 内存访问延迟。

4.5 配置示例:在 containerd 中使用 cpuset

以下示例展示如何通过 containerd 的 cri 插件为容器配置 cpuset 绑核:

# containerd 配置文件 /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd]
  default_runtime_name = "runc"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  runtime_type = "io.containerd.runc.v2"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true

# 创建容器时通过 cgroup 路径设置 cpuset
# 假设容器 cgroup 路径为 /sys/fs/cgroup/kubepods/besteffort/podxxx/container-yyy
echo "0-3" > /sys/fs/cgroup/kubepods/besteffort/podxxx/container-yyy/cpuset.cpus
echo "0" > /sys/fs/cgroup/kubepods/besteffort/podxxx/container-yyy/cpuset.mems
性能收益: 在 2 路 NUMA 服务器上,将 Redis 容器绑定到单个 NUMA 节点的核心,可减少 30%-50% 的跨 NUMA 内存访问延迟,吞吐量提升 15%-25%。

4.6 本章小结