找回密码
 立即注册

QQ登录

只需一步,快速开始

peUfSYR.png
查看: 45|回复: 0

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

[复制链接]

876

主题

13

回帖

2808

积分

管理员

积分
2808
发表于 2026-3-29 16:17:54 | 显示全部楼层 |阅读模式
Docker 30天实战系列 - 第24天

你有没有遇到过这种情况:线上跑着好几个服务,突然某天凌晨三点被电话叫醒,一看监控,某个容器把整台服务器 16G 内存全吃光了,OOM Killer 一顿乱杀,连 SSH 都登不上去,只能去机房硬重启。

这就是今天要聊的问题——如果你不给容器设资源限制,它就像一个没有节制的食客,能把自助餐厅吃到倒闭。

本文你将学到

  • 为什么必须给容器设置资源限制
  • --memory 和 --cpus 参数的正确用法
  • OOM Killer 是什么,它怎么决定杀谁
  • 用 docker stats 实时监控容器资源
  • 用压测工具验证资源限制是否生效

    阅读时间:10 分钟 | 实操时间:20 分钟 | 难度等级:中级

    ━━━━━━━━━━━━━━━━━━━━

    一、不设限制会怎样

    先打个比方。一台服务器就是一栋公寓楼,每个容器是一个租户。如果不限制用水量,某个租户 24 小时开着水龙头洗车,整栋楼就会停水。操作系统面对这种情况的处理方式很简单粗暴——直接断水断电,也就是 OOM Killer 出场。

    来看一个真实的资源消耗示意:
    1. ┌─────────────────────────────────────────────┐
    2. │                宿主机 (8GB RAM)               │
    3. │                                             │
    4. │  ┌──────────┐ ┌──────────┐ ┌──────────┐    │
    5. │  │ 容器A    │ │ 容器B    │ │ 容器C    │    │
    6. │  │ 2GB/无限 │ │ 3GB/无限 │ │ 4GB/无限 │    │
    7. │  │          │ │          │ │  (爆了!)  │    │
    8. │  └──────────┘ └──────────┘ └──────────┘    │
    9. │                                             │
    10. │  总需求: 2+3+4 = 9GB > 8GB 可用            │
    11. │  结果: OOM Killer 随机杀进程                │
    12. └─────────────────────────────────────────────┘
    复制代码

    加了资源限制之后:
    1. ┌─────────────────────────────────────────────┐
    2. │                宿主机 (8GB RAM)               │
    3. │                                             │
    4. │  ┌──────────┐ ┌──────────┐ ┌──────────┐    │
    5. │  │ 容器A    │ │ 容器B    │ │ 容器C    │    │
    6. │  │ 限2GB    │ │ 限2GB    │ │ 限2GB    │    │
    7. │  │ [安全]   │ │ [安全]   │ │ [安全]   │    │
    8. │  └──────────┘ └──────────┘ └──────────┘    │
    9. │                                             │
    10. │  总上限: 2+2+2 = 6GB < 8GB 可用            │
    11. │  结果: 各自在限额内运行,互不干扰           │
    12. └─────────────────────────────────────────────┘
    复制代码

    二、内存限制

    2.1 基本用法

    给容器设内存限制非常简单,用
    1. --memory
    复制代码
    (简写
    1. -m
    复制代码
    )参数:
    1. docker run -d --name mem-limited --memory=256m nginx
    复制代码

    这条命令启动一个 Nginx 容器,最多只能用 256MB 内存。

    来验证一下限制是否生效:
    1. docker inspect mem-limited --format='{{.HostConfig.Memory}}'
    复制代码

    预期输出:
    1. 268435456
    复制代码

    这个数字是 256 1024 1024 = 268435456 字节,没毛病。

    2.2 内存和 Swap 的关系

    Docker 默认会给容器分配和内存等量的 Swap。也就是说
    1. --memory=256m
    复制代码
    实际上容器可以用 256M 内存 + 256M Swap = 512M。

    如果你想精确控制,可以这样:
    1. # 256M 内存,完全禁用 Swap
    2. docker run -d --name no-swap --memory=256m --memory-swap=256m nginx
    3. # 256M 内存,512M Swap(总共 768M)
    4. docker run -d --name with-swap --memory=256m --memory-swap=768m nginx
    复制代码

    小贴士:
    1. --memory-swap
    复制代码
    的值是内存加 Swap 的总量,不是单独的 Swap 大小。这个设计确实有点反直觉,但记住就好。

    2.3 OOM Killer 机制

    当容器使用的内存超过限制时,Linux 内核的 OOM Killer 会出手。它的工作原理是这样的:
    1. 容器内存使用 → 接近限制 → 触发内核回收
    2.     │
    3.     ├── 回收成功 → 继续运行
    4.     │
    5.     └── 回收失败 → OOM Killer 启动
    6.               │
    7.               ├── 计算 oom_score(谁用得多杀谁)
    8.               └── 杀掉得分最高的进程
    复制代码

    我们可以用一个实际的例子来触发 OOM:
    1. # 启动一个只有 64M 内存的容器
    2. docker run -d --name oom-test --memory=64m ubuntu:22.04 \
    3.   bash -c "apt-get update > /dev/null 2>&1; stress-ng --vm 1 --vm-bytes 128M --timeout 30s"
    复制代码

    等几秒钟后查看状态:
    1. docker inspect oom-test --format='{{.State.OOMKilled}}'
    复制代码

    预期输出:
    1. true
    复制代码

    容器确实被 OOM 杀掉了。你还可以通过
    1. docker events
    复制代码
    1. dmesg
    复制代码
    看到相关日志。

    2.4 内存预留(Soft Limit)

    除了硬限制,Docker 还支持软限制:
    1. docker run -d --name soft-limited \
    2.   --memory=512m \
    3.   --memory-reservation=256m \
    4.   nginx
    复制代码
    1. --memory-reservation
    复制代码
    是一个软限制。当宿主机内存紧张时,Docker 会尝试把容器内存回收到这个水位线以下。但不像硬限制那样会触发 OOM,只是"尽量"。

    三、CPU 限制

    3.1 限制 CPU 核数

    1. --cpus
    复制代码
    参数限制容器可以使用的 CPU 核数:
    1. # 最多使用 1.5 个 CPU 核
    2. docker run -d --name cpu-limited --cpus=1.5 nginx
    复制代码

    这里的 1.5 表示容器最多使用相当于 1.5 个 CPU 核的计算能力。如果你的机器有 4 核,这个容器最多用 37.5% 的总 CPU。

    3.2 CPU 份额(相对权重)

    如果你不想设硬上限,而是想让多个容器按比例分配 CPU,用
    1. --cpu-shares
    复制代码

    1. # 容器A 权重 1024(默认值)
    2. docker run -d --name app-a --cpu-shares=1024 nginx
    3. # 容器B 权重 512(分到一半的 CPU)
    4. docker run -d --name app-b --cpu-shares=512 nginx
    复制代码

    打个比方:cpu-shares 就像是自助餐的 VIP 和普通票。人少的时候大家随便吃,人多的时候 VIP 优先。只有在 CPU 资源紧张时,这个权重才会起作用。

    3.3 绑定特定 CPU 核心

    对于对延迟敏感的应用,可以把容器绑定到特定的 CPU 核心上:
    1. # 只使用第 0 和第 1 个 CPU 核
    2. docker run -d --name pinned --cpuset-cpus="0,1" nginx
    复制代码

    这在高性能场景下很有用,可以避免 CPU 缓存失效带来的性能损失。

    四、用 docker stats 监控资源

    Docker 内置了实时监控命令:
    1. docker stats --no-stream
    复制代码

    预期输出:
    1. CONTAINER ID   NAME          CPU %   MEM USAGE / LIMIT   MEM %   NET I/O       BLOCK I/O   PIDS
    2. a1b2c3d4e5f6   mem-limited   0.01%   3.5MiB / 256MiB     1.37%   1.2kB / 0B    0B / 0B     3
    3. b2c3d4e5f6a7   cpu-limited   0.02%   3.2MiB / 7.77GiB   0.04%   900B / 0B     0B / 0B     3
    复制代码

    关键字段说明:

  • MEM USAGE / LIMIT:当前内存使用量和上限。如果你设了
    1. --memory=256m
    复制代码
    ,LIMIT 就是 256MiB
  • CPU %:CPU 使用率。如果设了
    1. --cpus=1.5
    复制代码
    ,这个值最高到 150%
  • PIDS:容器内进程数

    如果想持续监控,去掉
    1. --no-stream
    复制代码

    1. docker stats
    复制代码

    它会每秒刷新一次,像 top 命令一样,按 Ctrl+C 退出。

    五、压测验证

    光设了限制,到底有没有真的生效?我们用压测工具来验证。

    5.1 验证内存限制
    1. # 启动一个限制 128M 内存的容器
    2. docker run -d --name mem-stress --memory=128m ubuntu:22.04 sleep 3600
    3. # 安装压测工具
    4. docker exec mem-stress bash -c "apt-get update -qq && apt-get install -y -qq stress-ng > /dev/null 2>&1"
    5. # 尝试分配 64M 内存(在限制内)
    6. docker exec mem-stress stress-ng --vm 1 --vm-bytes 64M --timeout 5s --vm-keep
    复制代码

    预期输出:
    1. stress-ng: info:  [xx] dispatching hogs: 1 vm
    2. stress-ng: info:  [xx] successful run completed in 5.00s
    复制代码

    成功了。现在试试超过限制:
    1. # 尝试分配 200M(超过 128M 限制)
    2. docker exec mem-stress stress-ng --vm 1 --vm-bytes 200M --timeout 10s --vm-keep
    复制代码

    这次你会发现进程被杀掉,或者容器直接退出。

    5.2 验证 CPU 限制
    1. # 启动一个限制 0.5 CPU 的容器
    2. docker run -d --name cpu-stress --cpus=0.5 ubuntu:22.04 sleep 3600
    3. # 安装压测工具
    4. docker exec cpu-stress bash -c "apt-get update -qq && apt-get install -y -qq stress-ng > /dev/null 2>&1"
    5. # 跑满 CPU
    6. docker exec -d cpu-stress stress-ng --cpu 4 --timeout 30s
    复制代码

    然后在另一个终端查看资源使用:
    1. docker stats cpu-stress --no-stream
    复制代码

    预期输出:
    1. CONTAINER ID   NAME         CPU %   MEM USAGE / LIMIT   MEM %   NET I/O     BLOCK I/O   PIDS
    2. 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 来管理容器。资源限制这样写:
    1. version: "3.8"
    2. services:
    3.   web:
    4.     image: nginx
    5.     deploy:
    6.       resources:
    7.         limits:
    8.           cpus: "1.0"
    9.           memory: 512M
    10.         reservations:
    11.           cpus: "0.25"
    12.           memory: 128M
    复制代码

    注意:
    1. deploy.resources
    复制代码
    在 Docker Compose v3 中需要配合
    1. docker stack deploy
    复制代码
    使用,或者在
    1. docker compose up
    复制代码
    时加
    1. --compatibility
    复制代码
    标志。

    如果是 Compose v2 格式,直接用顶层字段:
    1. version: "2.4"
    2. services:
    3.   web:
    4.     image: nginx
    5.     mem_limit: 512m
    6.     mem_reservation: 128m
    7.     cpus: 1.0
    复制代码

    6.2 资源限制参考值

    不同类型的应用,资源限制的经验值:
    1. ┌───────────────────┬──────────┬──────────┐
    2. │ 应用类型           │ 内存建议  │ CPU建议   │
    3. ├───────────────────┼──────────┼──────────┤
    4. │ Nginx/静态服务     │ 64-128M  │ 0.25-0.5 │
    5. │ Node.js API       │ 256-512M │ 0.5-1.0  │
    6. │ Java Spring Boot  │ 512M-1G  │ 1.0-2.0  │
    7. │ MySQL             │ 1-4G     │ 1.0-2.0  │
    8. │ Redis             │ 128-512M │ 0.5-1.0  │
    9. │ Elasticsearch     │ 2-8G     │ 2.0-4.0  │
    10. └───────────────────┴──────────┴──────────┘
    复制代码

    这只是参考值,实际要根据你的业务压测结果来定。先给一个保守的值,再根据
    1. docker stats
    复制代码
    的监控数据逐步调整。

    七、常见问题 Q&A

    Q1:设了内存限制后容器频繁被 OOM 杀掉怎么办?

    首先用
    1. docker stats
    复制代码
    观察容器的实际内存使用曲线。如果内存持续增长直到被杀,说明应用有内存泄漏,需要修复应用本身。如果是偶尔的峰值导致被杀,可以适当调大限制,或者加 Swap 作为缓冲。另外检查一下
    1. --memory-swap
    复制代码
    的设置,确保没有意外禁用 Swap。

    Q2:--cpus 和 --cpu-shares 该用哪个?

    如果你需要保证某个容器"绝对不能超过 X 核",用
    1. --cpus
    复制代码
    。如果你希望多个容器在竞争 CPU 时按比例分配,用
    1. --cpu-shares
    复制代码
    。两者可以同时使用:
    1. --cpus
    复制代码
    设上限,
    1. --cpu-shares
    复制代码
    设优先级。

    Q3:Docker Desktop 上资源限制行为和 Linux 一样吗?

    不完全一样。Docker Desktop 在 Mac 和 Windows 上是跑在虚拟机里的,资源限制是相对于虚拟机的资源,不是宿主机的。你需要先在 Docker Desktop 设置里给虚拟机分配足够的 CPU 和内存,然后再给容器设限制。

    小结

    今天我们学了 Docker 容器资源限制的完整知识链:

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

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

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

    ━━━━━━━━━━━━━━━━━━━━
    本文为 Docker 30天实战系列第 24 篇。如果觉得有帮助,欢迎转发给同样在学 Docker 的朋友。
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-5-8 21:41 , Processed in 0.197059 second(s), 20 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表