Docker CGroup 资源限制原理
核心定义 CGroup(Control Groups)是 Linux 内核提供的资源限制、资源统计和资源隔离机制。Docker 通过 CGroup 为容器进程设置 CPU、内存、磁盘 IO、进程数量等资源边界,避免单个容器无限制消耗宿主机资源。
1. 为什么容器需要 CGroup
容器本质上是宿主机上的一组普通进程。Namespace 负责隔离进程看到的系统视图,例如 PID、网络、挂载点;CGroup 负责限制这些进程能使用多少资源。
如果没有 CGroup,容器可能出现以下问题:
- 某个容器占满全部 CPU,影响宿主机和其他容器。
- 某个容器耗尽内存,触发宿主机 OOM。
- 某个容器疯狂创建进程,耗尽宿主机 PID。
- 某个容器持续写磁盘,拖慢其他服务 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 行为
容器超过内存限制时,常见结果是:
- 内核发现该 cgroup 内存超过限制。
- 触发 cgroup OOM。
- 选择并杀掉容器内的进程。
- 如果主进程被杀,容器退出。
可以通过以下命令查看容器是否因 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
适用场景:
- 数据库、中间件等对 CPU 抖动敏感的服务。
- 需要减少跨 NUMA 或跨核心调度影响的场景。
- 需要为关键进程保留特定 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,可能导致:
- 宿主机 PID 被耗尽。
- 新进程无法创建。
- SSH、监控 Agent、业务进程启动失败。
- 宿主机可用性下降。
因此,生产容器建议设置合理的 --pids-limit。
8. Docker 参数与 CGroup 控制项映射
| Docker 参数 | 控制资源 | 类型 | 常见用途 |
|---|---|---|---|
--memory / -m | 内存 | 硬限制 | 限制容器最大内存 |
--memory-swap | 内存 + Swap | 硬限制 | 控制是否允许使用 Swap |
--cpus | CPU | 硬限制 | 限制最大 CPU 使用量 |
--cpu-shares | CPU | 权重 | CPU 竞争时按比例分配 |
--cpuset-cpus | CPU 核心 | 绑核 | 固定容器运行核心 |
--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、镜像和运行时机制的整体入口。
