冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 24 如何限制容器运行资源

浏览:18次阅读
没有评论

共计 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 eventsdmesg 看到相关日志。

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 容器资源限制的完整知识链:

  1. 内存限制 --memory 设硬上限,--memory-reservation 设软限制,--memory-swap 控制 Swap
  2. CPU 限制 --cpus 设核数上限,--cpu-shares 设相对权重,--cpuset-cpus 绑定核心
  3. OOM Killer:内存超限时内核的自动杀进程机制,通过合理设限来预防
  4. 监控验证 docker stats 实时监控,配合压测工具验证限制效果

资源限制是生产环境的必备配置,没有它就像开车不系安全带——平时觉得没事,出了事就是大事。

明天的 Day 25,我们将进入容器监控的深水区——用 Prometheus + Grafana 搭建一套专业的容器监控体系,让你对每个容器的资源使用了如指掌。


本文为 Docker 30 天实战系列第 24 篇。如果觉得有帮助,欢迎转发给同样在学 Docker 的朋友。

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计5652字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。