原力注入

解读 Linux Cgroup 之 cpuset 子系统及其在 Docker 中的使用

0. 相关文章

Image

1. 背景与概述

在 Linux 系统中,cgroup(Control Groups)是一种用于限制、记录和隔离进程资源使用的机制。其中,cpuset 子系统主要用于管理 CPU 和内存节点的分配,可以有效实现资源隔离和性能优化。

在现代容器编排系统(如 Kubernetes)中,cpuset 的功能被进一步扩展,用于满足高性能计算(HPC)、实时任务和 NUMA 优化等场景的需求。本文将全面介绍 cpuset 的基本功能、在 Kubernetes 中的应用以及一些高级使用场景。

2. cpuset 子系统的基本功能

cpuset 提供了对 CPU 和内存节点的绑定与管理功能。

2.1 cpuset 的核心配置文件

cpuset 子系统的主要功能是管理进程可用的 CPU 和内存资源。以下是核心配置文件及其用途:

1.cpuset.cpus

指定任务可以运行的 CPU 核心。例如:

echo 0-3 > /sys/fs/cgroup/cpuset/mygroup/cpuset.cpus

表示绑定到第 0 到第 3 个 CPU 核心。

2. cpuset.mems

指定任务可以访问的内存节点(NUMA 节点)。例如:

echo 0 > /sys/fs/cgroup/cpuset/mygroup/cpuset.mems

2.2 cpuset 的状态文件

cpuset 子系统的状态文件提供了实际生效的资源范围,受父 cgroup 的限制。

  • cpuset.effective_cpus:显示当前 cgroup 实际可用的 CPU 集(受父 cgroup 的限制)。

  • cpuset.effective_mems:显示当前 cgroup 实际可用的内存节点(受父 cgroup 的限制)。

cat /sys/fs/cgroup/cpuset/mygroup/cpuset.effective_cpus# 输出: 2-3
cat /sys/fs/cgroup/cpuset/mygroup/cpuset.effective_mems# 输出: 0

2.3 cpuset 的控制文件

cpuset 提供了多个控制文件,用于精细化管理 CPU 和内存资源分配:

  • cpuset.cpu_exclusive:设置为 1 时,此 cgroup 的 CPU 核心不能与其他 cgroup 共享。应用于需要专用 CPU 的场景,例如高优先级任务。

  • cpuset.mem_exclusive:设置为 1 时,此 cgroup 的内存节点不能与其他 cgroup 共享。用于隔离关键任务的内存访问。

  • cpuset.memory_migrate:设置为 1 时,当进程迁移到新绑定的内存节点时,自动迁移其内存页数据。在动态调整内存节点时非常有用。

  • cpuset.sched_load_balance:控制调度器是否对 CPU 核心进行负载均衡:

        • 1:允许负载均衡(默认)。

          0:禁止负载均衡,适用于实时任务。

  • cpuset.memory_spread_page 和 cpuset.memory_spread_slab:控制内存页面和 slab 缓存是否在绑定的内存节点间均匀分布。这对均衡 NUMA 节点内存压力非常有效。

2.4 其他配置

在 cpuset 子系统中,除了已经介绍的核心文件、状态文件和控制文件外,以下几个文件也值得关注,尤其在更细粒度的 CPU 和内存资源管理场景中有用。

2.4.1 cpuset.mem_hardwall

功能:

  • 设置为 1 时,强制进程仅能访问绑定的内存节点,任何超出范围的内存访问都会失败。

  • 设置为 0 时,则允许内存访问溢出到未绑定的节点。

用途:用于严格控制内存分配,适合高隔离要求的任务场景,例如实时计算或资源隔离测试。

2.4.2 cpuset.memory_pressure

功能:只读文件,用于显示当前 cgroup 内存的压力指标(通常为页面回收事件的统计)。

用途:用于监控内存负载情况,帮助调整任务分配。

示例:

cat /sys/fs/cgroup/cpuset/mygroup/cpuset.memory_pressure# 输出的值越高,表示内存压力越大。

2.4.3 cpuset.memory_pressure_enabled

功能:设置为 1 时,启用内存压力监控功能;设置为 0 时关闭。默认值为 0。

