systemd 教程 / Slice 与资源控制(cgroups)
Slice 与资源控制(cgroups)
概述
systemd 通过 Linux cgroups(控制组)实现对服务的资源限制和隔离。Slice(切片)是 systemd 管理 cgroups 层次结构的机制,可以对一组服务统一设置资源上限。在生产环境中,合理配置资源限制是保障系统稳定性的关键。
cgroups v2 概述
cgroups 简介
cgroups(Control Groups)是 Linux 内核提供的资源管理机制,可以限制、统计和隔离进程组的资源使用(CPU、内存、IO 等)。
cgroups v1 vs v2
| 对比项 | cgroups v1 | cgroups v2 |
|---|---|---|
| 层次结构 | 多棵树(每个控制器独立) | 单一统一层次 |
| 挂载点 | /sys/fs/cgroup/*/ | /sys/fs/cgroup/ |
| 控制器 | 各自独立管理 | 统一管理 |
| 委托支持 | 有限 | 完整支持 |
| PSI 支持 | 不支持 | 支持(Pressure Stall Information) |
| 推荐版本 | 旧系统 | 新系统推荐 |
检查当前 cgroups 版本
# 检查 cgroups 版本
stat -fc %T /sys/fs/cgroup/
# 输出 cgroup2fs 表示 v2
# 查看已挂载的 cgroups
mount | grep cgroup
# 查看内核支持
cat /proc/filesystems | grep cgroup
systemd Slice 层次结构
默认层次
/ (root slice)
├── system.slice — 系统服务
├── user.slice — 用户会话和用户服务
└── machine.slice — 虚拟机和容器
查看 cgroups 树
# 查看 cgroups 树
systemd-cgls
# 输出示例:
# Control group /:
# -.slice
# ├─user.slice
# │ └─user-1000.slice
# │ └─session-1.scope
# │ └─1234 /bin/bash
# ├─system.slice
# │ ├─nginx.service
# │ │ └─5678 nginx: worker process
# │ ├─mysql.service
# │ │ └─9012 /usr/sbin/mysqld
# │ └─sshd.service
# │ └─3456 sshd: user [priv]
# └─machine.slice
自定义 Slice
# /etc/systemd/system/custom-apps.slice
[Unit]
Description=Custom Applications Slice
[Slice]
# 在此设置该 slice 的资源限制
CPUQuota=200%
MemoryMax=2G
CPU 资源限制
CPUQuota — CPU 时间配额
限制服务可使用的最大 CPU 时间百分比:
# /etc/systemd/system/myapp.service
[Service]
Type=simple
ExecStart=/opt/myapp/bin/server
# 限制最多使用 1.5 个 CPU 核心
CPUQuota=150%
| 配置值 | 含义 |
|---|---|
50% | 最多使用半个 CPU 核心 |
100% | 最多使用 1 个 CPU 核心 |
200% | 最多使用 2 个 CPU 核心 |
400% | 最多使用 4 个 CPU 核心(4 核系统) |
CPUWeight — CPU 权重
当 CPU 资源竞争时,按权重分配(默认值 100):
[Service]
# 高优先级服务
CPUWeight=200
| 场景 | CPUWeight | 效果 |
|---|---|---|
| 高优先级 | 200 | 获得 2 倍于默认的 CPU 时间 |
| 默认 | 100 | 标准分配 |
| 低优先级 | 50 | 获得一半于默认的 CPU 时间 |
| 后台任务 | 10 | 仅在空闲时使用 CPU |
CPUSet — CPU 亲和性
将服务绑定到指定的 CPU 核心:
[Service]
# 绑定到 CPU 0 和 CPU 1
AllowedCPUs=0 1
💡 提示:对于延迟敏感的服务,使用 AllowedCPUs 绑定核心可以减少 CPU 缓存抖动。
内存资源限制
MemoryMax — 最大内存
超过限制时,OOM killer 会终止进程:
[Service]
ExecStart=/usr/sbin/mysqld
# 最大内存 4GB
MemoryMax=4G
MemoryHigh — 内存软限制
超过限制时,进程会被节流(throttle),但不会被杀死:
[Service]
ExecStart=/usr/sbin/mysqld
# 软限制 3GB
MemoryHigh=3G
# 硬限制 4GB
MemoryMax=4G
MemoryMin — 内存最低保障
在内存竞争时,保证至少获得指定内存:
[Service]
# 保证至少 512MB
MemoryMin=512M
内存限制层次
MemoryMin (最低保障) ─── MemoryLow (低水位) ─── MemoryHigh (软限制) ─── MemoryMax (硬限制)
| 参数 | 作用 | 超出后果 |
|---|---|---|
MemoryMin | 最低保障 | 其他服务被回收 |
MemoryLow | 低水位 | 可被回收 |
MemoryHigh | 软限制 | 进程被节流 |
MemoryMax | 硬限制 | 触发 OOM killer |
MemorySwapMax | swap 限制 | swap 空间用完后可能 OOM |
⚠️ 注意:MemoryMax 设置过低会导致服务被 OOM killer 终止。建议先设置 MemoryHigh,再设置合理的 MemoryMax。
查看内存使用
# 查看服务的内存使用
systemctl status mysql.service
# 或
systemd-cgtop
# 查看详细内存统计
cat /sys/fs/cgroup/system.slice/mysql.service/memory.current
cat /sys/fs/cgroup/system.slice/mysql.service/memory.stat
IO 资源限制
IOWeight — IO 权重
控制块设备 IO 的优先级(默认 100):
[Service]
# 后台备份服务,降低 IO 优先级
IOWeight=50
IOReadBandwidthMax — 读取带宽限制
[Service]
# 限制读取带宽为 50MB/s
IOReadBandwidthMax=/dev/sda 50M
# 限制写入带宽为 30MB/s
IOWriteBandwidthMax=/dev/sda 30M
IOReadIOPSMax — IOPS 限制
[Service]
# 限制读取 IOPS
IOReadIOPSMax=/dev/sda 1000
# 限制写入 IOPS
IOWriteIOPSMax=/dev/sda 500
💡 提示:对于需要限制磁盘 IO 的服务(如备份、日志归档),IO 限制可以有效避免影响其他关键服务。
任务数限制
TasksMax
限制服务可创建的最大进程/线程数:
[Service]
# 最多 512 个任务
TasksMax=512
⚠️ 注意:如果服务创建的线程或子进程过多,可能耗尽系统 PID 资源。TasksMax 可以防止 fork 炸弹。
查看系统级限制
# 查看默认 TasksMax
systemctl show -p DefaultTasksMax
# 查看服务当前任务数
systemctl show mysql.service -p TasksCurrent
systemctl show mysql.service -p TasksMax
systemd-cgtop 实时监控
systemd-cgtop 类似 top,实时显示各 cgroup 的资源使用:
# 基本使用
systemd-cgtop
# 输出示例:
# Control Group Tasks %CPU Memory Input/s Output/s
# / 150 25.3 2.1G 1.2M 3.4M
# /system.slice 45 12.1 1.5G 800K 2.1M
# /system.slice/mysql.service 15 8.2 800M 500K 1.2M
# /system.slice/nginx.service 8 2.1 120M 200K 800K
# /user.slice 20 3.5 400M 100K 300K
# 按内存排序
systemd-cgtop -m
# 只显示一次(不刷新)
systemd-cgtop -n 1 --no-pager
指定列
# 只显示 CPU 和内存
systemd-cgtop -O
# 按路径排序
systemd-cgtop -p
资源限制实战
案例 1:限制 MySQL 内存使用
# /etc/systemd/system/mysql.service.d/memory-limit.conf
[Service]
# 内存软限制 6GB
MemoryHigh=6G
# 内存硬限制 8GB
MemoryMax=8G
# 禁止使用 swap
MemorySwapMax=0
# CPU 限制
CPUQuota=300%
# 任务数限制
TasksMax=512
# 应用配置
sudo systemctl daemon-reload
sudo systemctl restart mysql
# 验证
systemctl show mysql.service -p MemoryMax,MemoryHigh
案例 2:限制 Redis 内存
# /etc/systemd/system/redis.service.d/limits.conf
[Service]
MemoryMax=2G
MemoryHigh=1536M
CPUQuota=100%
TasksMax=256
案例 3:后台备份任务
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Backup
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
# 低 CPU 优先级
CPUWeight=20
# 内存限制
MemoryMax=1G
# IO 限速
IOWeight=20
IOReadBandwidthMax=/dev/sda 20M
IOWriteBandwidthMax=/dev/sda 20M
# 任务数限制
TasksMax=64
# 使用低优先级的 nice 值
Nice=19
IOSchedulingClass=idle
案例 4:使用 Slice 统一管理
# /etc/systemd/system/production.slice
[Unit]
Description=Production Services Slice
[Slice]
CPUQuota=800%
MemoryMax=16G
MemoryHigh=12G
TasksMax=4096
# /etc/systemd/system/development.slice
[Unit]
Description=Development Services Slice
[Slice]
CPUQuota=200%
MemoryMax=4G
MemoryHigh=3G
TasksMax=512
# 将 MySQL 放入 production.slice
# /etc/systemd/system/mysql.service.d/slice.conf
[Service]
Slice=production.slice
cgroup 委托(Delegation)
委托允许非特权用户管理自己的 cgroup 子树:
# /etc/systemd/system/container.slice
[Unit]
Description=Container Slice
[Slice]
Delegate=yes
# 委托所有控制器
Delegate=cpu memory io
用户服务委托
# ~/.config/systemd/user/myapp.slice
[Unit]
Description=My App Slice
[Slice]
Delegate=cpu memory
MemoryMax=2G
CPUQuota=200%
💡 提示:对于容器运行时(如 Podman、Docker),委托 cgroup 是必要的,这样容器可以管理自己的子 cgroup。
Drop-in 文件技巧
使用 drop-in 文件覆盖默认资源限制,而不是修改原始单元文件:
# 创建 drop-in 文件
sudo systemctl edit mysql.service
在编辑器中添加:
[Service]
MemoryMax=8G
MemoryHigh=6G
CPUQuota=300%
# 查看生成的 drop-in 文件
systemctl cat mysql.service
# 重载并重启
sudo systemctl daemon-reload
sudo systemctl restart mysql
常用命令汇总
# 查看 cgroups 树
systemd-cgls
# 实时监控资源使用
systemd-cgtop
# 查看服务的资源限制
systemctl show mysql.service -p MemoryMax,MemoryHigh,CPUQuota,CPUWeight
# 查看服务当前资源使用
systemctl status mysql.service
# 查看所有 slice
systemctl list-units --type=slice
# 设置运行时资源限制(不修改文件)
sudo systemctl set-property mysql.service MemoryMax=8G