docker容器技术原理:Cgroup
1、cgroups简介
cgroups(全称:control groups)是 Linux 内核的一个功能,它可以实现限制进程或者进程组的资源(如 CPU、内存、磁盘 IO 等)。
在 2006 年,Google 的工程师( Rohit Seth 和 Paul Menage 为主要发起人) 发起了这个项目,起初项目名称并不是cgroups,而被称为进程容器(process containers)。在 2007 年cgroups代码计划合入Linux 内核,但是当时在 Linux 内核中,容器(container)这个词被广泛使用,并且拥有不同的含义。为了避免命名混乱和歧义,进程容器被重名为cgroups,并在 2008 年成功合入 Linux 2.6.24 版本中。cgroups目前已经成为 systemd、Docker、Linux Containers(LXC) 等技术的基础。
cgroups 功能及核心概念
cgroups 主要提供了如下功能。
- 资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
- 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
- 审计:计算控制组的资源使用情况。
- 控制:控制进程的挂起或恢复。
cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。
- 子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
- 控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
- 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。
实战核心概念概念解析表
核心概念 | 在实例中的表现 | 关键验证命令 | 结果分析 |
---|---|---|---|
子系统 (资源控制器) | cpu 和 memory 子系统生效 |
cat cpu.max cat memory.max |
输出: 100000 100000 (1核) 629145600 (600MB) |
控制组 (策略集合) | Docker 创建名为 docker-d3adb33fd3a.scope 的控制组 |
cat cgroup.procs |
显示容器进程PID: 8123 8150 |
层级树 (继承结构) | 继承 system.slice 父级策略 | cat ../system.slice/cpu.max |
父级通常为: max 100000 (不限制) → 子组覆盖了父级策略 |
总结关键结论:
- 子系统是引擎
CPU
/memory
等子系统通过/sys/fs/cgroup/
中的文件提供资源控制能力 - 控制组是载体
Docker 自动为每个容器创建专属控制组目录承载策略 - 层级树是骨架
控制组按system.slice → docker-xxx.scope
形成继承链 - 策略继承规则
2、Cgroups 核心概念与实战对照表
概念类型 | Cgroups术语 | Docker参数映射 | Linux内核文件示例 | 功能解释 |
---|---|---|---|---|
资源限制 | CPU限额 | --cpus=1.5 |
/sys/fs/cgroup/cpu.max |
限制容器使用1.5个CPU核心 |
内存硬限制 | -m 500m |
/sys/fs/cgroup/memory.max |
内存使用上限500MB(触发OOM) | |
内存软限制 | --memory-reservation=300m |
/sys/fs/cgroup/memory.high |
内存压力预警线300MB(优先回收) | |
PID数量限制 | --pids-limit=100 |
/sys/fs/cgroup/pids.max |
容器内进程数≤100 | |
优先级策略 | CPU权重 | --cpu-shares=1024 |
/sys/fs/cgroup/cpu.weight |
默认1024,值越高分配CPU越多 |
IO权重 | --blkio-weight=500 |
/sys/fs/cgroup/io.bfq.weight |
相对权重控制磁盘带宽 | |
资源审计 | 内存使用统计 | docker stats |
/sys/fs/cgroup/memory.stat |
包含cache/RSS/swap等详细数据 |
CPU时间统计 | docker stats |
/sys/fs/cgroup/cpu.stat |
显示总CPU时间及限流次数 | |
进程控制 | 容器暂停 | docker pause |
/sys/fs/cgroup/cgroup.freeze |
冻结容器内所有进程 |
OOM事件监控 | docker events |
/sys/fs/cgroup/memory.events |
记录OOM发生次数及时间 |
3、cgroups 实战操作步骤表
以CPU子系统为例:
操作类型 | 手动命令 | 实际用途 | Docker等价命令 |
---|---|---|---|
1. 创建cgroup | sudo mkdir /sys/fs/cgroup/cpu/demo |
CPU限制组创建 | docker run -it --rm --cgroup-parent=/demo/ alpine |
2. 设置限制 | echo 100000 > /sys/fs/cgroup/cpu/demo/cpu.max |
限制1核CPU | docker run --cpus=1.0 ... |
3. 加入进程 | echo $PID > /sys/fs/cgroup/cpu/demo/cgroup.procs |
控制特定进程 | 自动处理 |
4. 动态修改 | echo 50000 > /sys/fs/cgroup/cpu/demo/cpu.max |
CPU减半 | docker update --cpus=0.5 <container> |
5. 监控资源 | cat /sys/fs/cgroup/memory/demo/memory.stat |
查看内存统计 | docker stats --no-stream |
6. 清理cgroup | sudo rmdir /sys/fs/cgroup/cpu/demo/ |
删除控制组 | 容器停止时自动清理 |
4、深度解析 Docker 的动作序列(以 CPU+内存子系统为例)
1 | flowchart TB |
可以用城市水电系统深化类比:控制组相当于每户的电表箱(集中展示电流/电压监控接口),供电局(内核)通过线路(挂载点)将服务接入,住户(容器进程)用电行为直接触发电表(子系统控制器)计数。
验证:手工在宿主机模拟容器限制
1 | # 1. 创建控制组目录(模拟容器) |
进程的CPU使用率会被压制在50%,内存超过10MB会被OOM killer终止
❗ 关键规则
必然创建
只要设置了资源限制(哪怕不显式设置,Docker 默认也会给限制),必然在子系统目录下创建控制组创建位置
在 已挂载的子系统目录下 创建:1
/sys/fs/cgroup/子系统名称/docker/容器ID
一对一绑定
每个容器对应 多个控制组目录(每个子系统一个):1
2# 查看容器在各类子系统中的控制组
ls /sys/fs/cgroup/*/docker/$(docker inspect -f '{{.Id}}' demo)
*本质:*控制组是子系统能力的客户端接口*,而非服务端实例*。就像插头(控制组)插入电源(子系统)才能生效,但电源始终存在于电网(内核)中!