共计 5652 个字符,预计需要花费 15 分钟才能阅读完成。
文章目录 [显示]
Docker 30 天实战系列 – 第 24 天
你有没有遇到过这种情况:线上跑着好几个服务,突然某天凌晨三点被电话叫醒,一看监控,某个容器把整台服务器 16G 内存全吃光了,OOM Killer 一顿乱杀,连 SSH 都登不上去,只能去机房硬重启。
这就是今天要聊的问题——如果你不给容器设资源限制,它就像一个没有节制的食客,能把自助餐厅吃到倒闭。
本文你将学到
- 为什么必须给容器设置资源限制
- –memory 和 –cpus 参数的正确用法
- OOM Killer 是什么,它怎么决定杀谁
- 用 docker stats 实时监控容器资源
- 用压测工具验证资源限制是否生效
阅读时间:10 分钟 | 实操时间:20 分钟 | 难度等级:中级
一、不设限制会怎样
先打个比方。一台服务器就是一栋公寓楼,每个容器是一个租户。如果不限制用水量,某个租户 24 小时开着水龙头洗车,整栋楼就会停水。操作系统面对这种情况的处理方式很简单粗暴——直接断水断电,也就是 OOM Killer 出场。
来看一个真实的资源消耗示意:
┌─────────────────────────────────────────────┐
│ 宿主机 (8GB RAM) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 容器 A │ │ 容器 B │ │ 容器 C │ │
│ │ 2GB/ 无限 │ │ 3GB/ 无限 │ │ 4GB/ 无限 │ │
│ │ │ │ │ │ (爆了!) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 总需求: 2+3+4 = 9GB > 8GB 可用 │
│ 结果: OOM Killer 随机杀进程 │
└─────────────────────────────────────────────┘
加了资源限制之后:
┌─────────────────────────────────────────────┐
│ 宿主机 (8GB RAM) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 容器 A │ │ 容器 B │ │ 容器 C │ │
│ │ 限 2GB │ │ 限 2GB │ │ 限 2GB │ │
│ │ [安全] │ │ [安全] │ │ [安全] │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 总上限: 2+2+2 = 6GB < 8GB 可用 │
│ 结果: 各自在限额内运行,互不干扰 │
└─────────────────────────────────────────────┘
二、内存限制
2.1 基本用法
给容器设内存限制非常简单,用 --memory(简写 -m)参数:
docker run -d --name mem-limited --memory=256m nginx
这条命令启动一个 Nginx 容器,最多只能用 256MB 内存。
来验证一下限制是否生效:
docker inspect mem-limited --format='{{.HostConfig.Memory}}'
预期输出:
268435456
这个数字是 256 1024 1024 = 268435456 字节,没毛病。
2.2 内存和 Swap 的关系
Docker 默认会给容器分配和内存等量的 Swap。也就是说 --memory=256m 实际上容器可以用 256M 内存 + 256M Swap = 512M。
如果你想精确控制,可以这样:
# 256M 内存,完全禁用 Swap
docker run -d --name no-swap --memory=256m --memory-swap=256m nginx
# 256M 内存,512M Swap(总共 768M)docker run -d --name with-swap --memory=256m --memory-swap=768m nginx
小贴士:--memory-swap 的值是内存加 Swap 的总量,不是单独的 Swap 大小。这个设计确实有点反直觉,但记住就好。
2.3 OOM Killer 机制
当容器使用的内存超过限制时,Linux 内核的 OOM Killer 会出手。它的工作原理是这样的:
容器内存使用 → 接近限制 → 触发内核回收
│
├── 回收成功 → 继续运行
│
└── 回收失败 → OOM Killer 启动
│
├── 计算 oom_score(谁用得多杀谁)└── 杀掉得分最高的进程
我们可以用一个实际的例子来触发 OOM:
# 启动一个只有 64M 内存的容器
docker run -d --name oom-test --memory=64m ubuntu:22.04 \
bash -c "apt-get update > /dev/null 2>&1; stress-ng --vm 1 --vm-bytes 128M --timeout 30s"
等几秒钟后查看状态:
docker inspect oom-test --format='{{.State.OOMKilled}}'
预期输出:
true
容器确实被 OOM 杀掉了。你还可以通过 docker events 或 dmesg 看到相关日志。
2.4 内存预留(Soft Limit)
除了硬限制,Docker 还支持软限制:
docker run -d --name soft-limited \
--memory=512m \
--memory-reservation=256m \
nginx
--memory-reservation 是一个软限制。当宿主机内存紧张时,Docker 会尝试把容器内存回收到这个水位线以下。但不像硬限制那样会触发 OOM,只是 " 尽量 "。
三、CPU 限制
3.1 限制 CPU 核数
用 --cpus 参数限制容器可以使用的 CPU 核数:
# 最多使用 1.5 个 CPU 核
docker run -d --name cpu-limited --cpus=1.5 nginx
这里的 1.5 表示容器最多使用相当于 1.5 个 CPU 核的计算能力。如果你的机器有 4 核,这个容器最多用 37.5% 的总 CPU。
3.2 CPU 份额(相对权重)
如果你不想设硬上限,而是想让多个容器按比例分配 CPU,用 --cpu-shares:
# 容器 A 权重 1024(默认值)docker run -d --name app-a --cpu-shares=1024 nginx
# 容器 B 权重 512(分到一半的 CPU)docker run -d --name app-b --cpu-shares=512 nginx
打个比方:cpu-shares 就像是自助餐的 VIP 和普通票。人少的时候大家随便吃,人多的时候 VIP 优先。只有在 CPU 资源紧张时,这个权重才会起作用。
3.3 绑定特定 CPU 核心
对于对延迟敏感的应用,可以把容器绑定到特定的 CPU 核心上:
# 只使用第 0 和第 1 个 CPU 核
docker run -d --name pinned --cpuset-cpus="0,1" nginx
这在高性能场景下很有用,可以避免 CPU 缓存失效带来的性能损失。
四、用 docker stats 监控资源
Docker 内置了实时监控命令:
docker stats --no-stream
预期输出:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a1b2c3d4e5f6 mem-limited 0.01% 3.5MiB / 256MiB 1.37% 1.2kB / 0B 0B / 0B 3
b2c3d4e5f6a7 cpu-limited 0.02% 3.2MiB / 7.77GiB 0.04% 900B / 0B 0B / 0B 3
关键字段说明:
- MEM USAGE / LIMIT:当前内存使用量和上限。如果你设了
--memory=256m,LIMIT 就是 256MiB - CPU %:CPU 使用率。如果设了
--cpus=1.5,这个值最高到 150% - PIDS:容器内进程数
如果想持续监控,去掉 --no-stream:
docker stats
它会每秒刷新一次,像 top 命令一样,按 Ctrl+C 退出。
五、压测验证
光设了限制,到底有没有真的生效?我们用压测工具来验证。
5.1 验证内存限制
# 启动一个限制 128M 内存的容器
docker run -d --name mem-stress --memory=128m ubuntu:22.04 sleep 3600
# 安装压测工具
docker exec mem-stress bash -c "apt-get update -qq && apt-get install -y -qq stress-ng > /dev/null 2>&1"
# 尝试分配 64M 内存(在限制内)docker exec mem-stress stress-ng --vm 1 --vm-bytes 64M --timeout 5s --vm-keep
预期输出:
stress-ng: info: [xx] dispatching hogs: 1 vm
stress-ng: info: [xx] successful run completed in 5.00s
成功了。现在试试超过限制:
# 尝试分配 200M(超过 128M 限制)docker exec mem-stress stress-ng --vm 1 --vm-bytes 200M --timeout 10s --vm-keep
这次你会发现进程被杀掉,或者容器直接退出。
5.2 验证 CPU 限制
# 启动一个限制 0.5 CPU 的容器
docker run -d --name cpu-stress --cpus=0.5 ubuntu:22.04 sleep 3600
# 安装压测工具
docker exec cpu-stress bash -c "apt-get update -qq && apt-get install -y -qq stress-ng > /dev/null 2>&1"
# 跑满 CPU
docker exec -d cpu-stress stress-ng --cpu 4 --timeout 30s
然后在另一个终端查看资源使用:
docker stats cpu-stress --no-stream
预期输出:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
c3d4e5f6a7b8 cpu-stress 50.2% 25MiB / 7.77GiB 0.31% 0B / 0B 0B / 0B 7
CPU 使用率被稳稳地限制在 50% 左右(0.5 核 = 50%),即使容器里开了 4 个压测进程。
六、生产环境的最佳实践
6.1 Docker Compose 中设置资源限制
在实际项目中,我们通常用 Docker Compose 来管理容器。资源限制这样写:
version: "3.8"
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
注意:deploy.resources 在 Docker Compose v3 中需要配合 docker stack deploy 使用,或者在 docker compose up 时加 --compatibility 标志。
如果是 Compose v2 格式,直接用顶层字段:
version: "2.4"
services:
web:
image: nginx
mem_limit: 512m
mem_reservation: 128m
cpus: 1.0
6.2 资源限制参考值
不同类型的应用,资源限制的经验值:
┌───────────────────┬──────────┬──────────┐
│ 应用类型 │ 内存建议 │ CPU 建议 │
├───────────────────┼──────────┼──────────┤
│ Nginx/ 静态服务 │ 64-128M │ 0.25-0.5 │
│ Node.js API │ 256-512M │ 0.5-1.0 │
│ Java Spring Boot │ 512M-1G │ 1.0-2.0 │
│ MySQL │ 1-4G │ 1.0-2.0 │
│ Redis │ 128-512M │ 0.5-1.0 │
│ Elasticsearch │ 2-8G │ 2.0-4.0 │
└───────────────────┴──────────┴──────────┘
这只是参考值,实际要根据你的业务压测结果来定。先给一个保守的值,再根据 docker stats 的监控数据逐步调整。
七、常见问题 Q&A
Q1:设了内存限制后容器频繁被 OOM 杀掉怎么办?
首先用 docker stats 观察容器的实际内存使用曲线。如果内存持续增长直到被杀,说明应用有内存泄漏,需要修复应用本身。如果是偶尔的峰值导致被杀,可以适当调大限制,或者加 Swap 作为缓冲。另外检查一下 --memory-swap 的设置,确保没有意外禁用 Swap。
Q2:--cpus 和 --cpu-shares 该用哪个?
如果你需要保证某个容器 " 绝对不能超过 X 核 ",用 --cpus。如果你希望多个容器在竞争 CPU 时按比例分配,用 --cpu-shares。两者可以同时使用:--cpus 设上限,--cpu-shares 设优先级。
Q3:Docker Desktop 上资源限制行为和 Linux 一样吗?
不完全一样。Docker Desktop 在 Mac 和 Windows 上是跑在虚拟机里的,资源限制是相对于虚拟机的资源,不是宿主机的。你需要先在 Docker Desktop 设置里给虚拟机分配足够的 CPU 和内存,然后再给容器设限制。
小结
今天我们学了 Docker 容器资源限制的完整知识链:
- 内存限制 :
--memory设硬上限,--memory-reservation设软限制,--memory-swap控制 Swap - CPU 限制 :
--cpus设核数上限,--cpu-shares设相对权重,--cpuset-cpus绑定核心 - OOM Killer:内存超限时内核的自动杀进程机制,通过合理设限来预防
- 监控验证 :
docker stats实时监控,配合压测工具验证限制效果
资源限制是生产环境的必备配置,没有它就像开车不系安全带——平时觉得没事,出了事就是大事。
明天的 Day 25,我们将进入容器监控的深水区——用 Prometheus + Grafana 搭建一套专业的容器监控体系,让你对每个容器的资源使用了如指掌。
本文为 Docker 30 天实战系列第 24 篇。如果觉得有帮助,欢迎转发给同样在学 Docker 的朋友。