Linux cgroup 实现 SSH 用户资源与 GPU 访问限制

Linux cgroup 实现 SSH 用户资源与 GPU 访问限制

概述

在多用户共享的 Linux 服务器上,需要解决两个问题:

  1. 资源限制:防止单个用户过度占用 CPU、内存,影响其他用户
  2. GPU 隔离:普通用户不能直接访问 GPU,只能通过 Slurm 等调度系统申请使用

通过 systemd 的 slice 机制 + DevicePolicy,可以用极简的配置同时解决这两个问题。

原理

systemd 用户 slice 层级

用户通过 SSH 登录时,systemd 会自动为该用户创建 slice,层级如下:

1
2
3
4
5
user.slice
├── user-0.slice ← root 用户 (UID=0)
├── user-1003.slice ← 普通用户 (UID=1003)
├── user-1011.slice ← 普通用户 (UID=1011)
└── ...

关键机制是 模板继承user-.slice.d/ 中的配置会自动应用到所有 user-*.slice。而为特定 UID 创建的 user-0.slice.d/ 则可以覆盖模板配置,实现对 root 的豁免。

DevicePolicy 策略

策略 行为
auto 默认值,不限制设备访问
closed 只允许标准设备(/dev/null、/dev/tty 等),阻止 nvidia 等非标准设备
strict 仅允许 DeviceAllow 中显式列出的设备

closed 正好满足需求:普通用户的 SSH 会话看不到 GPU,但正常终端操作不受影响。

Slurm 作业不受影响

Slurm 作业运行在独立的 cgroup 中,由 Slurm 的 task/cgroup 插件管理设备权限。用户通过 srun/sbatch 提交的 GPU 作业不受 DevicePolicy=closed 限制,因为 Slurm 在作业 cgroup 中会放行分配到的 GPU 设备。

完整配置脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash
# 1. 清理现有配置
sudo rm -rf /etc/systemd/system/user.slice.d/
sudo rm -rf /etc/systemd/system/user-*.slice.d/
sudo rm -rf /etc/systemd/system/user@.service.d/

# 2. 顶层 user.slice:全局资源上限(所有用户合计不能超过此值)
sudo mkdir -p /etc/systemd/system/user.slice.d

sudo tee /etc/systemd/system/user.slice.d/limits.conf <<'EOF'
[Slice]
CPUQuota=2400%
CPUWeight=2400
MemoryMax=64G
EOF

# 3. 普通用户 slice:限制 CPU 权重/内存 + DevicePolicy=closed 阻止 GPU 访问
# closed 策略允许标准设备(/dev/null, /dev/tty 等),但禁止访问 GPU 设备(/dev/nvidia*)。
sudo mkdir -p /etc/systemd/system/user-.slice.d

sudo tee /etc/systemd/system/user-.slice.d/limits.conf <<'EOF'
[Slice]
CPUWeight=100
MemoryMax=8G
DevicePolicy=closed
EOF

# 4. root (UID 0):高权重 + 恢复 GPU 访问
sudo mkdir -p /etc/systemd/system/user-0.slice.d

sudo tee /etc/systemd/system/user-0.slice.d/override.conf <<'EOF'
[Slice]
CPUWeight=2400
MemoryMax=
DevicePolicy=auto
EOF

# 5. 特定用户覆盖:高权重 + 恢复 GPU 访问
# 把白名单用户的 UID 列在这里,他们和 root 一样不受限
sudo mkdir -p /etc/systemd/system/user-1003.slice.d

sudo tee /etc/systemd/system/user-1003.slice.d/override.conf <<'EOF'
[Slice]
CPUWeight=2400
MemoryMax=
DevicePolicy=auto
EOF

# 6. 重载配置
sudo systemctl daemon-reload
sudo systemctl restart systemd-logind

配置详解

1. 清理旧配置

删除所有残留的 slice 配置,确保干净的状态:

1
2
3
sudo rm -rf /etc/systemd/system/user.slice.d/
sudo rm -rf /etc/systemd/system/user-*.slice.d/
sudo rm -rf /etc/systemd/system/user@.service.d/

2. 顶层全局限速 (user.slice.d)

user.slice 是所有用户 slice 的根,它下面的限制对全部用户合计生效:

1
2
3
4
[Slice]
CPUQuota=2400% # 全部用户合计最多 24 核
CPUWeight=2400 # 资源竞争时的权重(默认 100)
MemoryMax=64G # 全部用户合计内存上限
  • CPUQuota=2400%:所有 user-*.slice 加起来最多占用 24 核
  • CPUWeight=2400:当整机 CPU 紧张时,user.slice 内进程会拿到更高份额(相对 system.slice 的默认 100)
  • MemoryMax=64G:所有用户合计内存硬限制

不配置这一层的话,单用户限速是有的,但理论上所有用户可以一起把 CPU 跑满。

3. 普通用户模板 (user-.slice.d)

