模拟内存泄漏导致容器 OOM 的脚本

解读

面试官抛出这道题,并不是想看你“写一段能崩掉机器的代码”,而是考察三层能力:

  1. 能否用最小可控成本在容器内制造可观测、可复现的 OOM 场景;
  2. 是否熟悉 Linux 内存子系统与 Docker 资源隔离的交互点(cgroup v1/v2、oom_killer 策略);
  3. 能否把实验脚本做成可回滚、可度量、可嵌入 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 容器跑 cadvisornode-exporter,Grafana 模板直接出 OOM 时刻内存折线
  • 回滚策略--rm 自动清理、prlimit --as 二次限制,防止宿主机真宕机

答案

以下脚本在CentOS 7/8、Ubuntu 20.04、麒麟 V10验证通过,Docker 20.10+,cgroup v1/v2 自适应。
脚本分为三部分:

  1. 构建最小可执行镜像(基于国内阿里源,非 root 用户,加固);
  2. 运行容器并逐步泄漏内存,触发 OOM;
  3. 自动记录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
等保测试报告模板要求完全一致。

拓展思考

  1. 大页内存泄漏:把 leak.c 改成 mmap(MAP_HUGETLB),可绕过常规 cgroup memory 限制,验证宿主机是否开启 vm.nr_hugepages,这在央行容器云规范里属于必测风险项。
  2. Swap 未关闭场景:若宿主机开启 swap,-m 200m --memory-swap=300m 会导致容器先慢速 swap 而不被杀,此时需用 stress-ng --vm 1 --vm-bytes 250m --vm-keep 观察性能抖动,模拟线上 latency 突刺
  3. sidecar 采集 OOM 事件:用 falcokube-audit 监听 kernel.oom_kill 事件,直接发钉钉/飞书告警,满足国内7×24 运维值班要求。
  4. CI/CD 集成:在 GitLab CI 里加 job:oom-test,仅允许在夜间低峰跑,跑完后把 max_usage_in_byteslimit_in_bytes 比值推送到 Prometheus,比值>0.95即判定为“高占用基线”,可提前扩容或优化镜像。
  5. Rootless Docker:在麒麟 OS 政务桌面场景,普通公务员用户无 sudo,用 dockerd-rootless.sh 起服务,同样能复现 OOM,证明非特权容器也能被 kill,打消领导“rootless 绝对安全”的顾虑。