用 systemd-run 给一次性命令加上内存与 CPU 限额
在云主机上排障时,经常要临时跑一条压测、日志压缩或数据校验命令。直接在登录 shell 里执行,最容易出的问题不是命令本身写错,而是它把整台机器的内存或 CPU 吃满,连带把 sshd、监控代理一起拖死。systemd 自带的 systemd-run 可以把一条一次性命令放进临时单元,并带上 cgroup 限额。这不需要写 unit 文件,适合只跑这一次的场景。
下面以一台使用 systemd 的 Linux 云主机为例,说明怎么给临时命令加内存上限、CPU 配额,并在结束后看资源记账。示例只使用系统自带工具,不创建长期服务,也不改全局限额。
先确认运行时
先看 systemd 版本和 cgroup 层级。cgroup v2 是统一层级,内存和 CPU 控制器通常已经挂在同一棵树上。如果还是 v1,内存和 CPU 会分成两套目录,systemd-run 仍然可用,但手工查看用量时路径不同。
systemctl --version | head -n 1
mount | grep -E 'cgroup2|cgroup '
cat /proc/1/cgroup | head
如果 mount 结果里能看到 cgroup2 on /sys/fs/cgroup,后面的查看路径就按 v2 来写。版本过旧时,个别属性名会不识别,systemctl 会直接报 Unknown key,这时先升级 systemd,不要把属性写进全局配置碰运气。
给命令加内存上限
假设要压缩一个很大的日志目录,不希望压缩进程把可用内存吃到只剩几百兆。MemoryMax 是硬上限,触顶后内核会回收,仍不够就只在这个临时单元里触发 OOM,而不是整机一起被杀掉。MemoryHigh 是软限制,超过后该 cgroup 会被更积极地回收,进程变慢但不一定立刻死亡。
systemd-run --uid="$(id -u)" --gid="$(id -g)" \
--collect --unit=pack-once \
--property=MemoryMax=512M \
--property=MemoryHigh=400M \
tar -C /var/log -czf /tmp/logs-once.tgz .
–collect 让这个临时单元在结束后被清理,避免 list-units 里留下一串失败的 transient unit。–unit=pack-once 只是方便你在另一个终端里观察。如果同名单元还在,先执行 systemctl stop pack-once 再跑。排障时我通常两个限制都设,软限制大约是硬限制的八成,这样在被 OOM 之前能从变慢里看出来。
再加 CPU 配额
CPU 用 CPUQuota。写成 50% 表示最多用半个核的时间片,写成 200% 就是最多两个核。它限制的是时间,不是绑到哪颗核。如果还想避开某颗已经被中断打满的核,可以再加 AllowedCPUs。
systemd-run --collect --unit=cpu-once \
--property=CPUQuota=50% \
--property=MemoryMax=256M \
python3 -c 'import hashlib,os; d=os.urandom(1048576); print(hashlib.sha256(d).hexdigest())'
跑的同时另开一个终端看它是否真的被压住:
systemctl status cpu-once --no-pager
systemd-cgtop -n 2
systemctl status 里如果出现 CPU 配额相关属性,说明限制已经写进单元。systemd-cgtop 会按 cgroup 刷新 CPU 和内存,临时单元一般出现在 system.slice/cpu-once.service 下面。若状态一直是 running 但 CPU 百分比上不去,先确认这台机器没有另外部署 cpu 限流,例如容器运行时再套了一层配额。
结束后看记账
transient unit 默认会被回收,所以要在它还活着或者刚退出时把记账读出来。可以让命令先睡一会儿,把峰值读走再清理:
systemd-run --unit=keep-once \
--property=MemoryMax=256M \
--property=CPUQuota=30% \
/bin/sleep 20
systemctl show keep-once -p MemoryCurrent -p CPUUsageNSec -p ActiveState
systemctl stop keep-once
CPUUsageNSec 是该单元累计消耗的 CPU 时间,单位纳秒。拿它除以墙钟时间,就能估算实际占用了百分之多少的单核。如果 MemoryCurrent 在接近 MemoryMax 时命令被杀掉,systemctl status 里通常能看到 oom-kill 提示,dmesg 里也会有对应 cgroup 的记录。把这两处对上,才能确定是限额生效,而不是程序自己异常退出。
使用时要注意的几点
第一,systemd-run 默认把命令放进一个新的 service,工作目录和环境变量不会自动等于你当前的 shell。需要当前目录时加上 WorkingDirectory,需要代理时用 setenv 单独传入,不要把密钥写进命令行,进程列表里谁都看得见。
systemd-run --collect --unit=wd-once \
-p WorkingDirectory="$PWD" \
--setenv=LANG=C.UTF-8 \
--property=MemoryMax=256M \
./bin/check-once
第二,在容器里再套 systemd-run,取决于容器是否有自己的 systemd,以及 cgroup 是否可写。很多精简容器没有 systemd,这条办法只适用于云主机、裸金属,或者明确以 systemd 为 1 号进程的环境。
第三,限额是保护机器,不是保证任务成功。内存顶得太低,压缩或校验会失败;CPU 配额太低,超时时间要一起放宽。我的习惯是先用 MemoryHigh 做软限制跑一遍,确认峰值,再把 MemoryMax 设成峰值的大约 1.5 倍。
把只跑一次的重命令放进带限额的临时单元,比临时改全局限额再改回来更不容易误伤线上进程。下次登录机器做压缩、校验或一次性导入时,可以先用上面的 systemd-run 模板包一层。
- 点赞
- 收藏
- 关注作者
评论(0)