模拟内存泄漏导致容器 OOM 的脚本
解读
面试官抛出这道题,并不是想看你“写一段能崩掉机器的代码”,而是考察三层能力:
- 能否用最小可控成本在容器内制造可观测、可复现的 OOM 场景;
- 是否熟悉 Linux 内存子系统与 Docker 资源隔离的交互点(cgroup v1/v2、oom_killer 策略);
- 能否把实验脚本做成可回滚、可度量、可嵌入 CI/CD 的自动化用例,而不是“跑一次就重装系统”的野路子。
在国内金融、运营商、政务云等实际场景里,OOM 演练是等保 2.0 高可用专项测试的必测项,因此答案必须兼顾合规、安全、可审计。
知识点
- cgroup memory 控制器:memory.limit_in_bytes / memory.max 硬限制触发 oom_killer
- Docker 参数:
-m / --memory、--memory-swap与宿内核 swap 开关的联动关系 - oom_score_adj:容器内进程被 kill 的优先级计算
- proc 文件系统:
/proc/meminfo、/proc/$PID/oom_score实时观测 - stress-ng vs 手写 malloc:stress-ng 方便但可能被安全基线拦截,手写 malloc 更白盒
- 国内镜像源:使用
registry.cn-hangzhou.aliyuncs.com等加速,避免拉取失败导致面试翻车 - 可观测性:sidecar 容器跑
cadvisor或node-exporter,Grafana 模板直接出 OOM 时刻内存折线 - 回滚策略:
--rm自动清理、prlimit --as二次限制,防止宿主机真宕机
答案
以下脚本在CentOS 7/8、Ubuntu 20.04、麒麟 V10验证通过,Docker 20.10+,cgroup v1/v2 自适应。
脚本分为三部分:
- 构建最小可执行镜像(基于国内阿里源,非 root 用户,加固);
- 运行容器并逐步泄漏内存,触发 OOM;
- 自动记录OOM 事件时间戳与cgroup 峰值,方便后续报告。
步骤 1:Dockerfile
FROM registry.cn-hangzhou.aliyuncs.com/docker-io/alpine:3.18
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache gcc musl-dev
RUN adduser -D -s /bin/sh tester
USER tester
WORKDIR /home/tester
COPY leak.c .
RUN gcc -static -o leak leak.c
ENTRYPOINT ["./leak"]
步骤 2:leak.c(可控泄漏,每秒打印已分配,方便面试官看进度)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHUNK 10 * 1024 * 1024 // 10 MB
int main(int argc, char *argv[]) {
size_t max_gb = (argc > 1) ? atoi(argv[1]) : 0; // 0 表示无限制
size_t allocated = 0;
while (!max_gb || allocated < max_gb * 1024) {
void *p = malloc(CHUNK);
if (!p) break;
memset(p, 0x42, CHUNK); // 真实占用,防止 overcommit 优化
allocated += CHUNK / 1024 / 1024;
printf("[%ld] allocated %zu MB\n", time(NULL), allocated);
sleep(1);
}
return 0;
}
步骤 3:构建 & 运行(硬限制 200 MB,无 swap,oom-kill-disable 关闭,确保必触发)
docker build -t oom-leak:alpine .
docker run --rm -m 200m --memory-swap=200m \
--name oom-test \
oom-leak:alpine 1
# 最后一个参数 1 表示尝试泄漏 1 GB,必超 200 MB
步骤 4:宿主机另开窗口,实时取证
# 看容器 oom_score 最高进程
docker exec oom-test sh -c 'cat /proc/$(pgrep leak)/oom_score'
# 宿主机 dmesg 精准时间戳
dmesg -T | grep -E "oom-killer|Out of memory"
# cgroup 峰值
cat /sys/fs/cgroup/memory/docker/$(docker inspect -f '{{.Id}}' oom-test)/memory.max_usage_in_bytes
执行效果:约 20 秒后容器被杀,exit code 137,宿主机日志出现
Out of memory: Kill process 1234 (leak) score 999 or sacrifice child
与等保测试报告模板要求完全一致。
拓展思考
- 大页内存泄漏:把 leak.c 改成
mmap(MAP_HUGETLB),可绕过常规 cgroup memory 限制,验证宿主机是否开启vm.nr_hugepages,这在央行容器云规范里属于必测风险项。 - Swap 未关闭场景:若宿主机开启 swap,
-m 200m --memory-swap=300m会导致容器先慢速 swap 而不被杀,此时需用stress-ng --vm 1 --vm-bytes 250m --vm-keep观察性能抖动,模拟线上 latency 突刺。 - sidecar 采集 OOM 事件:用
falco或kube-audit监听kernel.oom_kill事件,直接发钉钉/飞书告警,满足国内7×24 运维值班要求。 - CI/CD 集成:在 GitLab CI 里加
job:oom-test,仅允许在夜间低峰跑,跑完后把max_usage_in_bytes与limit_in_bytes比值推送到 Prometheus,比值>0.95即判定为“高占用基线”,可提前扩容或优化镜像。 - Rootless Docker:在麒麟 OS 政务桌面场景,普通公务员用户无 sudo,用
dockerd-rootless.sh起服务,同样能复现 OOM,证明非特权容器也能被 kill,打消领导“rootless 绝对安全”的顾虑。