4. Cgroups 资源限制:CPU、内存、磁盘 IO 与 cgroups v2

在多租户容器环境中,单一宿主机上运行着来自不同租户的多个容器实例。如果没有资源限制,一个“吵闹的邻居”容器可能耗尽宿主机 CPU、内存或磁盘 IO,导致其他租户的服务降级甚至中断。Cgroups(Control Groups)是 Linux 内核提供的一项核心功能,它允许系统管理员对进程组所使用的物理资源(CPU、内存、磁盘 IO、网络等)进行精细化的限制、记录和隔离。

本章将深入讲解 Docker 如何利用 cgroups 实现 CPU、内存和磁盘 IO 的限制,并介绍新一代 cgroups v2 的关键变化与配置方法。

4.1 CPU 资源限制

Docker 通过 --cpus、--cpu-shares、--cpuset-cpus 等参数控制容器对 CPU 的访问。这些参数最终会写入容器对应的 cgroup 控制文件中。

参数 作用 底层 cgroup 文件
--cpus 限制容器能使用的 CPU 核心数(如 1.5 表示 1.5 个核) cpu.cfs_period_us 和 cpu.cfs_quota_us
--cpu-shares 设置 CPU 相对权重(默认 1024),仅在 CPU 竞争时生效 cpu.shares
--cpuset-cpus 将容器绑定到指定的物理 CPU 核心(如 0-3) cpuset.cpus
关键机制:CFS 完全公平调度器
Docker 的 --cpus 参数基于 CFS 的带宽控制。内核以 cfs_period_us(通常 100000 微秒 = 100ms)为一个周期,容器在每个周期内最多能运行 cfs_quota_us 微秒。例如 --cpus=2 意味着 quota = 2 * period = 200000 微秒。
# 限制容器最多使用 1.5 个 CPU 核心
docker run -d --name app-cpu --cpus="1.5" nginx

# 查看该容器对应的 cgroup 配置
cat /sys/fs/cgroup/cpu/docker/<container-id>/cpu.cfs_quota_us
# 输出: 150000
cat /sys/fs/cgroup/cpu/docker/<container-id>/cpu.cfs_period_us
# 输出: 100000
实践建议: 对于延迟敏感型服务(如支付网关),优先使用 --cpuset-cpus 绑定专用核心,避免上下文切换带来的抖动。对于批处理任务,使用 --cpus 配合 --cpu-shares 实现弹性分配。

4.2 内存资源限制

内存限制是防止容器内存泄漏或突发占用导致宿主机 OOM(Out Of Memory)的关键防线。Docker 提供硬限制和软限制两种机制。

  • 硬限制(--memory / -m):容器使用的内存(包括物理内存和 swap)不得超过此值。超过时,内核会触发 OOM Killer 终止容器进程。
  • 软限制(--memory-reservation):当宿主机内存充足时,容器可以超过此值;当宿主机内存紧张时,内核会尝试将容器内存回收到此值以下。
  • Swap 限制(--memory-swap):控制容器可以使用的总内存+swap 大小。默认与 --memory 相同(即禁用 swap)。
# 限制容器最多使用 512MB 内存,且禁用 swap
docker run -d --name app-mem -m 512m --memory-swap 512m redis

# 查看内存限制
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes
# 输出: 536870912
OOM 优先级调整: 通过 --oom-kill-disable 可以禁止内核杀死容器进程,但必须配合 --memory 使用。不推荐在生产环境随意禁用 OOM Killer,否则可能导致宿主机内核 panic。

4.3 磁盘 IO 资源限制

磁盘 IO 限制在传统 cgroups v1 中通过 blkio 子系统实现,支持两种模型:

模型 参数 说明
权重模型 --blkio-weight 相对权重(10-1000),仅在 IO 竞争时生效
限速模型 --device-read-bps
--device-write-bps
--device-read-iops
--device-write-iops
按设备限制每秒字节数或 IO 次数
# 限制容器对 /dev/sda 的写入速度为 10MB/s
docker run -d --name app-io --device-write-bps /dev/sda:10mb nginx

