为什么我们的某个系统遇到了极慢的 systemd 会话启动问题
文章背景与核心概要
在服务器运维过程中,合理的资源限制本该保护系统免受用户误操作的影响,但有时过严的策略却会引发隐蔽的性能陷阱。本文记录了一起因过于严格的资源限制导致服务器系统响应迟钝、systemd 用户会话启动极其缓慢的故障排查过程。
文章的核心原因在于通过 systemd 和 pam_exec 应用了极其严格的 CPU 和内存约束,并借助 --runtime 标志将这些限制写入了 /run 目录。由于这些配置在用户登出后依然残留在 /run/systemd/system.control/ 中,当用户再次登录、系统重建 user-<uid>.slice 时,这些限制会从初始化阶段就开始生效,从而严重拖慢了 systemd 会话初始化以及日常管理命令的执行。通过适当放宽限制并理解 --runtime 的持久化特性,该性能问题得到了圆满解决。
调查过程
The Investigation
此前我注意到,在我们的某台服务器上,用户会话设置期间 systemd 加载用户单元(user units)竟然需要三秒钟的时间,而且尽管 I/O 指标完全正常,系统整体却表现出莫名的迟钝。
Previously, I noted how systemd took three seconds to load user units during session setup on one of our servers, alongside unexplained general system slowness despite healthy I/O metrics.
这台特定的机器是我们 SLURM 集群的头节点(主服务器)。由于历史原因,用户通常会直接登录这里来提交作业,这导致我们经常遇到用户不小心在该节点上直接运行消耗大量 CPU 或内存的准备任务。为了劝阻这种行为,我们在 user-<uid>.slice 单元上实施了非常严格的 基于 systemd 的 CPU 和 内存限制:128 MB RAM 以及单个 CPU 的 25%。在 Ubuntu 26.04 上,这些限制严苛到让像 dpkg -S 这样的常规命令都变得慢得明显。
This specific machine is the head node (master server) of our SLURM cluster. Because users historically logged in here to submit jobs, we faced ongoing issues with people accidentally running heavy CPU or memory-consuming preparation tasks directly on the node. To discourage this, we implemented very strict systemd-based CPU and memory limits on
user-<uid>.sliceunits: 128 MB of RAM and 25% of a single CPU. On Ubuntu 26.04, these limits proved restrictive enough to make routine commands likedpkg -Snoticeably slow.
意料之外的罪魁祸首:持久化的运行时限制
The Unexpected Culprit: Persistent Runtime Limits
虽然这些资源限制很容易解释登录后 dpkg 等命令变慢的原因,但起初它们似乎与 systemd 会话的设置并没有关系。这些限制是通过 pam_exec 在 pam_systemd 构建用户会话之后应用的。此外,user-<uid>.slice 单元在用户登出后理应完全消失。
While the resource limits easily explained the slowness of commands like
dpkgpost-login, they initially seemed unrelated to systemd session setup. These limits are applied viapam_execafterpam_systemdconstructs the user session. Furthermore,user-<uid>.sliceunits are supposed to disappear entirely upon logout.
然而,我发现我错了。
However, I discovered I was mistaken.
我们使用以下命令设置针对每个用户的限制:
We set per-user limits using:
systemctl --runtime set-property user-<uid>.slice ...
正如文档所述,--runtime 标志不仅会修改内存中正在运行的单元,它还会将持久配置文件写入 /run/systemd/system.control/<unit>.d/,这些文件将一直保留到下一次重启。当 systemd 随后为再次登录的用户重新创建 user-<uid>.slice 时,这些 /run 中的文件会导致限制条件从一开始就被重新应用。因此,systemd 用户会话进程受到了极大的约束,从而大幅变慢。
As documented, the
--runtimeflag does not merely modify the running unit in memory; it writes persistent configuration files to/run/systemd/system.control/<unit>.d/, which persist until the next reboot. When systemd subsequently recreated auser-<uid>.slicefor a returning user, those/runfiles caused the restrictive limits to be re-applied right from the start. Consequently, the systemd user session process was heavily constrained and drastically slowed down.
将限制提高到更合理的数值完全解决了会话启动迟钝的问题。
Raising the limits to more reasonable values completely resolved the sluggish session startups.
核心要点
Key Takeaways
/run中的持久性:--runtime标志写入的文件其生命周期会超出单独的用户会话。在登录时停止应用限制,并不能清除过去会话中建立的限制。要撤销它们,必须显式清除或重启系统。- 重置 Cgroup v2 限制: Systemd 可能会在内存中缓存状态。要完全清除限制,可以通过配置参数将其重置:
- 将
CPUQuotaPerSecUSec设置为infinity - 将
MemoryMax和/或MemoryHigh设置为infinity - 将
TaskMax设置为适当的大系统默认值
- Persistence in
/run: The--runtimeflag writes files that outlive individual user sessions. Stopping the application of limits at login time does not clear limits established during past sessions. To undo them, you must explicitly clear them or reboot.- Resetting Cgroup v2 Limits: Systemd may cache state in memory. To completely clear limits, you can reset them via configuration parameters:
- Set
CPUQuotaPerSecUSectoinfinity- Set
MemoryMaxand/orMemoryHightoinfinity- Set
TaskMaxto an appropriate large system default