你有没有遇到过这种情况:线上某个容器突然挂了,docker ps -a一看Exited (137),或者某个 Java 容器把整个宿主机的 CPU 跑满,其他服务全跟着遭殃?
我就是这么踩过来的。
默认情况下,Docker 容器对资源的使用是无限的——它可以使用主机内核调度器允许的任意资源。单个容器的内存泄漏就能拖垮整台物理机。生产环境不加资源限制,就是在给自己埋雷。
读完这篇文档,你将会:
- 掌握
--cpus、--cpu-shares、--cpuset-cpus的选型和踩坑点 - 搞懂
-m和--memory-swap的正确用法,彻底告别 OOMKilled - 学会
docker stats和docker update动态调整,不重启也能救场 - 拿到可直接复制的 Docker Compose 配置模板
1. 为什么必须做资源限制?
很简单:防一个坏容器搞死整台机器。
Docker 底层用的是 Linux 的 cgroups(Control Groups),说白了就是给每个容器划一个资源“配额”,超过配额就拦住。没有限制的情况下,一个跑偏的应用就能把主机的 CPU 或内存吃光。
这里有个重要概念:可压缩资源 vs 不可压缩资源。
- 可压缩资源如 CPU:不够用的时候只是跑得慢,容器不会因此被干掉。
- 不可压缩资源如内存:一旦用完,Linux 内核的 OOM Killer 会直接杀进程——通常是你的容器。这就是
Exited 137的来源。
所以,内存限制比 CPU 限制更紧急,必须设。不然泄露一次就要你半夜爬起来处理故障。
2. CPU 资源限制:三种方式选哪个
Docker 提供了三种 CPU 限制方式:
2.1--cpu-shares:相对权重(弹性分配)
设置容器的 CPU 时间分配权重,默认值是1024。只在 CPU紧张时才按比例分配,空闲时容器随便用。
docker run -d --name app-a --cpu-shares 1024 nginx docker run -d --name app-b --cpu-shares 512 nginx当 CPU 资源紧张时,app-a 获得的 CPU 时间是 app-b 的两倍。
什么时候用?多容器混合部署,想让核心服务多分点 CPU。但一旦 CPU 充裕,这个设置根本无效——所以它不能作为硬性限制。
2.2--cpus:硬性核数限制(推荐)
我最推荐这种方式,简单直接不绕弯子:
# 限制最多使用 1.5 个 CPU 核心 docker run -d --name myapp --cpus=1.5 nginx # 限制最多使用 2 个完整核心 docker run -d --name myapp --cpus=2 nginx这个参数从 Docker 1.13 就有了,生产环境可以直接用。50% 的核心数就用0.5,别搞复杂了。
2.3--cpuset-cpus:绑定特定核心
把容器绑死在固定的 CPU 核心上跑:
# 只在 CPU 0 和 CPU 3 上执行 docker run -d --name myapp --cpuset-cpus="0,3" nginx # 绑定 0-2 三个核心 docker run -d --name myapp --cpuset-cpus="0-2" nginx什么时候用?低延迟场景可以试试,因为能减少 CPU 跨核调度的开销。
但我一般不用,因为绑核心会降低整体资源的灵活调度能力。说实话,除非你对延迟极度敏感,否则--cpus就够了。
2.4--cpu-period和--cpu-quota(精确控制版)
这个是从 cgroup CFS(完全公平调度器)来的,99% 的场景用不上。
# 周期 100ms(10万 µs),配额 50ms(5万 µs)= 50% CPU docker run -it --cpu-period=100000 --cpu-quota=50000 centos /bin/bash我的建议:用--cpus就够了。--cpu-shares软限制 +--cpus硬限制一起用是稳妥的生产实践。
参数 | 类型 | 适用场景 | 推荐优先级 |
| 硬性核数 | 绝大多数场景 | ⭐⭐⭐⭐⭐ |
| 软性权重 | 混合部署时弹性分配 | ⭐⭐⭐ |
| 核心绑定 | 低延迟/性能敏感 | ⭐⭐ |
| 精确配额 | 特殊定制需求 | ⭐ |
3. 内存资源限制:不可压缩资源,设置错了就等着 OOM
这是最重要的一节,多看几遍。
内存一旦用完,容器直接 OOMKilled,不会给你缓冲的机会。我见过公司把 MySQL 容器内存设得太低,半夜业务高峰直接崩溃,亏惨了。
3.1 核心参数:-m/--memory(必须设)
docker run -d --name mysql-prod -m 4g mysql:8.0容器最多用 4GB 物理内存。最小值是 6MB(Docker 官方允许的最小值),但实际上别设太极限。
3.2--memory-swap:内存 + Swap 总量
# 物理内存 512MB,内存+Swap 总量 1GB → Swap 实际可用 512MB docker run -d -m 512m --memory-swap=1g myapp # 只设 -m 不设 --memory-swap → Swap 默认是 -m 的 2 倍 docker run -d -m 512m myapp # 128? 2 倍,OK # 禁用 Swap docker run -d -m 512m --memory-swap=-1 myapp这个坑我栽过:--memory-swap必须 ≥-m。如果不想用 Swap,设成-1。
3.3--memory-reservation:软性内存警戒线
docker run -d -m 4g --memory-reservation 3g myapp软限制:平时内存 < 3GB 不管,超过 3GB 开始回收内存。可把它理解为内存版--cpu-shares。
3.4--memory-swappiness:换出倾向(0-100)
# 尽量不用 Swap(推荐) docker run -d -m 512m --memory-swappiness=0 myapp默认内核可能会换出匿名内存页。我通常设成 0,因为 Swap 很慢。数值越高越倾向换出。
内存参数配置捷径:
命令 | 效果 |
| 物理内存 512MB,Swap 1GB |
| 物理 512MB,Swap 512MB |
| 物理 512MB,禁用 Swap |
| 物理 512MB,禁用 Swap(等价) |
⚠️换一句话说:内存绝对是硬限制,超了就杀。设值前务必评估应用峰值,留 20–30% 缓冲。
4. 磁盘 I/O 与文件句柄限制
4.1 磁盘 IO 限制(--device-read/write-bps)
# 读取限速 1MB/s,写入限速 1MB/s docker run -it --device-read-bps /dev/sda:1mb --device-write-bps /dev/sda:1mb ubuntu读写 IO 用--device-read-iops和--device-write-iops。/dev/sda替换成你的实际块设备。
4.2 文件句柄(--ulimit nofile)
docker run -d --name nginx-prod --ulimit nofile=65535:65535 nginx这个防止 Nginx 这类应用跑出too many open files错误。生产环境建议 65535。
5. 验证与动态调整
5.1docker stats— 看资源占用
$ docker stats --no-stream CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O a1b2c3d4 mysql 5.23% 1.2GiB / 4GiB 30% 1.2kB / 1.1kB5.2docker inspect— 查配置详情
$ docker inspect mysql --format 'Memory: {{.HostConfig.Memory}}, CPU: {{.HostConfig.CpuShares}}' Memory: 4294967296, CPU: 10245.3docker update— 动态调整,不重启(重磅)
运行时调整资源限制不用重启容器,这是 Docker 20.10+ 的特性。
# 实时加 CPU 核数 docker update --cpus 2.0 mysql-prod # 实时加内存 docker update --memory 6g mysql-prod # 同时调 docker update --cpus 2.0 --memory 6g --cpu-shares 2048 mysql-prod这个方法我在半夜救场过好几次——业务高峰内存不够了,直接docker update扩容,不用重启,服务不断。
注意:--memory调大需要确保宿主机有货;调小时可能导致 OOM,三思。
6. 常见错误与解决方法
❌ 错误 1:退出码 137 + OOMKilled
$ docker inspect myapp --format '{{.State.ExitCode}} {{.State.OOMKilled}}' 137 true原因:内存用超了,被内核直接杀了。
解法:docker stats看历史内存峰值,调大-m或在宿主机层面dmesg -T | grep -i "killed process"看内核日志。如果排应用内存泄露没问题,调大限制。
❌ 错误 2:容器慢得像蜗牛
可能被 CPU 限流了。先看docker stats—— CPU % 一直逼近你设定的--cpus上限,说明被 thottled 了。
偶尔高于上限说明在排队。查看 cgroup 统计数据:
$ docker inspect myapp --format '{{.Id}}' a1b2c3d4... $ cat /sys/fs/cgroup/cpu/docker/a1b2c3d4.../cpu.stat | grep nthrottled nr_throttled 532 throttled_time 12345678数字不断增长,说明容器在喘气等 CPU。解法:增加--cpus或优化应用逻辑。
❌ 错误 3:No swap limit support警告
$ docker info | grep -i swap WARNING: No swap limit support原因:系统内核的参数没开。在/etc/default/grub加swapaccount=1:
GRUB_CMDLINE_LINUX="... swapaccount=1"然后update-grub && reboot跑一下。建议尽早修复,避免内存配置失效。
7. 总结与互动
3 个核心点,记牢:
- 内存必须设限制(
-m),不可压缩资源超了就死。 - CPU 推荐
--cpus+--cpu-shares组合,硬配额 + 弹性权重两不误。 docker update动态调参是生产救星,不重启就能扩容,半夜少哭几次。
这还只是单机 Docker 层面的资源限制。如果你想了解 Kubernetes 下的资源管理,比如requestsvslimits怎么配才不挖坑,可以告诉我,我回头再肝一篇。
有用的话欢迎分享~ 你还在生产环境遇到过哪些资源限制导致的诡异问题?评论区见。