【云原生问题集】容器内存监控避坑:90%工程师踩过的“free命令雷区”
你有没有遇到过这种怪事压测跑得好好的容器突然被 OOM Kill 了。你赶紧进容器敲了个free -h一看内存快吃满了心想“资源不够加” 加完内存跑一会儿又被杀了。坑爹的是你明明在 K8s 里给容器设了内存上限 2Gi但free显示的却是宿主机的 64Gi 内存而且 used 那列的数字远超 2Gi读完后你能搞懂为什么容器里的free看到的是“假”内存页缓存Page Cache怎么把你给坑了内核回收页缓存的时间差为啥能杀人3 条命令精准看 cgroup 真实内存占用一、先捅破这层窗户纸free这命令在容器里就是“骗子”我亲自踩过这个坑。那会儿刚上容器化业务总在凌晨 OOM监控显示容器内存 1.8Gi上限 2Gi但进容器一查freeused 高达 50Gi宿主机的。关键点free读的是/proc/meminfo而/proc/meminfo从来都是节点的全局信息。不管你在不在容器里它都返回整台物理机/虚拟机的内存数据。# 进容器执行 $ free -h total used free shared buff/cache available Mem: 62Gi 28Gi 12Gi 1.2Gi 21Gi 31Gi62Gi total我容器明明只配了 2Gi 上限。这就离谱。正确姿势想看 cgroup 限制的真实内存用cat /sys/fs/cgroup/memory/memory.limit_in_bytescgroup v1或cat /sys/fs/cgroup/memory.maxcgroup v2。# cgroup v2常见于新系统 $ cat /sys/fs/cgroup/memory.max 2147483648 # 2Gi这才是真的上限二、页缓存那点破事它算使用量但能回收另一个常见误会free里的used包含了页缓存。页缓存是啥就是文件读写时内存里留的副本目的是加速 I/O。比如你容器里拷贝个大文件$ cp /data/bigfile /dev/null执行完你再free发现 used 蹭蹭涨。但这不是你程序泄漏内存是系统拿空闲内存做了缓存。内核的承诺当程序真需要内存时页缓存可以被回收。所以在 cgroup 统计里页缓存默认算作“可回收”的内存。但问题来了——回收需要时间。我遇到过线上事故一个 Java 应用突然申请 500Mi 内存而此时容器空闲内存只剩 200Mi剩下的 300Mi 全是页缓存。内核开始回收页缓存但 IO 压力大回收速度没跟上两三秒后 OOM Killer 直接就把 Java 进程给砍了。你猜怎么着过了半分钟页缓存回收完了内存又富裕了。进程白死了。三、亲手复现用一段代码把容器搞 OOM来跟着我做。这是个最小复现案例。前置条件Docker 已装最好有个测试环境别在生产搞。1. 起一个带内存限制的容器$ docker run -it --rm --memory512m --memory-swap512m alpine sh设置了 512Mi 硬上限。2. 看 cgroup 真实上限# 容器内执行 $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes 536870912 # 512Mi3. 模拟页缓存填满# 生成一个 400Mi 的文件 $ dd if/dev/zero of/tmp/bigfile bs1M count400 $ cat /tmp/bigfile /dev/null # 后台读刷页缓存4. 看内存占用两种视角的差异# 假视角 - free $ free -m total used free shared buff/cache available Mem: 62785 28000 30000 200 4785 32000 # 这 total 明显是宿主机的 # 真视角 - cgroup 统计 $ cat /sys/fs/cgroup/memory/memory.stat | grep -E ^(cache|rss|total_rss) cache 420000000 # 页缓存约 400Mi rss 51200000 # 常驻内存 50Mi真正的内存压力要看rss cache是否接近 limit。这里总占用约 450Mi离 512Mi 还差一点。5. 手动触发 OOM# 再申请 100Mi 匿名内存用 stress 或自己写个 malloc $ dd if/dev/zero of/dev/shm/oom bs1M count100如果在 cgroup 限制边缘内核会先回收页缓存。回收不过来就杀进程。你可以dmesg看内核日志$ dmesg | tail -20 [12345.678] Memory cgroup out of memory: Kill process 1234 (dd) score 1000 or sacrifice child四、怎么安全地看容器内存我推荐这 3 条命令命令1最稳cat /sys/fs/cgroup/memory/memory.stat 自己算$ cat /sys/fs/cgroup/memory/memory.stat | awk {if($1rss) rss$2; if($1cache) cache$2} END {print 真实使用量(MiB): (rsscache)/1024/1024}命令2K8s 用户kubectl top pod或kubectl top pod --containers$ kubectl top pod my-app NAME CPU(cores) MEMORY(bytes) my-app 150m 438Mi # 这个已经是扣除可回收页缓存的数值kubectl top拿的是 cgroup 的usage_in_bytes减去total_inactive_file不活跃页缓存比较贴近真实压力。命令3docker 用户docker stats --no-stream$ docker stats --no-stream CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O abc123 app 2.3% 438MiB / 512MiB 85.5% ...它和kubectl top逻辑类似已经帮你减掉可回收的 page cache。我个人不推荐用free做任何容器内存监控这命令在容器场景基本等于自欺欺人。五、常见坑明明内存没超容器还是挂了场景你设了 limit 2Gi监控显示 used 1.8Gi但容器 OOM 了。大概率原因页缓存 rss 超过 limit但监控工具只看了 rss。我见过挺多团队用ps的 RSS 累加或者top看 RES 列这些都不包含页缓存。而你容器里跑了个日志采集或者某个模块写临时文件页缓存悄悄涨到了 500Mi加上 1.8Gi 的 RSS 已经 2.3Gi 了内核当然要杀人。解决办法监控系统要拉 cgroup 的total_rss total_cache或者直接用container_memory_working_set_bytes指标Prometheus node_exporter 或 cAdvisor 都有。六、最后说几个实用建议都是血泪教训告警阈值别设 95%——页缓存回收需要时间留 20% buffer 吧设到 80% 就该扩容了。应用启动时预热会导致页缓存暴涨——比如 Java 类加载、Python 读一堆依赖文件。建议启动时预留额外内存或者等稳定了再看监控。/proc/meminfo在容器不可信——任何时候都别用。用 cgroup 接口。OOM 后留现场dmesg看内核日志docker inspect看退出码137 就是 OOM kill。顺便提一嘴cgroup v2 的文件路径和名字变了memory.limit_in_bytes改成memory.maxmemory.stat结构也略有调整。别在旧脚本上硬套。你遇到过因为看错free导致半夜起来加机器的经历吗评论区聊聊。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2605968.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!