Docker 容器内存泄漏排查:从 OOMKilled 到根因定位


上周一位读者遇到个头疼的问题:他的 VPS 上跑了 5 个 Docker 容器,服务器 4G 内存,莫名其妙就触发 OOM(Out of Memory),系统直接杀掉容器进程。更诡异的是,他明明给每个容器都加了内存限制。今天把整个排查过程复盘一遍,教你遇到 OOM 时如何快速定位根因。

img

一、先确认:到底是谁在吃掉内存

OOM 发生时,系统日志里会有记录。先看 dmesg:

dmesg | grep -i "killed process"

输出类似这样:

Out of memory: Killed process 12345 (java) total-vm:2.5GB, anon-rss:1.8GB

这里能看到被杀死的是 PID 12345 的 Java 进程,用了 1.8G 实际内存。

但 Docker 容器里的进程 PID 是宿主机上的,怎么对应到具体容器?用这个命令:

# 查看所有容器的内存使用
docker stats --no-stream

# 输出示例
CONTAINER ID   NAME        CPU %   MEM USAGE / LIMIT   MEM %
abc123def456   web-app     12.5%   1.8GiB / 2GiB       90.0%
def789ghi012   redis       0.3%    512MiB / 512MiB     100.1%

如果 MEM % 接近或超过 100%,说明这个容器触发了内存限制,但问题可能不止于此。

二、深入排查:容器内到底什么在占内存

进入容器内部,用 top 或 ps 看进程内存:

docker exec -it web-app /bin/sh
top -o %MEM

如果发现某个进程内存持续增长,可能是内存泄漏。但 Docker 容器里还有个隐藏杀手——缓存和缓冲区。

容器内的 free -m 看到的内存是宿主机的,不准确。正确做法是在宿主机上看:

# 查看容器 cgroup 内存统计
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.stat

# 关键指标
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes

重点关注 cache 和 rss(常驻内存)。如果 cache 很大,说明是文件缓存占用了内存,加 --memory-swap 限制可能有效。

三、常见内存泄漏场景与修复

场景 1:Java 应用堆内存设置过大

很多 Java 容器启动参数里 -Xmx 设得比容器内存限制还大:

# 错误示例:容器限制 1G,但 JVM 堆设了 1.5G
docker run -m 1g my-java-app -Xmx1536m

JVM 不知道自己在容器里,会按宿主机的内存来分配。正确做法是用 -XX:+UseContainerSupport(Java 8u191+ 默认开启)或显式设置:

docker run -m 1g -e JAVA_OPTS="-XX:MaxRAMPercentage=75.0" my-java-app

这样 JVM 会自动按容器内存的 75% 设置堆上限。

场景 2:Nginx/PHP-FPM 进程数失控

PHP-FPM 的 pm.max_children 如果设得太大,并发一上来内存就爆:

; php-fpm.conf
pm = dynamic
pm.max_children = 50      ; 如果每个子进程 50MB,50 个就是 2.5G
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10

根据容器内存调整:

pm.max_children = 10      ; 2G 内存的容器,10 个子进程比较安全

场景 3:Redis 没有设置 maxmemory

Redis 默认会吃掉所有可用内存:

# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru  # 内存满了淘汰最近最少使用的 key

场景 4:日志文件在容器内无限增长

如果应用把日志写到容器内的文件系统,而没做切割,日志文件会撑爆容器存储层(虽然存储层和内存是分开的,但某些情况下会影响整体性能):

# 更好的做法:日志输出到 stdout/stderr,让 Docker 管理
# 或者挂载宿主机目录,配合 logrotate
docker run -v /var/log/myapp:/app/logs my-app

四、给容器加正确的内存限制

很多人只加 -m,但忽略了 swap:

# 只限制内存,不限制 swap(容器可能用 swap 规避限制)
docker run -m 512m my-app

# 正确做法:内存和 swap 一起限制
docker run -m 512m --memory-swap 512m my-app  # 禁用 swap
# 或
docker run -m 512m --memory-swap 768m my-app  # 允许 256m swap

在 docker-compose.yml 中:

services:
  web:
    image: my-app
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

五、监控与告警

亡羊补牢不如未雨绸缪,加个简单的监控脚本:

#!/bin/bash
# /usr/local/bin/docker-memory-check.sh
THRESHOLD=90
ALERT_WEBHOOK="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY"

docker stats --no-stream --format "{{.Name}}\t{{.MemPerc}}" | while read name mem; do
    mem_num=$(echo $mem | sed 's/%//')
    if (( $(echo "$mem_num > $THRESHOLD" | bc -l) )); then
        curl -s -X POST $ALERT_WEBHOOK \
            -H "Content-Type: application/json" \
            -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"告警:容器 $name 内存使用率 $mem_num%,请检查!\"}}"
    fi
done

加到 crontab 每 5 分钟执行一次:

*/5 * * * * /usr/local/bin/docker-memory-check.sh

六、总结

Docker OOM 排查三步走:

  1. 看日志:dmesg + docker stats 定位肇事容器
  2. 进容器:top/ps 找内存大户,检查应用配置(JVM、PHP-FPM、Redis)
  3. 加限制:-m 和 --memory-swap 一起用,配合监控告警

内存泄漏不可怕,可怕的是不知道怎么查。收藏这篇,下次遇到 OOM 不慌张。



手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

你的 SSH 真的安全吗?7 个加固步骤让暴力破解彻底失效

安全资讯: Zapscape (CVE‑2026‑64561) KVM虚拟化逃逸漏洞,PoC已公开

评 论
更换验证码