容器

Docker CGroup 资源限制原理

·16 分钟阅读·6211 字

说明 Docker 如何通过 Linux CGroup 限制容器的内存、CPU、磁盘 IO 和进程数量

📋 目录

Docker CGroup 资源限制原理

核心定义 CGroup(Control Groups)是 Linux 内核提供的资源限制、资源统计和资源隔离机制。Docker 通过 CGroup 为容器进程设置 CPU、内存、磁盘 IO、进程数量等资源边界,避免单个容器无限制消耗宿主机资源。

1. 为什么容器需要 CGroup

容器本质上是宿主机上的一组普通进程。Namespace 负责隔离进程看到的系统视图,例如 PID、网络、挂载点;CGroup 负责限制这些进程能使用多少资源。

如果没有 CGroup,容器可能出现以下问题:

  1. 某个容器占满全部 CPU,影响宿主机和其他容器。
  2. 某个容器耗尽内存,触发宿主机 OOM。
  3. 某个容器疯狂创建进程,耗尽宿主机 PID。
  4. 某个容器持续写磁盘,拖慢其他服务 IO。
Namespace:解决“看见什么”的问题
CGroup:解决“能用多少”的问题

Docker 的资源限制参数,本质上会被转换成 CGroup 控制项,写入内核暴露的 cgroup 文件系统。

2. CGroup 的核心能力

CGroup 主要提供三类能力:

能力说明Docker 典型参数
资源限制限制进程组最多能使用多少资源--memory、--cpus、--pids-limit
资源统计统计进程组已经使用的资源docker stats
优先级控制资源竞争时按权重分配--cpu-shares、--blkio-weight

CGroup 的限制对象不是“容器”这个抽象概念,而是容器内实际运行的一组进程。Docker 创建容器时,会把容器进程加入对应的 cgroup 分组。

3. CGroup v1 与 CGroup v2

Linux 系统中常见两代 CGroup:

版本特点常见路径
CGroup v1每类资源一个独立 controller,目录结构相对分散/sys/fs/cgroup/memory/、/sys/fs/cgroup/cpu/
CGroup v2统一层级结构,controller 统一挂载/sys/fs/cgroup/

Docker 在不同系统上的底层路径可能不同,但上层参数含义基本一致。排查时应先确认宿主机使用的 cgroup 版本。

stat -fc %T /sys/fs/cgroup

典型输出含义:

cgroup2fs   # CGroup v2
 tmpfs      # 通常表示 CGroup v1 分层挂载

4. 内存限制原理

4.1. —memory:物理内存上限

--memory 或 -m 用于限制容器可使用的内存上限。

docker run -d --name web -m 512m nginx

含义:容器进程最多使用约 512 MiB 内存。超过限制后,内核会触发容器 cgroup 内的 OOM 处理,通常会杀掉容器内消耗内存的进程。

内存限制不是预分配 -m 512m 并不是启动时立即占用 512 MiB,而是设置最大可用上限。容器实际使用多少内存,取决于进程运行情况。

4.2. —memory-swap:内存加 Swap 总上限

--memory-swap 表示内存与 Swap 的总限制,必须结合 --memory 理解。

参数组合含义
--memory=512m --memory-swap=1g内存 512 MiB,内存 + Swap 总计 1 GiB
--memory=512m --memory-swap=512m不允许额外使用 Swap
--memory=512m --memory-swap=-1内存限制 512 MiB,Swap 不限制

生产环境通常不建议让容器无限制使用 Swap,因为大量 Swap 会导致服务严重抖动,甚至拖慢宿主机。

4.3. OOM 行为

容器超过内存限制时,常见结果是:

  1. 内核发现该 cgroup 内存超过限制。
  2. 触发 cgroup OOM。
  3. 选择并杀掉容器内的进程。
  4. 如果主进程被杀,容器退出。

可以通过以下命令查看容器是否因 OOM 退出:

docker inspect <container_id> --format '{{.State.OOMKilled}}'

也可以查看退出码:

docker inspect <container_id> --format '{{.State.ExitCode}}'

常见 OOM 退出码是 137,表示进程收到 SIGKILL。

