15.10.1 容器资源限制原理(cgroups与namespace)

容器资源限制依赖 Linux 内核的 cgroups(限额与统计)与 namespace(隔离与视图)。两者协同,让容器共享内核但资源可控、环境隔离。

原理草图(cgroups + namespace)#

文章图片

cgroups:资源控制与度量#

cgroups 将进程组织为层级树,控制 CPU、内存、IO、PIDs 等资源,并提供统计接口。

关键点
- 层级与继承:父组限制对子组生效。
- 控制器:cpu、memory、blkio、pids、cpuset 等。
- 硬/软限制:memory hard 触发 OOM,CPU quota/period 控制时间片比例。
- 统计:memory.currentcpu.stat 等用于监控。

安装与准备(cgroup 工具)#

# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y cgroup-tools

# RHEL/CentOS
sudo yum install -y libcgroup

# 验证
cgget -a | head

cgroup v2 检查与挂载#

# 检查当前 cgroup 版本
stat -fc %T /sys/fs/cgroup
# 预期:cgroup2fs 表示 v2,tmpfs 表示 v1 混合

# 若需手动挂载 cgroup v2(仅实验环境)
sudo mkdir -p /sys/fs/cgroup
sudo mount -t cgroup2 none /sys/fs/cgroup

示例:用 cgroup v2 限制 CPU/内存(手动)#

# 创建 cgroup
sudo mkdir -p /sys/fs/cgroup/demo.slice

# 限制 CPU:20%(quota=20000us/period=100000us)
echo "20000 100000" | sudo tee /sys/fs/cgroup/demo.slice/cpu.max

# 限制内存:256MB
echo $((256*1024*1024)) | sudo tee /sys/fs/cgroup/demo.slice/memory.max

# 启动进程加入 cgroup
sudo bash -c 'echo $$ > /sys/fs/cgroup/demo.slice/cgroup.procs; exec stress -c 2 -m 1 --vm-bytes 512M'
# 预期:CPU 占用受限,内存达到 256MB 触发 OOM

示例:Docker 中验证 cgroups 限制#

# 启动容器限制 CPU/内存
docker run -d --name cgrp-demo --cpus=0.5 --memory=256m --memory-swap=256m busybox sleep 300

# 查看容器 cgroup 路径
docker inspect --format '{{.Id}}' cgrp-demo

# 在宿主机查看限制(cgroup v2 示例)
CID=$(docker inspect --format '{{.Id}}' cgrp-demo)
CGPATH=$(find /sys/fs/cgroup -name "*$CID*" | head -n1)
cat $CGPATH/cpu.max
cat $CGPATH/memory.max
# 预期:cpu.max 为 "50000 100000" 左右;memory.max 约为 268435456

namespace:隔离与视图#

namespace 为进程提供系统资源的隔离视图,让容器拥有“独立”环境。

常见类型
- PID、NET、MNT、UTS、IPC、USER

示例:使用 unshare 创建隔离视图#

# 安装工具
sudo apt-get install -y util-linux

# 新建 PID/NET/UTS/MNT namespace 并进入 shell
sudo unshare --pid --net --uts --mount --fork /bin/bash

# 在新 namespace 中验证
hostname test-ns
ip addr     # 预期:无物理网卡(仅 lo)
ps -ef      # 预期:PID 从 1 开始

示例:Docker 中观察 namespace#

# 查看容器与宿主机 PID 对比
docker run -d --name ns-demo busybox sleep 300
docker exec ns-demo sh -c 'echo "Container PID:"; ps -ef'
# 宿主机上查看同进程
PID=$(docker inspect --format '{{.State.Pid}}' ns-demo)
ps -p $PID -o pid,cmd
# 预期:容器内 PID=1,宿主机 PID 为实际进程号

协同关系与运行时流程#

容器启动时(如 runc):
1. 创建/加入 namespace → 提供隔离视图;
2. 创建/加入 cgroups → 设置资源配额;
3. 启动容器 init 进程。

sequenceDiagram
  participant R as Runtime(runc)
  participant N as Namespace
  participant C as cgroups
  participant P as Container Init
  R->>N: create/join ns
  R->>C: create/join cgroup, set limits
  R->>P: start init
  P->>C: usage stats

排错与诊断#

  • 限制未生效
  • 检查 cgroup 版本与挂载:stat -fc %T /sys/fs/cgroup
  • Docker 参数是否传递:docker inspect --format '{{.HostConfig.Memory}}' <name>
  • 容器 OOM 退出
  • 查看日志:docker logs <name>
  • 宿主机:dmesg | tail -n 50 | grep -i oom
  • namespace 不隔离
  • 检查是否以 --pid=host--net=host 启动容器
  • 验证:docker inspect --format '{{.HostConfig.NetworkMode}}' <name>

练习#

  1. 使用 docker run 分别设置 --cpus=0.2--memory=128m,运行 stress 验证限制生效。
  2. 通过 unshare 创建新的 PID namespace,观察 ps -ef 的 PID 起始值。
  3. 在宿主机查找容器 cgroup 路径并读取 cpu.maxmemory.max,解释其含义。