服务器入坑指北:cgroup v2 内存上限与 OOM 取证

举报
Tulpartner 发表于 2026/11/03 08:47:50 2026/11/03
【摘要】 进程被杀了,却查不到是谁杀的;明明设了内存上限,服务还是被 OOM;软限也设了,可一点反应都没有;同一个控制组里的进程一起死掉,说不清为什么。 顺序是:先把压力算成比例 → 再看"杀过没有"的判据键 → 分清软限与硬限 → 最后看是谁占的。跳过前三步,你会把"压力大"直接当成"被杀过"。

进程被杀了,却查不到是谁杀的;明明设了内存上限,服务还是被 OOM;软限也设了,可一点反应都没有;同一个控制组里的进程一起死掉,说不清为什么。
顺序是:先把压力算成比例 → 再看"杀过没有"的判据键 → 分清软限与硬限 → 最后看是谁占的。跳过前三步,你会把"压力大"直接当成"被杀过"。
这篇给三段可直接粘贴的内容:第一段只读(算压力与软硬限对照)、第二段只读(取证:判据键与两类内存)、第三段含写操作(复现与读回,只在测试单元上做、都标回滚点)。取值用占位符,文件名、字段名与配置键照写。

压力是一个比例,不是绝对值

压力比例 = 当前用量 / 硬上限。
只看分子没有意义——同一个用量在不同上限下意义完全不同:memory.current 给的是当前用量,memory.max 给的是硬上限,而达到硬上限且内存无法回收时才触发 OOM。
算一遍:假设当前用量 3 GiB、硬上限 4 GiB,则 3 / 4 = 75%;同一个 3 GiB,硬上限是 3 GiB 时就是 3 / 3 = 100%(已经在门槛上),硬上限是 8 GiB 时只有 3 / 8 = 37.5%。分子没变,含义完全变了。
现读值 → 算式 → 结论(走一遍):

  1. 现读 memory.current 得 <用量>、memory.max 得 <上限>;
  2. 算式:<用量> / <上限> = 压力比例;
  3. 现读 memory.events 里 oom_kill 的计数 <计数>;
  4. 结论:比例高、oom_kill 不变 ⇒ 只是压力,不是故障;比例顶到 1、且 oom_kill 在递增 ⇒ 确实被 OOM 杀过。
    第 4 步之所以成立,是因为比例与判据键回答的是两个问题:一个说"挤不挤",一个说"有没有被杀"。两个都读,才敢下结论。

算压力块

cat /sys/fs/cgroup/<路径>/memory.current     # 分子:当前用量
cat /sys/fs/cgroup/<路径>/memory.max         # 分母:硬上限(达此值且无法回收才 OOM)
cat /sys/fs/cgroup/<路径>/memory.high        # 软限:超它只加大回收压力,不触发 OOM
cat /sys/fs/cgroup/<路径>/memory.swap.max    # 值为 0 表示本层禁用交换:压力无处可去
systemctl show <单元> -p MemoryMax           # 上层写法对应的现读生效值(不看配置文件原文)

判据:三个值分别意味着什么状态——

  • 用量低于软限:正常区间;
  • 用量超过软限但没到硬限:在节流,回收压力加大,不会杀进程;
  • 用量顶到硬限且无法回收:这才走到 OOM 那一步。
    memory.swap.max 读出来是 0,说明这一层禁用了交换——内存压力没有任何缓冲,顶到硬限就是硬碰硬。

例证:软限与硬限的分界,用同一份数据看两遍

在你去设上限之前,先把这条分界看清:超过软限只节流,不杀进程;只有到硬限且无法回收才触发 OOM。
拿同一份数据看两遍(设软限 3 GiB、硬限 4 GiB):

当前用量 与软限 3 GiB 比 与硬限 4 GiB 比 会发生什么
2 GiB 没到 没到 正常,2 / 3 ≈ 67%、2 / 4 = 50%
3.5 GiB 超了 没到 只节流:回收压力加大,不杀进程——3.5 / 4 = 87.5%
4 GiB 超了 到了 先尝试回收;回收不到才 OOM
判据:设了软限还指望它防 OOM,方向就错了——软限是"提前施压",硬限才是"最后一道"。

取证块:判据键只有一项

