3、容器内存限制(--memory)归纳总结
容器的内存限制,说白了就是给进程画一条红线——你最多只能用这么多,越线就出局。刚接触 Docker 的时候,我总觉得不设限制也没事,直到有一次线上服务把宿主机内存吃光,整台机器的容器一起崩了……嗯,那次事故之后,我再也不敢省略 --memory 了。
3.1 --memory 到底在限制什么?
--memory 设置的是容器的硬限制。达到这个值,内核会直接干预,不再给容器分配更多物理内存。底层实现靠的是 Linux CGroup(Control Group),Docker 把限制值写入 /sys/fs/cgroup/memory/memory.limit_in_bytes(CGroup v1)或对应的 cgroup v2 接口。CGroup v2 在较新的发行版上已经是默认了,但原理一样——都是内核来守门。
你想想看,如果不设硬限制,一个容器就能把宿主机的内存全占完,其他服务和系统进程会跟着遭殃。我习惯把 --memory 当作容器的安全护栏,哪怕给的值比实际需求大,也一定要设。
3.2 参数组合与典型配置
--memory 很少单用,通常配合 --memory-swap 和 --memory-reservation 一起出场。我整理了一个参数对比表,平时调优直接对着看。
| 参数 | 作用 | 默认值 | 备注 |
|---|---|---|---|
--memory(-m) |
物理内存硬限制 | 不限 | 容器超过此值即触发 OOM |
--memory-swap |
内存 + swap 总量限制 | 与 --memory 相同 (即 swap 为 0) |
设为 -1 表示不限 swap |
--memory-reservation |
软限制(尽力保证) | 不限 | 低于此值,系统尽量不回收内存 |
--memory-swappiness |
控制内核使用 swap 的倾向 | 继承宿主机 | 0 表示尽量不用 swap |
我个人最常用的组合是这样:
docker run -d \
--name nginx-app \
--memory="512m" \
--memory-swap="768m" \
--memory-reservation="384m" \
nginx:latest
解释一下:物理内存硬限制 512 MB,加上 256 MB swap 额度(一共 768 MB),软限制留 384 MB。这样在不紧张时能跑到 500 MB 左右,压力大时也不会捅破 512 MB 的硬顶——swap 只是最后一道缓冲。
3.3 我踩过的坑:OOM Killer 与 Swap
有一回压测,我给了容器 --memory=512m 但没设 --memory-swap。服务跑到 500 MB 左右突然挂了,docker logs 里啥都没有,只有一句 Exit code 137。137 意味着进程被 SIGKILL 干掉了——说白了就是 OOM Killer 动了手。
查了 dmesg 才看到:
[67432.198] Memory cgroup out of memory: Killed process 14231 (java) total-vm: 1.2GB, anon-rss: 512MB, file-rss: 8MB
原来 Docker 默认 --memory-swap 等于 --memory,也就是 swap 额度为 0。进程物理内存一到 512 MB,内核直接开枪。我的建议是:压测阶段一定把 swap 额度放开一些,防止突发流量把进程瞬间打死。
查看容器当前的内存水位,我一般用这套三板斧:
docker stats --no-stream <容器名>—— 看实时内存和限制docker inspect <容器名> | jq '.[].HostConfig.Memory'—— 确认配置是否生效cat /sys/fs/cgroup/memory/docker/<容器ID>/memory.limit_in_bytes—— 直接读内核接口
--memory-swap 设成了 -1(不限 swap),结果容器大量使用交换分区,磁盘 IO 飙升,整体吞吐反而掉了一半。不限 swap 有时候比 OOM 更可怕——进程变慢了,但你以为它还好。
3.4 生产环境配置建议
经过几次线上事故,我总结了一套相对稳的配置策略,分享给你:
- 硬限制 = 预留 20% 缓冲。 如果 JVM 或 Node 进程实际需要 1 GB,
--memory给 1.2~1.3 GB。给进程留点堆外内存和突发空间。 - 软限制 = 实际需求的 80%。
--memory-reservation设成这个值,让系统在内存紧张时优先从你的容器回收内存。 - 尽量关闭 swap。 对延迟敏感的服务,建议
--memory-swap和--memory相等。否则换页抖动会拖垮响应时间。 - 配合 cgroup 内存统计监控。 用
docker stats或 Prometheus cAdvisor 采集container_memory_usage_bytes,设置告警水位线在硬限制的 80%。
3.5 检查限制是否生效
配置完了,怎么确认真的生效了?我习惯这么测:
# 启动一个测试容器,限制 128 MB
docker run -d --name mem-test --memory="128m" alpine sleep 3600
# 查看 cgroup 限制文件
docker exec mem-test cat /sys/fs/cgroup/memory/memory.limit_in_bytes
# 用 stress 工具压到超限
docker run --rm --memory="128m" alpine sh -c "apk add stress; stress --vm 1 --vm-bytes 256m"
# 观察是否被 OOM 杀死(返回 137)
echo $?
返回 137 就对了——说明硬限制在正常工作。如果返回 0,要么限制没生效,要么压力没打够。我遇到过几次因为宿主机内核版本太老(3.x 系列)导致 CGroup 支持不完整,后来统一升级到 4.18+ 才稳定。
嗯,内存限制这块其实没那么玄乎。记住一条:每个容器都必须有硬限制,数值可以大,但不能没有。 有了这个底,后面的内存优化才能站得住脚。