容器内存看着够用却 OOM?先别信 free,去读 cgroup
先说结论
容器里 free -h 显示的不是你能用的内存,而是宿主机的内存。真实上限在 cgroup 的 memory.max 文件里。判断"这个任务能不能跑"之前,先去读那个文件,而不是看 free。
一、为什么会误判
free 读的是 /proc/meminfo,nproc 读的是 /proc/cpuinfo。容器(namespace)默认不隔离这两个文件,除非额外挂了 lxcfs 之类的工具去伪造。
所以会看到很荒诞的场景:free -h 报几百 GB 内存、几十个核,实际配额只有几十 GB、十几个核。程序按"还剩一大半空闲"去规划内存,然后在看起来最安全的时候被 OOM kill。
二、三个必读文件(cgroup v2)
文件 | 含义
/sys/fs/cgroup/memory.max | 硬上限,写着 max 表示不限
/sys/fs/cgroup/memory.current | 当前用量(包含 page cache)
/sys/fs/cgroup/memory.events | oom / oom_kill 计数
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
grep -E '^(oom|oom_kill)' /sys/fs/cgroup/memory.events
cgroup v1 的老机器路径不一样:/sys/fs/cgroup/memory/memory.limit_in_bytes。判断版本看 stat -fc %T /sys/fs/cgroup,返回 cgroup2fs 就是 v2。
三、memory.current 高不等于要 OOM
memory.current 把 page cache 也算进去了。跑完一次大文件读操作,current 可能贴着 max,但只要内存可回收,内核会自己腾出来。
要判断"真实占用",去 memory.stat 里看:
grep -E '^(file|inactive_file|anon) ' /sys/fs/cgroup/memory.stat
粗略换算:实际匿名内存 ≈ anon,可回收缓存 ≈ file。真正会把你顶到 OOM 的是 anon 部分。
四、CPU 同理,也在 cgroup 里
cat /sys/fs/cgroup/cpu.max
两个数字:配额 周期。比如 1500000 100000 表示每 100ms 周期里有 1.5 秒 CPU 时间,也就是 15 核。写 max 100000 才是不限。
五、OOM 之后怎么确认到底是谁杀的
1. 退出码 137 = 128 + SIGKILL,被强杀的典型特征(SIGKILL 的编号是 9,128+9=137)
2. memory.events 里的 oom_kill 计数增加了
3. dmesg | grep -i oom —— 真正的 kill 日志在宿主机侧,容器内经常看不到
4. 平台控制台可能只回一句"内存溢出,请检查代码或增加配置",不给任何数字
所以关键是自己提前算好峰值,别等平台来告诉你。
六、算峰值再决定跑不跑
- 先量小的:拿数据量缩到 1/10 的用例跑一遍,记录峰值
- 量两个数:
/usr/bin/time -v里的 Maximum resident set size,或者采样memory.current - 再外推:按数据量线性放大,留 30% 余量
- 先看常驻:如果"任务峰值 + 常驻服务占用"已经接近
memory.max,正确做法是先停常驻服务再跑,而不是赌调度运气
七、一个高频踩法
压测脚本自己读 free 判断"还有几百 GB 空闲",据此决定并发数,结果一开满就被 kill——因为它读的那份数据是假的。
改法有两种。第一种,让脚本自己算真实余量:
LIMIT=$(cat /sys/fs/cgroup/memory.max)
CUR=$(cat /sys/fs/cgroup/memory.current)
AVAIL=$(( (LIMIT - CUR) / 1024 / 1024 ))
echo "真实可用约 $AVAIL MB"
第二种更省事:把上限当参数从外部传进来,脚本不再自己探测。
小结
容器里别信 free 和 nproc;memory.max 才是权威;判断 OOM 靠退出码 137 + memory.events + 宿主机 dmesg。算清峰值再跑,比跑崩了再猜便宜得多。
- 点赞
- 收藏
- 关注作者
评论(0)