当一个容器申请使用多于整个主机可用的内存时, 内核可能会杀掉容器或者是 Docker daemon (守护进程)来释放内存, 这可能会导致所有服务不可用, 为了避免这个错误, 我们应该给每个容器限制合适的内存.
我们可以在 Docker-Compose 或者 Docker Stack 环境中使用以下配置来限制容器的内存使用:
version: '3.7'
services:
mysql:
image: mysql:5.7
deploy:
resources:
limits:
memory: 200M
mode: global
restart_policy:
condition: on-failure
delay: 5s
本文使用 3.7 版本的配置文件语法和 swarm 模式举例, 其他环境会有些差异, 其他版本的配置文件语法可以在官方文档-compose-file中找到.
更多语法, 如限制 CPU 等, 可以查阅resource_constraints
接下来我们来理解上面的配置
limits.memory
The maximum amount of memory the container can use. If you set this option, the minimum allowed value is 4m (4 megabyte).
容器允许的内存最大使用量, 最小值为4M.
当容器使用了大于限制的内存时, 会发生什么, 触发程序 GC 还是 Kill?
不幸的时, 官方文档好像没有对内存限制说明得很详细, 不过 Google 可以帮忙, 在下面的文章中能找到一点蛛丝马迹:
再经过试验证明当程序使用超过 limits.memory 限制的内存时, 容器会被 Kill (cgroup干的 resource_management_guide/sec-memory).
简单的, 可以使用 redis 容器来进行这个实验: 限制内存为 10M, 再添加大量数据给 redis, 然后查看容器的状态.
实际上我们不想让容器直接被 Kill, 而是让 Redis 触发清理逻辑, 直接 Kill 会导致服务在一段时间内不可用(虽然会重启).
怎么办?
各种调研后发现官方提供的其他参数都不能解决这个问题, 包括 memory-reservation, kernel-memory, oom-kill-disable.
看来并不能傻瓜化的解决这个问题, 现在如果我们只想触发程序的 GC, 应该怎么做?
一般来说, 程序当判定到内存不足时会有自己的 GC 机制, 但正如这篇文章Understanding Docker Container Memory Limit Behavior里所说, 运行在docker容器里的程序对内存限制是不可见的, 程序还是会申请大于docker limit的内存最终引起OOM Kill.
这就需要我们额外对程序进行配置, 如 redis 的 maxmemory 配置, java 的 JVM 配置, 不幸的是并不是所有程序都有自带的内存限制配置, 如 mysql, 这种情况下建议调低程序性能 和 保证留够的程序需要的内存.
这篇文章有提到如何调整mysql内存: https://marcopeg.com/2016/dockerized-mysql-crashes-lot
如果你的服务器开启了 Swap, 有可能还会遇到一个问题: 当容器将要达到内存限制时会变得特别慢并且磁盘 IO 很高(达到顶峰).
这是因为我们还忽略了一个参数: memory-swap, 当没有设置 memory-swap 时它的值会是 memory-limit 的两倍, 假如设置了 limit-memory=300M, 没有设置 memory-swap, 这意味着容器可以使用300M内存和300M Swap. https://docs.docker.com/config/containers/resource_constraints/#--memory-swap-details
值得注意的是 Swap 并不是无损的, 相反的, 它十分慢(使用磁盘代替内存), 我们应该禁用它。
不过 compose file v3 并不支持 memory-swap limit 的设置, 唉。
无奈, 那就关闭主机的 swap 吧。
总结 当容器达到内存限制时会发送的事情:
2023-11-06 更新:
虽然我关闭了主机的 swap,但有时候容器再达到 99% 内存使用的时候,不会在继续增长内存使用量而 OOM,而是启用了 swap,导致磁盘读取 100%,程序陷入假死状态。
再次 google:docker swap
得到官方文档:https://docs.docker.com/config/containers/resource_constraints/
Many of these features require your kernel to support Linux capabilities. To check for support, you can use the docker info command. If a capability is disabled in your kernel, you may see a warning at the end of the output like the following:
WARNING: No swap limit support
原来 swap limit 还需要配置才能支持?试试吧。
再按照文档指示操作下。
再继续观察一段时间,哦 对了,memory-swap 不支持在 stack 模式下设置的问题在三年后(写文到现在)依然没人解决,有 MR 都不合。