使用 systemd 的 cgroups v2 资源限制
11 分钟阅读 - 2026年6月3日

使用 cgroups v2 和 systemd 设置 CPU、内存和 I/O 限制。多租户 Linux 主机的实用配置,PSI 监控和分片隔离。
使用 systemd 设置 cgroups v2 资源限制
cgroups v2 是 Linux 内核的统一资源控制框架。 它用单一树结构取代了分散的 v1 层级体系,能够一致地管理 CPU、内存和 I/O,并为 Docker、Kubernetes 和 systemd 中的容器隔离提供基础。本文将介绍如何启用 cgroups v2、通过 systemd 设置限制,并将其应用于实际的多租户托管场景。
启用 cgroups v2
现代发行版默认启用了 cgroups v2:Ubuntu 21.10 及以上、Debian 11 及以上、Fedora 31 及以上以及 RHEL/Rocky 9 及以上。较旧的系统可能运行混合层级结构,或仍默认使用 v1。请通过以下命令进行确认:
stat -fc %T /sys/fs/cgroup/
执行 cgroup2fs 确认 v2 已启用。 tmpfs 通常表示仍为 v1。
若要将混合系统切换为纯 v2 模式,请编辑 /etc/default/grub 并在 GRUB_CMDLINE_LINUX_DEFAULT:
systemd.unified_cgroup_hierarchy=1 cgroup_no_v1=all
然后重新生成 GRUB 并重启:
sudo update-grub
sudo reboot
在生产环境中,请运行内核 5.2 或更高版本以获取 v2 所需的 cgroup 冻结功能,并使用 systemd 244+ 版本以实现完整的 cpuset 委托功能。在 Rocky Linux 8 和 RHEL 8 上,您可能还需要通过在 /etc/systemd/system.conf:
DefaultCPUAccounting=yes
DefaultMemoryAccounting=yes
DefaultIOAccounting=yes
使用 sudo systemctl daemon-reexec。重启后,验证哪些控制器可用:
cat /sys/fs/cgroup/cgroup.controllers
您应看到 cpu, memory, io以及 pids 列出的控制器。这些控制器默认未对子 cgroups 启用。要激活它们,请向根子树控制文件写入:
echo "+cpu +memory +io" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
若想全面了解 v2 与 v1 在内部实现上的差异,Michael Kerrisk 在 NDC TechTown 上的演讲是最佳的单一资源:
systemd 如何组织 cgroups
systemd 会为启动的每个服务创建一个 cgroup,其名称取自该服务的单元(unit)。 nginx.service 获取 /sys/fs/cgroup/system.slice/nginx.service/,且其生成的每个进程都运行于该 cgroup 内。三种单元类型直接映射到该层次结构:
| 单元类型 | 角色 | 描述 |
|---|---|---|
.slice | 内部节点 | 将相关服务分组并定义共享限制 |
.service | 终端节点 | 管理由 systemd 启动的进程 |
.scope | 叶节点 | 跟踪外部启动的进程(容器有效载荷、登录会话) |
出厂即提供四个默认分区: -.slice (root), system.slice, user.slice,以及 machine.slice。对切片施加的任何限制将自动应用于其中的每个服务。
有一条 v2 规则值得记住:进程只能驻留在叶节点中。包含子 cgroup 的 cgroup 无法直接托管进程,这就是为什么 systemd 绝不会将服务放入切片的干节点中。
请务必通过 systemd 设置限制,而非直接写入 /sys/fs/cgroup/ 。手动写入不会在重启后保留,且会与 systemd 对该层次结构的独占所有权发生冲突。对于一次性更改和单元文件插入,请使用 systemctl set-property 用于一次性更改,并使用 unit drop-ins (systemctl edit nginx.service) 进行永久性修改。
CPU 限制
cgroups v2 提供了两种 CPU 控制方式:硬限制(cpu.max,在 systemd 中显示为 CPUQuota 在 systemd 中)以及比例权重(cpu.weight / CPUWeight).
CPUQuota 是绝对上限。 CPUQuota=50% 允许占用半个核心; CPUQuota=200% 允许占用相当于两个完整核心的时间。如果服务试图超过此限制,无论 CPU 其余部分有多闲置,都会被限流。
CPUWeight 仅在资源竞争时起作用。取值范围为 1 到 10,000,默认值为 100。当三个权重分别为 150、100 和 50 的服务同时请求 CPU 时间时,它们将分别获得约 50%、33% 和 17% 的 CPU 时间。 当 CPU 处于空闲状态时,权重不会产生任何限制。
对于对延迟敏感的工作负载,可使用 AllowedCPUs=。这能减少上下文切换,并保持各核心缓存处于热状态:
[Service]
CPUQuota=200%
CPUWeight=150
AllowedCPUs=0-3
当需要可预测的成本(如多租户计费、隔绝“嘈杂邻居”)时,请使用硬配额。当您希望最大限度利用硬件资源,且仅在负载峰值期间需要优先级排序时,请使用权重。
cgroups v2 的内存限制
内存分为两层: memory.high (软限制,即限速)和 memory.max (硬限制,OOM)。关于交换分区、页面回收及内核 OOM 杀手(OOM killer)的背景知识,请参阅我们关于 Linux 内存管理的配套文章。
将 memory.high 约比 memory.max。一旦 memory.high ,这通常能让工作负载在 OOM 杀手触发前恢复。若使用率达到 memory.max,内核将终止 cgroup 中的进程。
典型配置:
[Service]
MemoryHigh=400M
MemoryMax=512M
MemorySwapMax=0
MemorySwapMax=0 为该 cgroup 禁用交换分区。对于对延迟敏感的工作负载(如数据库、实时流媒体),此配置值得采用,因为交换 I/O 会导致尾部延迟急剧恶化。
对于那些若留下孤立子进程会导致共享状态损坏的工作者池,请将 1 写入 cgroup 的 memory.oom.group 文件中。当某个进程因内存不足(OOM)被终止时,内核将同时终止该 cgroup 中的所有进程。
查看 memory.events 以查看某项服务被限流或因内存不足(OOM)被终止的频率:
cat /sys/fs/cgroup/system.slice/nginx.service/memory.events
该 high 和 oom_kill 计数器可帮助判断限制值是否设置得当。若这些值长期不为零,则表明工作负载需要更大的资源余量。
I/O 限制
I/O 控制器采用相同的双模式设计:通过 io.max ,以及通过 io.weight.
限制按块设备设置,通过 major:minor 编号标识。可通过 lsblk -o NAME,MAJ:MIN。一个典型的 systemd 配置:
[Service]
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 30M
IOReadIOPSMax=/dev/sda 1000
IOWriteIOPSMax=/dev/sda 500
io.weight 其工作原理如下 cpu.weight:范围为 1 到 10,000,默认值为 100。将 500 分配给面向用户的服务,并将 50 分配给夜间备份,这样既能防止备份在高峰时段占用磁盘带宽,又能在无其他需求时让其使用全部带宽。
I/O 限制仅在针对正确设备时生效。内核按块设备跟踪 I/O,因此对 /dev/sda 对流向 /dev/nvme0n1的 I/O 毫无作用。在拥有多块磁盘的主机上,请针对每块磁盘分别设置限制。
基于切片的多租户隔离
对于共享环境,请为每个租户定义一个切片。创建 /etc/systemd/system/tenant-a.slice:
[Slice]
CPUQuota=200%
CPUWeight=150
MemoryHigh=3584M
MemoryMax=4096M
MemorySwapMax=0
IOReadBandwidthMax=/dev/sda 200M
TasksMax=512
TasksMax=512 限制了进程和线程的总数,从而防止某个租户中的fork炸弹导致主机崩溃。将租户服务放入该切片中(通过 Slice=tenant-a.slice 在单元文件中)将租户服务放入该切片,它们将自动继承所有设置。
此模式同样适用于将嘈杂的后台任务与面向用户的服务分离。将备份、日志轮换和批处理作业放入一个 background.slice 具有较低 CPUWeight 和 io.weight 。当系统空闲时,它们将获得全部资源;而当生产流量到达时,它们会主动让出资源。
对于 Docker 和 Podman 等容器运行时,请在 Delegate=yes 。这使它们无需 root 权限即可管理自身的子 cgroup,且父切片上设置的限制仍适用于其下的所有进程。
使用 systemd-cgtop 和 PSI 进行监控
若要查看按 cgroup 分类的 CPU、内存和 I/O 的实时 top 风格视图,请运行:
systemd-cgtop
若需查看静态层次结构及进程所在位置,请使用 systemd-cgls.
v2版本中对生产环境监控最有价值的功能当属压力阻塞信息(PSI)。PSI报告了cgroup内任务因等待资源而阻塞的时间占比,该信息通过每个cgroup对应的三个文件呈现:
cat /sys/fs/cgroup/tenant-a.slice/cpu.pressure
cat /sys/fs/cgroup/tenant-a.slice/memory.pressure
cat /sys/fs/cgroup/tenant-a.slice/io.pressure
CPU 利用率为 100% 且压力为 0% 时,系统状态良好。此时,所有需要 CPU 的任务都能获得资源。同个 CPU 若利用率为 80% 但压力为 30%,则意味着任务正在排队等待运行时间。请针对 PSI 设置告警,而非利用率:它能捕捉到利用率指标完全忽略的资源竞争情况。
无需重启即可实时调整限制:
sudo systemctl set-property tenant-a.slice MemoryMax=6144M
更改立即生效,并在重启后保持有效。结合基于 PSI 的警报,这使您能够在负载变化演变为 OOM 杀死进程或失控延迟之前做出响应。
如果您正在运行高密度多租户工作负载,且需要一台具备充足余量以干净利落地应用这些策略的主机,我们的专用服务器正是为此而打造。
Linux 内存管理:交换、OOM 杀手和 Cgroups
12 分钟阅读 - 2026年5月31日