专栏:运维漏洞指南 | 难度:高级 | 适用:Linux 服务器运维、自托管 AI 服务
TL;DR
Linux 服务器内存吃紧的典型剧本:SSH 卡死 → 服务超时 → 内核 OOM Killer 几十秒后才姗姗来迟 → 杀掉的可能不是罪魁祸首。
systemd-oomd 是 systemd 自带的用户态 OOM 管理器(systemd 247+ 内置),它不等到内存耗尽,而是通过 PSI(Pressure Stall Information) 实时监控 cgroup 的内存压力——压力一过阈值,立刻精准杀掉肇事 cgroup 里的进程,而不是等整个系统崩了才动手。
1. 内核 OOM Killer 的三个致命问题
1.1 出手太晚
内核 OOM Killer 只在 __alloc_pages_slowpath 彻底失败时才触发。此时系统已经经历了:
内存不足 → 换出匿名页 → 回收 page cache → 压缩内存 → OOM Killer
这期间 SSH 可能已经无响应、API 超时、用户看到 502。
1.2 杀的不一定对
内核根据 oom_score 决定杀谁。公式:
oom_score = (进程RSS / 总内存) × 1000 + oom_score_adj
但这个公式不知道哪个进程是"可以杀的 batch job",哪个是"不能死的数据库"。默认情况下,内存占用最大的进程最先被杀——这在单服务机器上合理,在多服务混跑时是灾难。
1.3 没有 cgroup 感知
内核 OOM Killer 杀的是进程,不知道 cgroup 边界。如果你用 cgroup 把 AI 推理服务限在 16GB,内核不知道这个限制——它只看到全局内存不够了。
2. systemd-oomd 怎么解决?
systemd-oomd 在用户态运行,直接读 /proc/pressure/memory 和 cgroup v2 的 memory.pressure 文件,三件事同时做:
| 内核 OOM Killer | systemd-oomd |
|---|---|
| 等内存彻底耗尽 | PSI 压力超阈值即触发 |
| 杀单进程 | 杀整个 cgroup |
| 不区分工作负载 | 可配置 per-unit 策略(ManagedOOMPreference=avoid/omit) |
| 一次全局扫描 | 持续监控 |
核心机制:
1. PSI 监控:读取 some avg10(过去 10 秒内至少有一个任务因内存不足被阻塞的时间比例)
2. 交换空间监控:SwapUsedLimit 默认 90%
3. 策略驱动 kill:ManagedOOMMemoryPressure=kill,压力超过 ManagedOOMMemoryPressureLimit 并持续 ManagedOOMMemoryPressureDurationSec 后执行 kill
3. 实战配置
3.1 确认兼容性
# 必须是 cgroup v2
stat -fc %T /sys/fs/cgroup
# 输出应为: cgroup2fs
# PSI 必须可用
cat /proc/pressure/memory
# 有输出即正常
3.2 启用 systemd-oomd
systemctl enable --now systemd-oomd
systemctl status systemd-oomd
3.3 为关键服务设保护策略
比如保护数据库,不让它被 oomd 杀掉:
# /etc/systemd/system/postgresql.service.d/50-oomd.conf
[Service]
ManagedOOMPreference=avoid
avoid 的含义:只有当不杀这个服务会导致整个 slice 被干掉时,才考虑杀它。
对于批处理任务(杀了无所谓):
[Service]
ManagedOOMPreference=omit
3.4 为 AI 推理服务设 slice 级策略
mkdir -p /etc/systemd/system/system.slice.d
cat > /etc/systemd/system/system.slice.d/60-oomd.conf <<'EOF'
[Slice]
ManagedOOMMemoryPressure=kill
ManagedOOMMemoryPressureLimit=50%
ManagedOOMMemoryPressureDurationSec=20s
EOF
systemctl daemon-reload
含义:系统 slice 内所有服务,如果 PSI 内存压力超过 50% 且持续 20 秒,oomd 开始杀。
关于 50% 的取值:
- 交互式服务(Web API):建议 30-40%
- 批处理任务:60-80%
- 数据库:配合 ManagedOOMPreference=avoid,设 80%+
4. 验证与排障
4.1 查看策略是否生效
systemctl show system.slice --property=ManagedOOMMemoryPressure --property=ManagedOOMMemoryPressureLimit --property=ManagedOOMMemoryPressureDurationSec
4.2 模拟内存压力
# 在一个受管理的 service 中运行
stress-ng --vm 1 --vm-bytes 90% --timeout 60s
然后观察:
journalctl -u systemd-oomd -f
你会看到:
systemd-oomd[xxx]: system.slice: Memory pressure 52.34% > 50.00% for > 20s
systemd-oomd[xxx]: Killed /system.slice/stress-test.service due to memory pressure
4.3 常见陷阱
| 陷阱 | 现象 | 修复 |
|---|---|---|
| cgroup v1 | oomd 启动但不工作 | grub: systemd.unified_cgroup_hierarchy=1 |
| PSI 未启用 | /proc/pressure 为空 |
grub: psi=1 |
DefaultMemoryAccounting=no |
per-unit 策略不生效 | system.conf: DefaultMemoryAccounting=yes |
| 桌面环境被杀 | GNOME/KDE session 被 oomd 杀掉 | user@UID.service: ManagedOOMPreference=avoid |
| --- | ||
| ## 5. 与内核 OOM Killer 的分工 | ||
| 推荐策略:oomd 做第一道防线,内核 OOM Killer 做最后兜底。 |
┌──────────────────────────────────────┐
│ systemd-oomd (第一道) │
│ PSI > 阈值 → 精准杀肇事 cgroup │
├──────────────────────────────────────┤
│ Cgroup MemoryMax (第二道) │
│ 硬限制 → 触发 memory.oom.group │
├──────────────────────────────────────┤
│ 内核 OOM Killer (兜底) │
│ 全局内存耗尽 → 按 oom_score 杀 │
└──────────────────────────────────────┘
6. 启示
传统的 Linux 运维思路是「加大内存 / 加 swap / 调 swappiness」。systemd-oomd 给了一个不同的答案:接受内存会不够的事实,但控制在谁先死、什么时候死。
对于跑 AI 推理服务的机器——vLLM 峰值吃 90% 显存、剩余内存还要处理图像预处理——oomd 比粗暴加 swap 更有效。swap 慢,oomd 快。
出处:systemd-oomd.service(8) | dev.to: Practical systemd-oomd | systemd.io/PRESSURE
本文由 admin 原创,转载请注明出处。
评论
0