4.4. —oom-kill-disable 的风险

--oom-kill-disable 可以关闭容器内 OOM kill 行为,但生产环境应谨慎使用。

docker run -d --memory=512m --oom-kill-disable nginx

如果容器持续申请内存但无法被 kill,可能导致宿主机资源压力扩大。因此该参数只适合非常明确的特殊场景。

5. CPU 限制原理

Docker 常用 CPU 控制方式包括硬性配额、相对权重和 CPU 绑核。

5.1. —cpus:CPU 硬性上限

--cpus 是最常用的 CPU 限制参数,用于设置容器最多能使用多少核 CPU。

# 最多使用 0.5 核
docker run -d --cpus=0.5 nginx

# 最多使用 2 核
docker run -d --cpus=2 nginx

在 CGroup v1 中,它通常映射到 CFS 配额:

cpu.cfs_period_us = 100000
cpu.cfs_quota_us  = --cpus × 100000

例如 --cpus=2 通常表示:

period = 100000us
quota  = 200000us

含义是在每 100ms 调度周期内,最多允许容器使用 200ms CPU 时间,相当于 2 核。

5.2. —cpu-shares:CPU 相对权重

--cpu-shares 是权重,不是硬性上限。只有当多个容器竞争 CPU 时,权重才明显生效。

docker run -d --name a --cpu-shares=2048 nginx
docker run -d --name b --cpu-shares=1024 nginx

当 CPU 满载竞争时,A 与 B 大致按 2:1 获得 CPU 时间。若宿主机 CPU 空闲,即使设置了较低权重,容器仍可能使用更多 CPU。

cpu-shares 不能限制峰值 如果目标是限制容器最多只能使用多少 CPU,应使用 --cpus;--cpu-shares 只适合资源竞争时做相对优先级控制。

5.3. —cpuset-cpus:绑定 CPU 核心

--cpuset-cpus 用于限制容器只能运行在指定 CPU 核心上。

docker run -d --cpuset-cpus="0,1" nginx

适用场景:

  1. 数据库、中间件等对 CPU 抖动敏感的服务。
  2. 需要减少跨 NUMA 或跨核心调度影响的场景。
  3. 需要为关键进程保留特定 CPU 核心的场景。

绑核不等于限制 CPU 使用率。容器绑定到 0,1 后,仍可能吃满这两个核心。

6. 磁盘 IO 限制原理

Docker 可以通过 blkio 或 io controller 控制容器磁盘 IO。不同系统、不同存储驱动、不同 cgroup 版本支持程度可能不同。

6.1. —blkio-weight:IO 权重

--blkio-weight 设置 IO 权重,范围通常是 10 到 1000,默认值为 500。

docker run -d --blkio-weight=200 nginx

它表示在磁盘 IO 竞争时,按权重分配 IO 资源。它不是精确的 MB/s 限速。

6.2. 按设备限制读写速率

Docker 也支持按块设备设置读写速率限制。

# 限制对 /dev/sda 的写入速率为 10MB/s
docker run -d --device-write-bps /dev/sda:10mb nginx

# 限制对 /dev/sda 的读取速率为 20MB/s
docker run -d --device-read-bps /dev/sda:20mb nginx

这类限制依赖具体块设备路径,且可能受文件系统、存储驱动和宿主机内核能力影响。

7. 进程数量限制

7.1. —pids-limit

--pids-limit 用于限制容器内最多能创建多少进程或线程。

docker run -d --pids-limit=200 nginx

作用:防止 fork bomb 或异常程序无限创建进程,耗尽宿主机 PID 资源。

7.2. 不限制 PID 的风险

如果容器内程序出现无限 fork,可能导致:

  1. 宿主机 PID 被耗尽。
  2. 新进程无法创建。
  3. SSH、监控 Agent、业务进程启动失败。
  4. 宿主机可用性下降。

因此,生产容器建议设置合理的 --pids-limit。

8. Docker 参数与 CGroup 控制项映射

