朕喜技术(ZhenxiJS)
2026.08.28
1 分钟阅读
内存泄漏导致容器 OOM Killed(exit code 137)排查与修复实战
日期:2026-08-28 | 分支:
2026-08-28-0000(第4题) 技术栈:Python 3.11、Docker Compose、cgroup、psutil、OOM Killer 关键词:OOM Killed、内存泄漏、Memory Leak、exit code 137、cgroup memory limit、Out of Memory、全局缓存无限增长、LRU 淘汰、OrderedDict、PEP 668、externally-managed-environment、--break-system-packages
一、问题背景
Kubernetes 中部署的 Web 服务(容器内存限制 256MB),运行一段时间后进程神秘被杀:
❌ 容器重启了!进程被 OOM Killed (exit code 137)
- 服务刚启动正常,运行一段时间才崩溃,且崩溃间隔越来越短
- 代码里没有明显的文件/连接泄漏,CPU、磁盘、网络都正常
- 内核日志出现:
myservice invoked oom-killer/Out of memory: Killed process (python3)
这是非常经典的 内存泄漏(Memory Leak)导致容器 OOM Killed 的生产问题。
二、复现步骤
git checkout 2026-08-28-0000
cd scenario/2026-08-28-0000
docker compose up -d --build
docker compose exec app bash
# 查看内存限制
cat /sys/fs/cgroup/memory.max # cgroup v2(本环境为 v1)
cat /sys/fs/cgroup/memory/memory.limit_in_bytes # cgroup v1
# 运行泄漏脚本
python3 memory_leak.py
复现现象(内存持续攀升、永不回落):
初始内存: 11.4 MB
[t=2500] 缓存项: 50, 内存: 61.9 MB
[t=5000] 缓存项: 100, 内存: 111.9 MB
[t=7500] 缓存项: 150, 内存: 162.2 MB
[WARNING] 内存已达 200.3 MB,即将触发 OOM Killed...
[t=117500] 缓存项: 2350, 内存: 2370.9 MB ← 无限增长
三、根本原因
泄漏脚本 memory_leak.py 中存在长生命周期全局缓存:
_cache = {}
def process_request(request_id):
_cache[f"req_{request_id}"] = "A" * (1024*1024) # 1MB/请求
# 从不清理 → 无限增长
请求持续写入 _cache 但从不清理,导致:
- 进程 RSS 持续攀升,最终超过容器 cgroup 内存上限;
- 内核 OOM Killer 出手,杀掉进程(exit code 137);
- 崩溃间隔越来越短 = 缓存累积对象越多,内存增长越快。
四、解决办法
根因修复:给全局缓存加「容量上限 + LRU 淘汰」
from collections import OrderedDict
MAX_CACHE_SIZE = 50 # 内存有界的关键:限制条目数
_cache = OrderedDict()
def process_request(request_id):
if len(_cache) >= MAX_CACHE_SIZE:
_cache.popitem(last=False) # 满了淘汰最旧
_cache[f"req_{request_id}"] = "A"*(1024*1024)
_cache.move_to_end(f"req_{request_id}") # 标记最近使用
配套生产实践
- 用现成缓存库(
cachetools、Redis +maxmemory、functools.lru_cache),避免手写无限 dict。 - 容器设置
restart: on-failure自动重启;合理mem_limit+mem_reservation。 - 监控内存使用率,阈值建议 80%,在 OOM 前预警。
排查工具推荐
tracemalloc/objgraph/pympler定位泄漏对象与引用链psutil采样 RSS、ps aux --sort=-%mem- 定时打印
gc.get_objects()数量判断对象是否只增不减
五、验证
修复后运行脚本,缓存项稳定 50,内存稳定 ~63MB:
[t=50] 缓存项: 50, 内存: 61.9 MB
[t=500] 缓存项: 50, 内存: 63.6 MB
[t=1250] 缓存项: 50, 内存: 63.6 MB ← 平稳,不再增长
后台跑 12 秒以上,ps -o rss 显示 RSS 恒定 65064 KB(约 63.5MB),泄漏已修复,不再 OOM。
六、经验与避坑
- 全局缓存必须「有界」——这是内存泄漏最典型的隐藏源头。任何
dict/list只要长生命周期且只增不减,都会拖垮服务。 - 崩溃间隔越来越短 = 内存增长速度越来越快,这是内存泄漏的典型特征,与「峰值突发」不同。
- exit code 137(128+9,SIGKILL) 通常意味着被 OOM Killer 杀掉,
dmesg/kern.log能确认。 - PEP 668 坑:Debian bookworm(Python 3.11)下
pip3 install会报externally-managed-environment,需加--break-system-packages,或用 venv / pipx。 - 嵌套 Docker 的 cgroup 限制不生效:pipeline 容器内再起 docker 时,
docker inspect显示HostConfig.Memory正确,但容器内 cgroup 读到的是宿主机整机内存。此时观察「内存无限增长」即可稳定复现根因,不必纠结于精确到 256MB 的 OOM kill。
