Docker 核心技术:Linux Cgroups
大家好,我是费益洲。Linux Cgroups 作为 Docker 的技术核心之一,主要作用就是限制、控制和统计进程的系统资源 (如 CPU、内存、磁盘 I/O 等)。容器的本质其实就是 Linux 的一个进程,限制、控制和统计容器的系统资源,其实就是限制、控制和统计进程的系统资源,本文将从 Linux 内核源码的层面,谈谈如何通过 Cgroups 实现限制系统资源。
本文中的的内核源码版本为linux-5.10.1
,具体的源码可以自行下载查看,本文只列举关键代码。
🔗 内核源码官方地址:www.kernel.org,linux-5.10.1 源码下载地址:linux-5.10.1.tar.xz
概念
Cgroups 的全称是 Control Groups,是 Linux 内核提供的一种机制,用于限制、控制和统计一组进程所使用的物理资源。它最早由 Google 工程师在 2006 年发起,最初称为"进程容器"(Process Containers),后来在 2007 年更名为 Cgroups,并在 2008 年合并到 Linux 2.6.24 内核中,2016 年 Linux 4.5 内核引入第二代(cgroup v2)。
特性 | cgroup v1 | cgroup v2 |
---|---|---|
设计 | 多层级树,子系统独立管理 | 单一层级树,统一资源管理 |
内存 | memory 子系统独立 | 整合内存、swap、内核内存 |
CPU | cpu 与 cpuacct 分离 | 统一通过 cpu 控制权重和上限 |
启动 | 旧版内核 | Linux 4.5+ 内核 |
与 Cgroups 相关的关键概念如下:
-
层级结构(Hierarchy)
- 树形组织,子级 cgroup 进程继承父级 cgroup 的限制(如
/sys/fs/cgroup/memory/father/child
child 初始继承 father 的限制)
- 树形组织,子级 cgroup 进程继承父级 cgroup 的限制(如
-
子系统(Subsystem)
- 每个子系统管理一类资源,具体可以通过
ls -al /sys/fs/cgroup/mygroup
查看,常用的子系统包括:子系统 功能 blkio 限制块设备 I/O 带宽(如磁盘读写) cpu 控制 cpu 时间分配 cpuacct 统计 CPU 使用情况 devices 控制设备访问权限(如禁止容器访问磁盘) freezer 挂起或恢复进程 memory 限制内存使用量,统计内存消耗 net_cls 标记网络数据包,配合 tc 实现网络限速 pids 限制进程数
- 每个子系统管理一类资源,具体可以通过
-
任务(Task)
- 进程或线程,可加入多个 cgroup(每个子系统层级仅属一个 cgroup)
-
文件系统接口
- 通过虚拟文件系统(挂载于 /sys/fs/cgroup)配置参数:
# 限制内存为 1GB echo 1G > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes # 将进程加入 cgroup echo 1234 > /sys/fs/cgroup/memory/mygroup/cgroup.procs
- 子级文件系统接口可以通过
mkdir
命令在父级文件系统接口目录下创建,并会自动创建并继承父级文件系统接口的配置
[root@master01 ~]# cd /sys/fs/cgroup/memory [root@master01 memory]# mkdir mygroup [root@master01 memory]# ls mygroup/ cgroup.clone_children memory.kmem.tcp.failcnt memory.numa_stat cgroup.event_control memory.kmem.tcp.limit_in_bytes memory.oom_control cgroup.kill memory.kmem.tcp.max_usage_in_bytes memory.pressure_level cgroup.procs memory.kmem.tcp.usage_in_bytes memory.qos_level memory.events memory.kmem.usage_in_bytes memory.reclaim memory.events.local memory.ksm memory.soft_limit_in_bytes memory.failcnt memory.limit_in_bytes memory.stat memory.flag_stat memory.low memory.swapfile memory.force_empty memory.max_usage_in_bytes memory.swap.max memory.force_swapin memory.memfs_files_info memory.swappiness memory.high memory.memsw.failcnt memory.usage_in_bytes memory.high_async_ratio memory.memsw.limit_in_bytes memory.use_hierarchy memory.kmem.failcnt memory.memsw.max_usage_in_bytes memory.wb_blkio_ino memory.kmem.limit_in_bytes memory.memsw.usage_in_bytes notify_on_release memory.kmem.max_usage_in_bytes memory.min tasks memory.kmem.slabinfo memory.move_charge_at_immigrate
⚠️ 不要直接修改根目录(/sys/fs/cgroup
)下的子系统配置
Cgroups 的生命周期和回收策略
Cgroups 的创建过程
进程结构体task_struct
的定义在文件linux-5.10.1/include/linux/sched.h
中,与 Cgroups 相关的关键数据结构如下所示:
struct task_struct {
// ...(省略部分代码)/* Control Group info protected by css_set_lock: */struct css_set __rcu *cgroups;/* cg_list protected by css_set_lock and tsk->alloc_lock: */struct list_head cg_list;// ...(省略部分代码)
}
- css_set:
- 包含进程组共享的子系统状态数组(subsys[CGROUP_SUBSYS_COUNT])
- 通过 tasks 链表关联所有绑定至此的进程
- list_head: 链入 css_set 的 tasks 链表
接下来从进程创建的过程,来说明进程的创建过程中,创建 Cgroups 的过程。创建进程的系统调用函数有三个:fork()、vfork()、clone()。当调用 fork()、vfork()、clone()时,最终都会调用同一个函数 kernel_clone(),和 Cgroups 创建关联的关键函数调用是 copy_process()
pid_t kernel_clone(struct kernel_clone_args *args)
{// ...(省略部分代码)// line 2456p = copy_process(NULL, trace, NUMA_NO_NODE, args);// ...(省略部分代码)
}
copy_process()函数和 Cgroups 创建关联的关键函数调用是有三个,cgroup_fork()、cgroup_can_fork()、cgroup_post_fork():
static __latent_entropy struct task_struct