使用 systemd 的 cgroups v2 资源限制

11 分钟阅读 - 2026年6月3日

hero section cover
目录
  • 使用 systemd 设置 cgroups v2 资源限制
  • 启用 cgroups v2
  • systemd 如何组织 cgroups
  • CPU 限制
  • cgroups v2 的内存限制
  • I/O 限制
  • 基于切片的多租户隔离
  • 使用 systemd-cgtop 和 PSI 进行监控
分享

使用 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

highoom_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 具有较低 CPUWeightio.weight 。当系统空闲时,它们将获得全部资源;而当生产流量到达时,它们会主动让出资源。

对于 DockerPodman 等容器运行时,请在 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 杀死进程或失控延迟之前做出响应。

如果您正在运行高密度多租户工作负载,且需要一台具备充足余量以干净利落地应用这些策略的主机,我们的专用服务器正是为此而打造。

博客

本周特色

更多文章
为什么必须拥有功能强大且不计量的 VPS

为什么必须拥有功能强大且不计量的 VPS

非计费 VPS 以固定端口速度提供固定费率带宽。它与计费计划有何不同,什么时候会有回报,以及购买前需要检查什么。

7 分钟阅读 - 2025年5月9日

Linux 内存管理:交换、OOM 杀手和 Cgroups

12 分钟阅读 - 2026年5月31日

更多文章