# 查看对应的 blkio 限制
cat /sys/fs/cgroup/blkio/docker/<container-id>/blkio.throttle.write_bps_device
# 输出: 8:0 10485760   (8:0 是 /dev/sda 的主次设备号)
IO 限制的局限性: cgroups v1 的 blkio 子系统对异步写入(writeback)的限制效果有限,因为脏页回刷由内核线程执行,不直接归属于容器进程。cgroups v2 通过 IO 控制器改进了这一行为。

4.4 cgroups v2:统一层级与新一代资源控制

cgroups v2 是 Linux 内核 4.5 引入的重大重构,旨在解决 v1 中多个子系统层级不一致、管理混乱的问题。主流 Linux 发行版(如 Ubuntu 22.04+、Debian 12+、RHEL 9+)已默认使用 cgroups v2。Docker 从 20.10 版本开始全面支持 cgroups v2。

下图展示了 cgroups v1 与 v2 在架构上的核心差异:

cgroups v1 vs v2 架构对比 cgroups v1:多层级树 每个子系统独立挂载一棵树 进程可加入不同子系统的不同 cgroup 管理复杂,资源统计分散 cgroups v2:统一层级树 所有控制器挂载在同一棵树上 进程只能属于一个 cgroup 统一资源统计,原子性操作 cgroups v2 关键变化 1. 统一层级:所有控制器(cpu、memory、io)挂载在 /sys/fs/cgroup 下,不再有多个挂载点 2. 控制器启用:通过 cgroup.controllers 和 cgroup.subtree_control 文件动态管理 3. 无内部进程规则:非根 cgroup 必须包含子 cgroup 或进程,不能同时拥有两者 4. 压力感知(PSI):提供内存、IO、CPU 的压力指标,用于更智能的调度决策

4.5 cgroups v2 下的资源限制配置

在 cgroups v2 环境中,Docker 的配置参数基本保持不变,但底层文件路径和接口发生了变化。以下是在 cgroups v2 下配置资源限制的示例:

# 检查当前系统是否使用 cgroups v2
stat -fc %T /sys/fs/cgroup/
# 输出: cgroup2fs  (表示 v2)

# 运行容器并限制资源
docker run -d --name app-v2 \
  --cpus="2.0" \
  --memory="1g" \
  --memory-reservation="512m" \
  --device-write-bps /dev/sda:50mb \
  nginx

# 查看 cgroups v2 下的控制文件
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/cpu.max
# 输出: 200000 100000  (quota period)

cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/memory.max
# 输出: 1073741824

cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/io.max
# 输出: 8:0 wbps=52428800
迁移注意事项: 从 v1 迁移到 v2 时,需注意以下差异:
  • memory.limit_in_bytes 变为 memory.max
  • cpu.cfs_quota_us 和 cpu.cfs_period_us 合并为 cpu.max
  • blkio.throttle.write_bps_device 变为 io.max,且格式改为 major:minor wbps=值
  • 不再支持 --blkio-weight,需使用 io.weight 接口

4.6 多租户场景下的资源隔离策略

在实际的多租户 Docker 环境中,建议采用以下组合策略来确保隔离效果:

资源类型 推荐策略 说明
CPU --cpus + --cpuset-cpus 对关键租户绑定专用核心,其他租户使用 --cpus 限制上限
内存 --memory + --memory-reservation 硬限制防止 OOM,软限制提高内存利用率
磁盘 IO --device-write-bps + --device-read-bps 按设备限速,避免 IO 干扰
PID --pids-limit 限制容器内进程数,防止 fork 炸弹
重要提醒: 资源限制只是多租户隔离的一部分。必须结合命名空间(Namespace)实现视图隔离、Seccomp 限制系统调用、Capabilities 降权以及网络策略,才能构建完整的安全隔离边界。资源限制解决的是“性能隔离”问题,而非“安全隔离”。

通过合理配置 cgroups 资源限制,运维团队可以在共享基础设施上为每个租户提供可预测的服务质量(QoS),同时最大化硬件利用率。cgroups v2 的引入进一步简化了管理模型,并为未来的资源感知调度(如基于 PSI 指标的自动扩缩容)奠定了基础。