cat /sys/fs/cgroup/<路径>/memory.events        # 判据键:oom_kill 计数递增即发生过 OOM
cat /sys/fs/cgroup/<路径>/memory.events.local  # 本层专用入口:只看本层、不含子层级
cat /sys/fs/cgroup/<路径>/memory.stat          # 判据列:anon 与 file 两项看"谁占的"
systemd-cgtop                                  # 宿主级视角:按 cgroup 排出的内存占用列
dmesg -T                                       # 判据行:oom-kill 一类文本与时间点是否对齐

判据:

  • 只看 oom_kill 这一项是否递增——其余字段不参与这个判定;只看 dmesg 是不够的(它可能没记全),两者对齐才算同一次事件;
  • 两个入口回答不同问题:memory.events 含子层级,memory.events.local 只看本层——"我会不会被我自己吃掉"用后者,"我这棵子树整体被杀了多少次"用前者,混用会把别人的账算到自己头上;
  • 谁占的看 memory.stat 里 anon 与 file 的差值:匿名内存高说明是应用自己吃掉的,页缓存高说明是缓存撑起来的——这是"该加内存还是该调缓存"的分水岭。

复现与读回块

复现实验必须在测试单元上做,不在生产上做——这一点写在动作之前。

systemd-run --unit=<名字> -p MemoryMax=<值> <命令>   # 在测试单元里把压力推到上限附近(非生产)
cat /sys/fs/cgroup/<路径>/memory.events             # 读回:oom_kill 是否递增
cat /sys/fs/cgroup/<路径>/memory.oom.group          # 现读"整组一起杀"开关
cat /sys/fs/cgroup/<路径>/memory.current             # 读回:用量回到什么位置

"整组一起杀"是取舍:memory.oom.group 置 1 时,该控制组作为一个整体被处理(整组进程一起被杀)——好处是不会留下半个残废的服务(同组里有的活着、有的死了,状态最难收拾);代价是同组里无辜的进程一起被带走。两种取向下"用户看到什么"完全不同:一种是"服务整体重启",另一种是"服务半死不活"。
上层写法与内核文件同义:MemoryMax=(硬限)、MemoryHigh=(软限)、MemorySwapMax=(交换上限)、MemoryAccounting=(记账开关)。记账不开,上面这些读数全都拿不到——那时你会以为"没有上限",其实是"没有数"。
读回是独立的一步,不许省:改完要重读 memory.events 的判据键与 memory.current,确认比例下降、oom_kill 不再递增。改上限与改开关都属高风险写操作,需回滚点。

现象到动作的判定表

现象 结论 动作
进程被杀但查不到谁杀的 没看判据键 读 memory.events 的 oom_kill,再与 dmesg -T 对齐时间
设了软限仍被 OOM 把软限当硬限用了 记住分界:memory.high 只节流,memory.max 才是最后一道
比例高但事件键不动 只是压力,不是故障 继续观察,别按"被杀过"处理
同组一起死 memory.oom.group 置了 1 明确这个取舍是否符合预期,再决定留不留
读不到数 记账没开 看 MemoryAccounting= 的现读值

改完怎么验证:每项一条判据

  • 压力比例下降到目标(memory.current / memory.max);
  • oom_kill 不再递增;
  • 软限与硬限的取值关系符合预期(软限低于硬限);
  • 两类内存的构成能解释"谁占的"(anon 与 file);
  • 有回滚形态与原值记录。

这篇不解决什么

  • 讲内存泄漏的观察与处置顺序的那条线管泄漏的观察;本篇管上限与 OOM 取证——一个管"涨不涨"、一个管"什么时候被杀"。
  • 讲内存口径打架时信谁的那篇是多个内存口径之间的取舍;本篇是单个控制组内的压力与上限。
  • 讲容器退出后的现场保留的那篇提到容器侧的退出原因字段;本篇是宿主级视角的取证——两篇互相指路。

踩坑清单

  • 只看用量不看上限:没有分母,比例无从谈起。
  • 把软限当硬限用:它不杀进程,只提前施压。
  • 只看 dmesg 不看事件计数:可能没记全。
  • 把"含子层级"和"只看本层"混用:把别人的账算到自己头上。
  • 不分匿名内存与页缓存就断定"应用泄漏":可能是缓存撑起来的。
  • 记账没开却以为"没有上限":其实是读不到数。
  • 在生产上做复现实验:验证动作自己成了事故。
  • 打开整组被杀却没预期无辜进程被带走。
  • 只看配置文件原文不看现读生效值。
  • 改前不记原值:回滚没有依据。
  • 把 memory.high 设得比 memory.max 还高:分界形同虚设。
  • 只调上限不管交换上限:memory.swap.max 为 0 时压力全压在内存上。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。