docker inspect、memory.events 和 dmesg,把“被 OOM 杀”与普通 SIGKILL 区分开。隔离边界:只在一台一次性 Linux 虚拟机或本地 Docker Desktop 上做,所有容器都加 --memory 与 --memory-swap,不要调整宿主机内存、不要关闭 swap、不要对生产节点执行 stress。实验结束删除容器即可,不会改动宿主机内核参数。
需要 Docker 和 python:3.12-alpine 镜像,预留约 300 MB 磁盘。
docker version --format '{{.Server.Version}}'
docker info --format 'cgroup={{.CgroupVersion}} driver={{.Driver}}'
docker pull python:3.12-alpine
确认 cgroup=2。cgroup v1 的路径是 /sys/fs/cgroup/memory/memory.oom_control,本页以 v2 的 memory.events 为准。
启动一个带硬限制的容器,主进程只做一次大块内存分配。分配量远大于 64 MiB,且关闭 swap 配额,确保触发的是本容器 cgroup 的 OOM,而不是宿主机内存压力:
docker run -d --name lab-oom --memory=64m --memory-swap=64m \
python:3.12-alpine \
python3 -c 'a = bytearray(256 * 1024 * 1024); import time; time.sleep(300)'
sleep 5
docker ps -a --filter name=lab-oom
--memory-swap 与 --memory 相等表示容器不能使用 swap。sleep 5 给内核留出统计和回收时间;如果没有退出,用 timeout 30 docker logs -f lab-oom 观察,不要直接加大到宿主机级别。
docker inspect -f 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' lab-oom
timeout 5 docker events --since 5m --filter container=lab-oom --filter event=oom
docker logs --tail=20 lab-oom
预期 OOMKilled=true、ExitCode=137。137 = 128 + 9,只说明进程收到 SIGKILL;只有 OOMKilled=true 或 cgroup 的 oom_kill 计数增加,才能把这次退出归因于内存超限。
再看 cgroup 计数。重新起一个常驻容器,让分配进程在容器内被 kill,而 PID 1 继续存活,便于读取计数:
docker run -d --name lab-oom-events --memory=64m --memory-swap=64m \
python:3.12-alpine sleep 300
docker exec lab-oom-events python3 -c 'a = bytearray(256 * 1024 * 1024)'
docker exec lab-oom-events cat /sys/fs/cgroup/memory.events
docker exec lab-oom-events cat /sys/fs/cgroup/memory.current
预期 memory.events 中出现 oom 1 与 oom_kill 1,memory.current 在 64 MiB 上限附近。若 oom_kill 为 0,说明分配被 Python 的 MemoryError 或 overcommit 拦截,并没有真正触发 cgroup OOM。
有宿主机 root 时,可在一次性虚拟机上核对内核日志:
sudo dmesg -T | grep -iE 'oom|killed process' | tail -10
预期能看到 Memory cgroup out of memory: Killed process ... (python3)。容器内通常看不到宿主机 dmesg,不要因为容器里没有该日志就否定 OOM。
kill -9。假设不同,取证位置不同。OOMKilled 与 exit code。OOMKilled=true 才指向内存;exit 137 单独出现可能来自 docker kill、Kubernetes 探针失败、节点驱逐或人为 kill -9。memory.events 的 oom_kill 是否增长,以及 memory.current、memory.peak 是否贴近 memory.max。--memory-swap。只设 --memory 时容器可能使用 swap,内存增长被掩盖,表现为延迟抖动而不是立即 OOM。MemoryError、OutOfMemoryError 或 runtime OOM,不等于被 cgroup OOM kill。前者是进程内部申请失败,后者是内核杀进程,二者日志和计数完全不同。docker inspect 的 OOMKilled 只反映最近一次退出;要看 RestartCount 和事件历史,避免把旧结论套到新实例上。演练环境直接删除容器;生产环境按“限制、观测、修复”顺序处理:
docker rm -f lab-oom lab-oom-events
真实服务应先确认内存增长曲线和 limit 是否匹配:
docker run -d --name lab-oom-fixed --memory=256m --memory-swap=256m \
python:3.12-alpine \
python3 -c 'a = bytearray(64 * 1024 * 1024); import time; time.sleep(30)'
sleep 3
docker inspect -f 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' lab-oom-fixed
docker stats --no-stream lab-oom-fixed
OOMKilled=false 且进程正常退出即表示限制与负载匹配。生产中还应检查泄漏、缓存上限、JVM heap 与容器 limit 的比例,并给 container_memory_working_set_bytes、OOM 计数配告警。
docker rm -f lab-oom lab-oom-events lab-oom-fixed 2>/dev/null || true
本页没有修改宿主机配置和内核参数,不需要回滚系统设置。不要用 docker system prune -a 清理,以免误删其他实验镜像。
kill -9?OOMKilled=true 与 exit code 137 是什么关系?memory.events 中的 oom_kill 为 0,但进程退出了,可能是什么原因?