讲述Docker底层原理的第二篇文章,本文讲述了Docker和K8S中限制容器硬件资源的技术:Linux内核中的Cgroups;
系列文章:
源代码:
Docker原理实战-2:Cgroups
Cgroups
Namespace用来隔离自己单独的空间,而Cgroups(Control Groups)用来限制每个空间的硬件资源,如:CPU、内存等;
通过Cgroups可以方便的限制某个进程的资源占用,并且可以实时的监控进程和统计信息;
Cgroups三大组件
Cgroups中一共包括了3大组件:
① cgroup
对进程进行分组管理的一种机制,即:一个cgroup中包含了一组进程;
而我们可以在整个cgrouo上增加Linux Subsystem的各种参数配置,将一整组进程和一整组subsystem的系统配置关联起来!
② subsystem
subsystem是一组资源控制的模块,包括了:
| 名称 | 说明 |
|---|---|
| blkio | 设置对块设备(比如硬盘)输入输出的访问控制; |
| cpu | 设置 cgroup 中进程的 CPU 被调度的策略; |
| cpuacct | 统计 cgroup 中进程的 CPU 占用 ; |
| cpuset | 在多核机器上设置 cgroup 中进程可以使用的 CPU 和内存(此处内存仅使用于UMA 架构); |
| devices | 控制 cgroup 中进程对设备的访问; |
| freezer | 用于挂起( suspend )和恢复( resume) cgroup 中的进程; |
| memory | 控制 cgroup 中进程的内存占用; |
| net_cls | 用于将 cgroup 中进程产生的网络包分类,以便 Linux 的 tc (traffic controller )可以根据分类区分出来自某个 cgroup 的包并做限流或监控; |
| net_prio | 设置 cgroup 中进程产生的网络流量的优先级; |
| ns | 这个 subsystem 比较特殊,它的作用是:使 cgroup 中的进程在新的 Namespace 中 fork 新进程 ( NEWNS )时,创建出一个新的 cgroup,这个 cgroup 包含新的 Namespace 中的进程; |
每个 subsystem 会关联到定义了相应限制的 cgroup 上,并对这个 cgroup 中的进程做相应的限制和控制!
可以通过安装 cgroup 命令行工具查看当前内核支持哪些subsystem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 root@jasonkay:~# apt install cgroup-tools(原:cgroup-bin)
root@jasonkay:~# lssubsys -a
cpuset
cpu,cpuacct
blkio
memory
devices
freezer
net_cls,net_prio
perf_event
hugetlb
pids
rdma
③ hierarchy
hierarchy的功能是将一组croup串成一个树状结构,通过这个树状结构,可以实现 cgroups 的继承;
例如:
系统对一组定时任务 cgroup-1 限制了CPU使用率,而其中的一个定时任务还需要限制磁盘IO;
为了避免影响到其他进程,可以创建继承自 cgroup-1的cgroup-2,并设置 cgroup-2 限制磁盘IO;
三个组件的关系
Cgroups主要是通过cgroup、subsystem和hierarchy三个组件协作完成的;
三个组件的关系:
- 系统在创建了新的 hierarchy 之后,系统中所有的进程都会加入这个 hierarchy 的 cgroup根节点,这个 cgroup 根节点是 hierarchy 默认创建的;在 hierarchy 中创建的 cgroup 都是这个 cgroup 根节点的子节点;
- 一个 subsystem 只能附加到 一 个 hierarchy 上面;
- 一个 hierarchy 可以附加多个 subsystem;
- 一个进程可以作为多个 cgroup 的成员,但是这些 cgroup 必须在不同的 hierarchy 中;
- 一个进程 fork 出子进程时,子进程是和父进程在同一个 cgroup 中的,也可以根据需要将其移动到其他 cgroup 中;
Linux Kernel接口
在Cgroups中,hierarchy是一个树状的结构;
因此,Linux内核为了使 Cgroups 的配置更加直观,直接通过虚拟的树状文件系统对 Cgroups 进行配置;
下面来实际创建一个 Cgroups;
创建并挂载一个hierarchy
首先,创建并挂载一个hierarchy(cgroup树):
1 | ################### 创建cgroup ################### |
完成hierarchy挂载后,目录下会生成一些默认文件,这些文件就是这个hierarchy中的cgroup根节点的配置:
① cgroup.clone_children
cpuset 的 subsystem 会读取这个配置文件:
如果这个值是1(默认是 0),子 cgroup 才会继承父 cgroup 的 cpuset 的配置;
② cgroup.procs
树中当前节点 cgroup 中的进程组ID;
现在的位置是在根节点,这个文件中会有现在系统中所有进程组的 ID!
例如:
1 | root@jasonkay:~# cat ./cgroup-test/cgroup.procs |
③ notify_on_release 和 release_agent
notify_on_release 和 release_agent 会一起使用:
notify_on_release 标识当这个 cgroup 最后一个进程退出的时候是否执行了 release_agent:
release_agent 则是一个路径,通常用作进程退出之后自动清理掉不再使用的 cgroup;
④ tasks
tasks 标识该 cgroup 下面的进程 ID:
如果把一个进程 ID 写到 tasks 文件中,便会将相应的进程加入到这个 cgroup 中!
扩展cgroup子节点
然后在刚刚创建好的 hierarchy 上的 cgroup 根结点上扩展出两个子 cgroup:
1 | # 当前路径: |
可以看到:
在一个 cgroup 的目录下创建子目录时,内核会将该目录直接标记为这个cgroup的子目录,并继承父 cgroup 的属性!
在cgroup中添加和移动进程
一个进程在一个Cgroups的hierarchy中,只能在一个cgroup节点上存在,系统进程都会默认在根节点上存在!
可以将进程移动到其他cgroup节点,只需要将进程的PID写入到对应cgroup节点的task文件即可!
例如:
1 | ################### 在cgroup中添加和移动进程 ################### |
可以看到当前6758进程,已经被加入到cgroup-1中!
通过subsystem限制cgroup中的进程资源
上面在创建hierarchy时,并没有关联到任何的subsystem,因此没办法通过该hierarchy中的cgroup对进程的资源进行限制;
注:系统默认已经为每个subsystem创建了默认的hierarchy;
例如:memory的hierarchy:
1
2 root@jasonkay:~/cgroup-test/cgroup-1# mount | grep memory
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)可以看到,
/sys/fs/cgroup/memory目录便是挂在了memory subsystem的hierarchy上!
下面通过在memory subsystem的hierarchy中创建cgroup,限制进程占用内存!
过程如下:
1 | ################### 通过subsystem限制cgroup中进程的资源 ################### |
注:测试需要使用stress命令;
安装:
1 sudo apt install stress
下面创建对进程内存进行限制:
1 | # 创建cgroup |
可以看到,通过cgroup,成功将stress进程最大内存占用限制到了100M!
注1:试验结束后,将stress进程kill掉!
注2:试验结束后,将目录umount:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 查看挂载情况:
root@jasonkay:~# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
……
cgroup-test on /root/cgroup-test type cgroup (rw,relatime,name=cgroup-test)
# 取消挂载:
root@jasonkay:~# umount ./cgroup-test
# 再次查看挂载情况:
root@jasonkay:~# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
…… # 已不存在cgroup-test注3:清除内存限制配置:
我们将当前进程加入了
test-limit-memory中,因此,在删除这个目录之前,需要将当前进程重新加入至其他hierarchy中:
1
2
3
4
5
6
7
8 # 切换到默认内存限制hierarchy
cd /sys/fs/cgroup/memory/
# 将当前进程放入默认内存限制tasks
echo $$ >> tasks
# 删除目录(它会将这个目录下的全部文件也都删除!)
rmdir test-limit-memory/
Docker是如何使用Cgroups的
Docker就是通过Cgroups实现容器资源限制和监控的;
1 | # 使用 -m 设置内存限制 |
可以看到,Docker通过为每个容器创建cgroup来配置资源限制和资源监控!
注:试验结束别忘了关闭容器!
通过Go使用Cgroups
下面我们通过Go代码来配置Cgroups,以实现限制容器内存;
chapter2_basic/cgroups/cgroup_demo.go
1 | package main |
执行代码并查看:
1 | pwd # /root/workspace/my_docker/chapter2_basic |
可以看到,只占用了100M内存!
注:最后别忘了清理cgroup配置:
1 rmdir /sys/fs/cgroup/memory/testmemorylimit/
小结
本篇主要讲述了Docker和K8S中限制容器硬件资源的技术:Cgroups;
Cgroups主要包括三个部分:
- cgroup;
- subsystem;
- hierarchy;
最后通过Shell操作和Go代码,分别进行了进程的内存限制操作;
本系列的下一小节将讲述和 Docker 存储相关的基本知识:UnionFS;
附录
系列文章:
源代码: