考量 system.slice 与内核所需的内存容量
文章背景与核心概要
在 Linux 系统管理中,利用 systemd 的内存控制功能(如 system.slice 的 MemoryMin 或 user.slice 的 MemoryMax)是防止系统因内存耗尽而崩溃的有效手段。然而,管理员在设定这些限制时,往往难以准确评估操作系统内核及系统服务所需的实际内存开销。
本文探讨了确定系统可用内存的复杂性,重点分析了内核开销、Slab 缓存及文件系统缓存对内存占用的影响。作者提出了一种基于 /proc/meminfo 动态计算内存限制的实践方法,并强调了通过监控工具(如 Prometheus)持续跟踪 cgroup 内存使用情况的重要性,以确保系统在限制资源的同时保持性能稳定。
系统内存预留的挑战
假设您希望使用 systemd 内存控制功能(作用于 system.slice 或 user.slice)来防止用户耗尽系统内存。无论哪种情况,您都需要预留一定量的内存给 system.slice(以及内核),这可以通过在 system.slice 上设置 MemoryMin=bytes,或者在 user.slice 上设置 MemoryMax=bytes 来实现。
Suppose you want to use systemd memory controls on either
system.sliceoruser.sliceto keep people from running the system out of memory. In either case, you need to come up with some amount of memory that you'll reserve forsystem.slice(and the kernel), through either settingMemoryMin=bytesonsystem.sliceor settingMemoryMax=bytesonuser.slice.
如果您要设置最大值,就需要知道系统拥有多少内存。正如 proc_meminfo(5) 所述,/proc/meminfo 中的 MemTotal 字段为您提供了在扣除保留位、内核基础代码及其他内存占用后,理论上系统可用的总 RAM。实际上,内核在正常运行中往往需要比这更多的 RAM,而且弄清楚内存的去向可能非常复杂。
If you're setting a maximum, you need to know how much memory the system has. As covered in
proc_meminfo(5),/proc/meminfo'sMemTotalfield gives you the total theoretically available system RAM after reserved bits and the kernel's very basic code and other memory usage. In practice, the kernel is likely to need more RAM than this in normal operation, and figuring out where the memory is going can be complicated.
此外,为了获得良好的性能,system.slice 下的所有内容很可能需要比单纯的程序内存占用更多的内存,因为 cgroup(进而 systemd)的内存限制包含了内核文件系统缓存等内容。
Also, it's likely that in order to perform well, everything under
system.slicewill need more memory than simply the total program memory usage, because cgroup and thus systemd memory limits include things like the kernel filesystem cache.我之前遇到的 CPU 和 RAM 限制过小严重损害程序性能的情况,肯定部分是因为 RAM 太少,也可能部分是因为阻碍了文件系统缓存。(来源)
My case of too-small CPU and RAM limits significantly harming program performance was definitely partly from having too little RAM, probably partly by hampering the filesystem cache. (Source)
评估实际内存使用情况
目前,在我所关注的服务器上,system.slice 似乎使用了约 1.6 GB 的用户级 RAM,以及高达 2 GB 以上的内核 Slab 缓存。遗憾的是,我没有这些指标随时间变化的记录(是的,我现在很想构建一个系统来收集它们)。
At the moment,
system.sliceon the servers I care about for this appears to be using about 1.6 GB of user-level RAM and anywhere up to just over 2 GB of kernel slab cache. Unfortunately, I don't have metrics over time for this (yes, I'm now tempted to build something to collect them).
文件系统缓存的使用情况波动很大,但我认为至少应该为此预留 1 GB,如果能更多则更好。我可以将其四舍五入,认为我们应该为广义上的“系统”预留 5 GB,并据此在 user.slice 上设置 MemoryMax=(比起尝试为 system.slice 设置最小内存,我更倾向于限制 user.slice 的内存使用量)。
Filesystem cache usage is all over the map, but I should probably allow at least 1 GB for that, and more might be better. I could round this up and say we should reserve 5 GB for the "system," broadly defined, and set
MemoryMax=onuser.sliceaccordingly (I prefer the approach of cappinguser.slice's memory usage to trying to set a minimum memory forsystem.slice).
(事实证明,这在我们大多数机器的 system.slice 上是典型的配置,不太活跃的机器使用量不到 1G,有些甚至不到 512M。当然,system.slice 的内核文件缓存大小差异巨大,而且我们的一些机器属于例外,因为它们运行着重量级的系统 .service 单元,例如我们的指标系统。)
(This turns out to actually be typical for
system.sliceacross most of our machines, with less active machines having under 1G and some under 512M. The kernel file cache size forsystem.slicevaries hugely, of course, and some of our machines are exceptions because they run heavyweight things as system.serviceunits, such as our metrics system.)
实现与动态调整
我需要一个脚本来根据 /proc/meminfo 中的 MemTotal 动态计算这个大小,但这并不特别困难(尤其是因为我有一个可以参考的脚本片段;它被用于根据文件服务器拥有的内存量来调整我们 ZFS 文件服务器上的 ZFS ARC 大小)。
I would need a script to dynamically compute this size from
MemTotalin/proc/meminfo, but that's not particularly difficult (especially since I have a script to copy chunks from; it's used to size the ZFS ARC on our ZFS fileservers based on the amount of memory the fileserver has).
我们现有的在用户登录时进行公平份额 CPU 调度的系统,可以在此时计算出正确的 MemoryMax 值并将其设置在 user.slice 上(因为当前登录用户的 user-<uid>.slice 作为其子单元已经存在)。在我们当前的脚本框架中,甚至有一个方便的地方可以放置此逻辑。
Our existing system for setting up fair-share CPU scheduling between people runs at login time, when we know a
user.sliceexists (because auser-<uid>.slicefor the person who's logging in now exists as its child), so we can compute the rightMemoryMaxvalue and set it onuser.slice. There's even a convenient place to put this in our current script framework.
监控与验证
然而,这绝对是那种在实施后需要进行监控的变更,以确保我对所有内容实际所需和使用的内存量的判断是正确的(参见 意外的变化是一个信号)。这意味着我需要对顶级 systemd cgroup 的内存使用情况进行一些基本的监控,并将其输入到我们的指标系统中,我可能会用一个极简的 shell 脚本来完成,而不是使用更复杂的方案。
However, this is definitely the sort of change that I should monitor after it's made to make sure that I'm correct about how much memory everything actually needs and uses (cf. Surprise Changes Are A Signal). That means I need some basic monitoring of top-level systemd cgroup memory usage that feeds into our metrics system, which I'll probably do with a minimal shell script rather than anything more elaborate.
(至少有一个 Prometheus 的 systemd 导出器可以提供 systemd 单元的指标,但我上次查看时,还没有任何工具能提供这种类型的内存使用信息。)
(There's at least one Prometheus systemd exporter that provides metrics for systemd units, but the last time I looked there was nothing that provided this kind of memory usage information.)