容器化后内存告警不断?日志“写爆”了Page Cache!
服务在虚拟机上跑得好好的,容器化上 K8s 后却频繁触发内存超限告警,甚至 OOM Kill?
堆内存才用了不到 10%,Pod 内存却飙到 80% 以上,到底是什么情况?
本文将层层拆解,揭开容器内存暴涨的真相。
诡异的现象:JVM 很健康,Pod 却要“撑爆”了
Java 服务容器化部署到 Kubernetes 后,Pod 内存使用率持续缓慢上升,频繁超过 80%,甚至触发 OOM 导致 Pod 重启。
资源配置如下:
1 | resources: |
按理说,JVM 堆内存只占 2000Mi,Pod limit 给了 3000Mi,还有近 1G 的空间给堆外、Metaspace 等,应该足够富裕。可为什么内存会超限呢?
排查过程:堆内存正常,堆外也正常,那内存去哪了?
首先排查 JVM 堆内存
进入容器,用 jhsdb jmap --heap 查看堆使用情况:
1 | Heap Usage: |
堆内存仅用了 160MB,占比 8%,完全正常。GC 也健康,没有内存泄漏迹象。
排查堆外内存
jstat -gc 显示 Metaspace 使用很小,代码中也没有 DirectByteBuffer 等直接内存操作。堆外内存嫌疑暂时排除。
排查 Pod 整体内存
查看 cgroup 的内存统计文件
1 | cat /sys/fs/cgroup/memory/memory.stat |
关键输出:
1 | cache 741126144 # 706 MB |
Pod 实际物理内存 RSS 约 2.28GB,而 cache(页缓存)竟然占用了 706MB!
Pod 内存总量 = RSS + cache ≈ 3GB,已经接近 3GB 的 limit,所以触发告警。
根源分析:谁制造了这么多 cache?
容器中日志先写入本地文件,再由采集器(SLS)读取发送。
Linux 的 Page Cache 用来缓存文件读写,频繁的文件写入和读取,导致大量文件数据被缓存在 Page Cache 中,而这些 cache 会计入容器的内存使用(cgroup v1 行为),最终撑爆 Pod 内存。
关键点:
宿主机内存充足,内核不会主动回收 Page Cache。
容器内无法执行
drop_caches(只读文件系统)。即使 RSS 只有 2.3GB,加上 0.7GB cache 后刚好超限,引发 OOM。
我们尝试在宿主机上清空 cache(echo 3 > /proc/sys/vm/drop_caches),Pod 内存瞬间下降 700MB,证实了 cache 就是元凶。
解决方案:三种方法根治“cache 暴涨”
日志直接输出到标准输出(最佳实践)
原理:应用日志不写本地文件,直接 console 输出,由容器运行时(docker/containerd)捕获并转发给日志中心(如 SLS、ELK)。
优点:
- 完全避免 Page Cache 产生,内存占用只来自 RSS。
- 符合云原生 12 要素,K8s 原生支持。
- 无需改造日志采集系统。
保留本地日志时的缓解措施
如果因合规要求必须保留日志文件,可采用以下方法:
- 优化日志策略:异步写入、调高日志级别、限制日志量。
- 调整内核参数(宿主机操作,会影响所有POD慎重操作,不建议):
1
2
3sysctl -w vm.vfs_cache_pressure=200 # 积极回收 cache
sysctl -w vm.dirty_ratio=10 # 降低脏页比例
sysctl -w vm.dirty_background_ratio=5
启用 cgroup v2(K8s 1.20+ 推荐)
cgroup v2 改进了内存回收机制,当 Pod 内存超过 memory.high(默认 limit 的 80%)时,内核会主动回收 Page Cache,无需人工干预。
K8s 集群启用 cgroup v2 后,只需配置 Pod 的 limits.memory,kubelet 自动设置 memory.high,即可自动抑制 cache 增长。
检查是否生效:
1 | cat /sys/fs/cgroup/memory.high # 应该等于 limit*0.8 |
容器化不是简单的“打包扔上去”,内存管理涉及 JVM、cgroup、内核缓存三层。希望本文的排查思路和解决方案能帮你避开“日志写爆 cache”的大坑。