1
2
3
4
[Slice]
CPUWeight=100
MemoryMax=8G # 内存硬限制 8GB
DevicePolicy=closed # 阻止 GPU 等非标准设备访问
  • CPUWeight=100:默认权重,竞争中处于劣势
  • MemoryMax=8G:内存硬限制,超限触发 OOM Killer
  • DevicePolicy=closed:允许 /dev/null、/dev/tty 等标准设备,阻止 /dev/nvidia* 等 GPU 设备

CPUWeight 替代 CPUQuota 的好处:用户需要时可以短时间借用空闲 CPU(抢占空闲核),长时间占用才会被压回 100/2400=4.2% 的份额。这样突发编译等场景不会卡死,但又不会被恶意常驻进程拖垮。

4. root 豁免 (user-0.slice.d)

1
2
3
4
[Slice]
CPUWeight=2400 # 高权重
MemoryMax= # 空值 = 取消内存限制
DevicePolicy=auto # 恢复 GPU 访问

空值表示取消从模板继承的限制。root 用户不受任何约束。

5. 特定用户白名单 (user-<UID>.slice.d)

把可信用户(管理员、研究员)的 UID 单独加 override,让他们与 root 同等待遇:

1
2
3
4
[Slice]
CPUWeight=2400
MemoryMax=
DevicePolicy=auto

白名单的另一种用法:放开 CPU/内存但仍然禁止 GPU 访问(如服务账号):

1
2
3
[Slice]
CPUWeight=2400
DevicePolicy=closed

要新增白名单用户:

1
2
3
4
5
6
7
8
9
UID=$(id -u <username>)
sudo mkdir -p /etc/systemd/system/user-${UID}.slice.d
sudo tee /etc/systemd/system/user-${UID}.slice.d/override.conf <<'EOF'
[Slice]
CPUWeight=2400
MemoryMax=
DevicePolicy=auto
EOF
sudo systemctl daemon-reload

6. 生效

1
2
sudo systemctl daemon-reload
sudo systemctl restart systemd-logind

注意restart systemd-logind 会断开所有 SSH 会话(包括当前的),请在 console 或 IPMI 上操作,或改用 sudo reboot 重启系统。

验证

检查单个用户的 cgroup 配置

1
2
3
# 查看某用户 slice 的 CPU 和内存限制
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/cpu.max
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/memory.max

批量巡检所有用户 slice(check.sh)

要快速看每个用户当前生效的限制,用以下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

find /sys/fs/cgroup/user.slice -name "cpu.max" -type f -print0 |
while IFS= read -r -d '' file; do
user_dir=$(dirname "$file")
cpu_limit=$(cat "$file")
mem_file="$user_dir/memory.max"

if [ -f "$mem_file" ]; then
mem_limit=$(cat "$mem_file")
else
mem_limit="未设置"
fi

echo "$user_dir: CPU=$cpu_limit, Memory=$mem_limit"
done

输出示例:

1
2
3
/sys/fs/cgroup/user.slice/user-0.slice: CPU=max 100000, Memory=max
/sys/fs/cgroup/user.slice/user-1003.slice: CPU=max 100000, Memory=max
/sys/fs/cgroup/user.slice/user-1011.slice: CPU=200% 800000, Memory=8G

  • CPU=max 100000 表示不限速(max = 100000 微秒/周期)
  • CPU=200% 800000 表示限制为 200%
  • Memory=max 表示不限内存
  • Memory=8G 表示限制为 8GB

通过对比目录下的限制值与 user-.slice.d/limits.conf 应有值,能立即定位哪个用户的 override 没生效。

验证 GPU 不可见(普通用户)

1
2
3
# 普通用户直接运行:
nvidia-smi
# 预期输出:Failed to initialize NVML: Unknown Error

验证 GPU 通过 Slurm 可用

1
2
3
# 通过 Slurm 申请 GPU 后可以正常使用:
srun --gres=gpu:V100:1 --mem=1G nvidia-smi
# 预期输出:正常显示 GPU 信息

查询用户 UID

slice 名称中的数字是 UID,可通过以下命令查询:

1
2
id hwen          # 输出: uid=1011(hwen) → 对应 user-1011.slice
getent passwd 1003 # 反向查询 UID 对应的用户名

注意事项

  1. Slurm 前提:需要 Slurm 配置 ConstrainDevices=yes(在 cgroup.conf 中),否则 Slurm 作业也无法访问 GPU
  2. 已有会话:配置生效后,已登录用户需要重新登录才会应用新限制
  3. 参数调整:CPUQuota 和 MemoryMax 根据实际硬件资源调整
  4. 持久化:配置写入 /etc/systemd/system,重启后自动生效

操作前请备份重要数据,建议在测试环境验证后再部署到生产系统。


Linux cgroup 实现 SSH 用户资源与 GPU 访问限制
https://zhazhajust.github.io/2025/12/21/linux-cgroup-ssh-limit/
作者
JayZz
发布于
2025年12月21日
许可协议