用途:启用后可以通过 cpuset.memory_pressure 文件获取实时的内存压力数据。

2.4.4 cpuset.sched_relax_domain_level

功能:

调整调度器中域关系的松弛级别,影响 CPU 调度粒度。值范围和含义如下:

  • -1:默认,调度器根据全局策略选择。

  • 0:最严格,调度仅限于当前 CPU。

  • 1 或更高:逐步放宽域松弛级别,允许更大范围的调度。

用途:用于调优 CPU 调度策略,适合对调度延迟敏感或多核通信的应用。

2.4.5 使用案例

动态内存监控与压力调整

通过启用 memory_pressure 和 memory_pressure_enabled,可以监控 cgroup 的内存压力并调整分配策略:

# 启用内存压力监控echo 1 > /sys/fs/cgroup/cpuset/mygroup/cpuset.memory_pressure_enabled
# 查看当前内存压力cat /sys/fs/cgroup/cpuset/mygroup/cpuset.memory_pressure
# 如果压力较高,可以扩展可用内存节点echo 0-2 > /sys/fs/cgroup/cpuset/mygroup/cpuset.mems

严格的内存和调度控制

结合 mem_hardwall 和 sched_relax_domain_level,实现对内存和调度的严格控制:

# 设置内存节点严格隔离echo 1 > /sys/fs/cgroup/cpuset/mygroup/cpuset.mem_hardwall

# 限制 CPU 调度到当前 CPU 核心echo 0 > /sys/fs/cgroup/cpuset/mygroup/cpuset.sched_relax_domain_level

这些配置文件为 cpuset 提供了更多的精细化控制能力,适合多样化的场景,包括高性能计算(HPC)、实时应用优化以及资源隔离等复杂需求。

3. cpuset 的典型使用场景

3.1 资源隔离

为高优先级任务分配独占资源,避免受到低优先级任务的干扰。

3.2 NUMA 优化

在多 NUMA 节点系统中,将任务绑定到特定节点以减少跨节点内存访问的延迟。

3.3 高性能计算(HPC)

为计算密集型任务绑定一组连续的 CPU 核心,提升计算性能。

3.4. 实时任务

禁用负载均衡,确保任务始终运行在固定 CPU 核心上,满足实时性要求。

4. 实验:在 Docker run 中使用 --cpuset-cpus 和 --cpuset-mems 参数

本实验将指导你如何通过 cpuset-cpus 和 cpuset-mems 选项限制容器使用的 CPU 核心和 NUMA 节点,并在容器外验证其配置。

4.1. 查看系统 CPU 和 NUMA 信息

先确认系统的 CPU 核心和 NUMA 节点信息。


lscpuArchitecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 46 bits physical, 48 bits virtual Byte Order: Little EndianCPU(s): 32...NUMA: NUMA node(s): 2 NUMA node0 CPU(s): 0-7,16-23 NUMA node1 CPU(s): 8-15,24-31...

说明:

  • 系统共有 32 个 CPU 核心(编号 0-31);

  • 存在 2 个 NUMA 节点;

  • node0 包含 CPU 核心 0-7,16-23;

  • node1 包含 CPU 核心 8-15,24-31。

查看 numa 信息

numactl -Havailable: 2 nodes (0-1)node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23node 0 size: 128797 MBnode 0 free: 121323 MBnode 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31node 1 size: 129017 MBnode 1 free: 114372 MBnode distances:node   0   1  0:  10  21  1:  21  10

4.2 启动 Docker 容器,限制 CPU 核心和 NUMA 节点

启动容器并限制使用 CPU 核心 0-3 和 NUMA 节点 0。

docker run -it --rm --name cpuset-test --cpuset-cpus="0-3" --cpuset-mems="0" ubuntu:latest bash
root@f29891b16847:/# uname -aLinux f29891b16847 6.8.0-45-generic #45~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Sep 11 15:25:05 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
# 注意不要退出容器

说明:

  • --cpuset-cpus="0-3":限制容器只能使用 CPU 核心 0 到 3。

  • --cpuset-mems="0":限制容器只能使用 NUMA 节点 0。

4.3 在主机上验证容器的配置

4.3.1 获取容器 ID