Docker 参数控制资源类型常见用途
--memory / -m内存硬限制限制容器最大内存
--memory-swap内存 + Swap硬限制控制是否允许使用 Swap
--cpusCPU硬限制限制最大 CPU 使用量
--cpu-sharesCPU权重CPU 竞争时按比例分配
--cpuset-cpusCPU 核心绑核固定容器运行核心
--blkio-weight磁盘 IO权重IO 竞争时按比例分配
--device-read-bps磁盘读限速限制指定设备读取速率
--device-write-bps磁盘写限速限制指定设备写入速率
--pids-limit进程数量硬限制防止进程数量失控

9. 查看与排查方法

9.1. 查看容器资源使用

docker stats

该命令可以实时查看容器 CPU、内存、网络和块 IO 使用情况。

9.2. 查看容器配置

docker inspect <container_id>

可重点关注:

HostConfig.Memory
HostConfig.MemorySwap
HostConfig.NanoCpus
HostConfig.CpuShares
HostConfig.CpusetCpus
HostConfig.PidsLimit

9.3. 查看 CGroup v1 内存限制

cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes

9.4. 查看 CGroup v2 内存限制

cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.current

不同发行版和 Docker 配置下路径可能不同,可结合 docker inspect、systemctl status docker 和 /proc/<pid>/cgroup 定位。

pid=$(docker inspect <container_id> --format '{{.State.Pid}}')
cat /proc/$pid/cgroup

10. 生产配置建议

10.1. 常规 Web 服务

docker run -d \
  --name web \
  --memory=512m \
  --memory-swap=512m \
  --cpus=1 \
  --pids-limit=200 \
  nginx

适用目标:限制内存、CPU 峰值和进程数量,避免单容器拖垮宿主机。

10.2. CPU 敏感服务

docker run -d \
  --name app \
  --cpus=2 \
  --cpuset-cpus="0,1" \
  myapp:latest

适用目标:限制最多使用 2 核,并固定运行在指定 CPU 核心上。

10.3. 需要持久化数据的服务

资源限制只能控制进程资源,不能替代数据持久化。数据库、消息队列、上传目录等数据应使用 volume 或 bind mount。

docker run -d \
  --name mysql \
  --memory=2g \
  --cpus=2 \
  -v mysql-data:/var/lib/mysql \
  mysql:8

11. 常见误区

11.1. 只设置 —cpu-shares 就认为限制了 CPU

--cpu-shares 只是权重。宿主机 CPU 空闲时,容器仍可能使用全部可用 CPU。限制峰值应使用 --cpus。

11.2. 不设置 —memory 导致容器无上限

未设置内存限制时,容器可能持续占用宿主机内存,最终影响其他服务或触发宿主机 OOM。

11.3. 过度依赖 Swap

Swap 可以缓解瞬时内存压力,但大量 Swap 会造成明显性能抖动。延迟敏感服务通常应避免过度使用 Swap。

11.4. 忽略 pids 限制

不设置 --pids-limit 时,异常程序可能创建大量进程或线程,导致宿主机无法创建新进程。

12. 面试回答模板

标准回答 Docker 的资源限制基于 Linux CGroup 实现。Namespace 负责隔离容器看到的系统视图,CGroup 负责限制容器进程组可使用的资源。Docker 启动容器时,会把容器进程加入对应 cgroup,并根据 --memory、--cpus、--cpu-shares、--cpuset-cpus、--blkio-weight、--pids-limit 等参数写入内核控制项。内存限制超出后通常触发 cgroup OOM;--cpus 通过 CFS quota 设置 CPU 硬上限;--cpu-shares 只在资源竞争时按权重分配;--pids-limit 用于防止进程数量失控。生产环境应同时限制内存、CPU 峰值和进程数量,并通过 docker stats、docker inspect 和 /sys/fs/cgroup 进行排查验证。


关联文档

  • Docker 联合文件系统:理解 CGroup 与 UnionFS 分别解决资源限制和文件系统分层问题。
  • Docker 三种挂载:理解资源限制与数据持久化之间的边界。
  • 容器技术:容器 Namespace、CGroup、镜像和运行时机制的整体入口。
Yanche Blog

记录云原生、Linux、数据库等技术领域的学习心得,以及日常生活的思考与感悟。

© 2026 Yanche Blog. All rights reserved.

Powered by Astro