4、内存软限制(--memory-reservation)

内存软限制,说白了就是给容器一个“建议值”。告诉 Docker:“平时你尽量把内存控制在这个范围内,但如果宿主机有空闲,偶尔超一点也没关系”。它不会像硬限制那样一刀切地拒绝或杀掉容器,而是更温和、更弹性。

我最初接触这个参数时,觉得它有点鸡肋——既然有了 --memory 硬限制,为什么还要软限制?后来在压测环境中踩过坑才明白:硬限制是保命底线,软限制是日常预算。两个配合使用,才是真正的内存管理。

4.1 软限制的工作原理

先看一个最简单的例子:

docker run -d --name my-app \
  --memory="512m" \
  --memory-reservation="384m" \
  nginx:latest

这个容器:

  • 硬限制 512MB:最多能用到 512MB,超过就 OOM Kill。
  • 软限制 384MB:Docker 会尽量把内存压到 384MB 以下。但如果宿主机内存充裕,允许短暂突增到 512MB。

为什么会这样?内核通过 cgroup 的 memory.soft_limit_in_bytes 实现。当宿主机内存紧张时(例如其他容器或进程在争抢内存),内核会优先回收那些超过软限制的进程的内存页。换句话说,软限制是“低优先级警告线”,不是“禁令线”。

核心区别:硬限制是“你不能超过这么多”,软限制是“你最好别超过这么多,但超过了我不立刻罚你”。

我曾经在一个高并发 Web 服务上只设置了硬限制,没设软限制。结果业务低谷时容器占着 800MB 内存不释放,导致其他服务启动失败。加上软限制后,Docker 会在内存压力下主动回收缓存,整个集群的利用率提升了 20%。

4.2 什么时候该用软限制?

根据我的经验,下面这几种场景特别适合:

场景 说明
批处理任务 任务高峰期内存猛涨,平时用量低。软限制可避免长期占用大块内存。
共享宿主机 多个容器混部,用软限制保证每个容器在压力下公平竞争。
Java/Python GC 应用 GC 后内存会回落,软限制帮助控制常驻集。
弹性扩容场景 允许流量峰值时突增,但平时压缩资源。

嗯,这里要注意:如果容器稳定运行且内存曲线很平,软限制意义不大。它真正起作用的时候是 宿主机发生内存竞争 时。

4.3 软限制 vs 硬限制:数值关系

官方推荐软限制 低于 硬限制,通常设置为硬限制的 60%~80%。你想想看,如果软限制等于硬限制,那它和硬限制有什么区别?等于白白浪费了一次弹性能力。

我的习惯:先压测摸清应用内存基线,然后硬限制设为峰值的 1.5 倍,软限制设为核心常驻内存的 1.2 倍。比如基线 300MB,峰值 500MB,我就设 hard=768MB, reservation=360MB。

反过来,如果软限制比硬限制还大,Docker 会报错吗?不会。但那样软限制就形同虚设——因为你设了一个比上限还高的建议值,内核根本不会去回收。

4.4 实战:如何验证软限制生效?

我的流程一般是:先启动容器,然后人为制造内存压力,观察内存回收行为。

# 步骤1:启动测试容器
docker run -d --name test-res \
  --memory="256m" \
  --memory-reservation="128m" \
  alpine sleep 3600

# 步骤2:在宿主机上制造内存压力(比如用 stress 工具)
stress --vm 2 --vm-bytes 512M --timeout 60s

# 步骤3:在另一个终端观察 test-res 容器的内存变化
docker stats test-res --no-stream

# 步骤4:查看 cgroup 中的软限制状态
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.soft_limit_in_bytes

你会看到:在压力到来前,容器可能用到 150MB(超过了 128MB 的软限制)。当 stress 开始消耗内存后,内核会尝试回收 test-res 的缓存和匿名页,把它的内存压回 128MB 附近。

注意:软限制不是严格保证!内核尽力而为。如果宿主机总内存不足,硬限制才是最后一道防线。我曾经见过一些生产事故,只设了软限制没设硬限制,结果宿主机 OOM 把整个主机都搞崩了。

4.5 总结一张图

内存软限制(--memory-reservation)核心逻辑 硬限制 --memory 禁止突破,超限即 OOM Kill 软限制 --memory-reservation 建议值,压力下尽力回收 未设限制(默认) 可占满宿主机,引发雪崩 容器运行时内存变化 无限制: 随意增长 宿主机 OOM 硬限制: 触及硬限制 OOM Kill 软+硬: 超过软限制 内核努力回收 回落至软线 💡 结论 硬限制决定容器的生死边界;软限制优化内存利用效率,在压力下优先回收。 两者搭配使用 = 底线保命 + 日常节能。

4.6 推荐配置模板

在我维护的几个生产集群中,统一使用这样的模板:

version: '3.8'
services:
  web:
    image: my-web-app:latest
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 320M
    # 或者直接用 docker run 参数:
    # --memory="512m" --memory-reservation="320m"

注意 deploy.resources.reservations 在 Docker Compose v3 中对应的是软限制。而 docker run 命令中就是 --memory-reservation,注意拼写不要错了——我手滑输错过好几次,结果容器没生效,排查半天。

避坑指南:我曾经在生产环境的 Compose 文件里写成了 memory_rsv,Docker 不报错但也不生效。所以一定检查容器启动后的 cgroup 配置:

docker inspect <container> | jq '.[0].HostConfig.MemoryReservation'

返回 0 就说明没设成功。

最后总结一句:软限制不是银弹。如果你的应用有明确的内存硬上限(比如数据库),直接设硬限制更干脆。但大多数通用服务,尤其是微服务场景,软限制能让整体资源利用率提升 10%~30%。嗯,这就是我使用它三四年的体会。