docker ps -l --format "{{.ID}}: {{.Names}}"f29891b16847: cpuset-test

4.3.2 验证 cpuset-cpus 和 cpuset-mems

使用 docker inspect 命令验证容器的 CPU 和内存节点限制:

docker inspect <container_id> --format 'CpusetCpus: {{.HostConfig.CpusetCpus}} CpusetMems: {{.HostConfig.CpusetMems}}'
#输出docker inspect f29891b16847 --format 'CpusetCpus: {{.HostConfig.CpusetCpus}} CpusetMems: {{.HostConfig.CpusetMems}}'CpusetCpus: 0-3 CpusetMems: 0

4.3.3 通过 /sys/fs/cgroup 进一步验证

1. 获取容器的完整 ID:

docker inspect <container_id> --format '{{.Id}}'
docker inspect f29891b16847 --format '{{.Id}}'f29891b16847ac394665c8d82591b9ed97380ea048f4f084541b73c0165429c0

2. 查看容器绑定的 CPU 和内存节点:

cat /sys/fs/cgroup/cpuset/docker/<container_id>/cpuset.cpuscat /sys/fs/cgroup/cpuset/docker/<container_id>/cpuset.mems
cat /sys/fs/cgroup/cpuset/docker/f29891b16847ac394665c8d82591b9ed97380ea048f4f084541b73c0165429c0/cpuset.cpus0-3
cat /sys/fs/cgroup/cpuset/docker/f29891b16847ac394665c8d82591b9ed97380ea048f4f084541b73c0165429c0/cpuset.mems0

4.4 验证 NUMA 节点绑定

可以使用 taskset 查看容器内进程的 NUMA 绑定情况。

docker exec <container_id> ps -eo pid,comm | grep bash
# 因为容器的 terminal 没有关闭,可以直接执行
root@f29891b16847:/# ps -eo pid,comm PID COMMAND 1 bash 15 ps
root@f29891b16847:/# taskset -pc 1pid 1's current affinity list: 0-3

4.5 运行 CPU 密集型任务验证

为了验证容器确实使用了指定的 CPU 核心和 NUMA 节点,可以在容器内运行 stress 或其他 CPU 密集型任务:

apt update && apt install stress -y
stress --cpu 4 --timeout 30

在宿主机上运行 pidstat,观察任务只使用了 CPU 核心 0 到 3:

ps -elf | grep stress0 S root        2646    2381  0  80   0 -   905 do_wai 03:13 pts/0    00:00:00 stress --cpu 4 --timeout 601 R root        2647    2646 79  80   0 -   905 -      03:13 pts/0    00:00:02 stress --cpu 4 --timeout 601 R root        2648    2646 80  80   0 -   905 -      03:13 pts/0    00:00:02 stress --cpu 4 --timeout 601 R root        2649    2646 80  80   0 -   905 -      03:13 pts/0    00:00:02 stress --cpu 4 --timeout 601 R root        2650    2646 80  80   0 -   905 -      03:13 pts/0    00:00:02 stress --cpu 4 --timeout 600 S root        2652    2146  0  80   0 -  1620 pipe_r 03:13 pts/0    00:00:00 grep --color=auto stress
pidstat -t -p 2647 1Linux 6.8.0-45-generic (idc-154) 11/27/2024 _x86_64_ (32 CPU)
03:13:34 AM UID TGID TID %usr %system %guest %wait %CPU CPU Command03:13:35 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:35 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:36 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:36 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:37 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:37 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:38 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:38 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:39 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:39 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:40 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:40 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:41 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:41 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:42 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:42 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:43 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:43 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:44 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:44 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress03:13:45 AM 0 2647 - 100.00 0.00 0.00 0.00 100.00 0 stress03:13:45 AM 0 - 2647 100.00 0.00 0.00 0.00 100.00 0 |__stress

pidstat 不够方便,我们可以用 htop 观测。

Image

如果我们在容器内使用更多 CPU 进行测试:

stress --cpu 6 --timeout 30

htop 截图中,我们可以看到还是只有 4 个 CPU 被打满了。

Image

参考

Redhat 资源管理指南 A.4. cpuset(https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux/7/html/resource_management_guide/sec-cpuset)