Cgroup介绍
什么是Cgroup?
Linux内核官方文档描述
Control Groups provide a mechanism for aggregating/partitioning sets of tasks, and all their future children, into hierarchical groups with specialized behaviour.
Cgroup(Crontrol Groups)是Linux内核提供的一种将进程以及它们衍生出来的子进程
进行分组和隔离的机制。
Linux内核实现的Cgroup分为v1和v2,而由于v1版本的实现在前期没有一个整体上的规划,v1版整体的使用和维护成本上并不可观。
cgroup v2在设计上致力于解决这些问题,于Linux内核4.5
版本正式被引入,官方宣称可以用于生产环境,同时引入了cgroup namespace
。
本文主要讲解cgroup-v1版本,目前来说,cgroup-v1仍然是主流版本,使用者如systemd(默认v1,可切换为v2),docker。
Cgroup组成介绍
cgroup由2个组件组成:
- hierachry, 如上图所示,hierarchy通过继承关系让cgroup组之间形成树结构,每棵树都可以关联0个或多个subsystem。每一个cgroup hierarchy都包含了当前系统中的所有进程,即每一个进程必然属于hierarchy中的某个cgroup节点。原则上,一个进程在一个hierarchy中只能属于一个节点。
- subsystem, subsystem是内核控制进程的管理模块,目前一共有12种subsystem,subsystem有时也被称作”resource controller”,即资源控制器。每个subsystem只能和一个hierarchy绑定,所以系统中最多只能有12个绑定了subsystem的hierarchy,而不和subsystem绑定的hierarchy没有数量限制。
查看系统的subsystem配置
root@worker-1:cgroup $ cat /proc/cgroups | awk '{printf "%-15s %-15s %-15s %-15s\n", $1, $2, $3, $4}'
#subsys_name hierarchy num_cgroups enabled
cpuset 5 1 1
cpu 7 9 1
cpuacct 7 9 1
blkio 12 1 1
memory 11 1 1
devices 2 51 1
freezer 8 1 1
net_cls 4 1 1
perf_event 3 1 1
net_prio 4 1 1
hugetlb 10 1 1
pids 6 57 1
rdma 9 1 1
系统中关于subsystem的信息保存在/proc/cgroup
文件中:
字段名 | 含义 |
---|---|
subsys_name | subsystem名字 |
hierarchy | 该subsystem绑定的cgroup hierarchy id |
num_cgroup | 所关联hierarchy中cgroup数量 |
enabled | 是否启用 |
在大多数使用systemd
Linux发行版中,systemd已经将subsystem默认挂载在/sys/fs/cgroup
,我们可以通过mount
查看挂载信息:
root@worker-1:cgroup $ mount|grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
test on /root/cgroup type cgroup (rw,relatime,release_agent=/root/release_opr.sh,name=test)
查看当前进程所属cgroup
root@worker-1:cgroup $ cat /proc/$$/cgroup
17:name=test:/
12:blkio:/
11:memory:/
10:hugetlb:/
9:rdma:/
8:freezer:/
7:cpu,cpuacct:/user.slice/user-0.slice/session-1.scope
6:pids:/user.slice/user-0.slice/session-1.scope
5:cpuset:/
4:net_cls,net_prio:/
3:perf_event:/
2:devices:/user.slice
1:name=systemd:/user.slice/user-0.slice/session-1.scope
进程的cgroup信息可以在/proc/{pid}/cgroup
文件中查看,由于进程在每一个cgroup hierarchy
中必然属于某个节点,所有在这里看到的cgroup信息必然包含当前内核中所有的hierarchy.
如何管理CGroup Hierarchy?
为了专注于cgroup hierarchy
本身,接下来我们的操作暂时不涉及subsystem的使用。
创建一个Cgroup
root@worker-1:~ $ ls cgroup/
root@worker-1:~ $ mount -t cgroup -o none,name=test test ./cgroup
root@worker-1:~ $ ls ./cgroup/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
root@worker-1:~ $ wc -l ./cgroup/cgroup.procs
89 ./cgroup/cgroup.procs
我们创建一颗不跟任何subsystem
绑定的cgroup树,初始状态下当前系统中所有的进程都被包含在新创建的cgroup数中。
这里我们介绍一下新创建的cgroup中每个文件的含义:
- clone_children, 仅在绑定cpuset时有效, 文件内容为1时子cgroup会继承父cgroup的配置, 文件可写入。
- procs, 当前cgroup包含的所有进程。
- tasks, 当前cgroup中所有的线程id,将进程加入到某个cgroup后,属于该进程的所有线程也会自动包含进来,虽然cgroup v1支持一个进程的不同线程在不同的cgroup节点中,但是通常不建议这么用,cgroup v2也移除了这个功能,只支持按照进程配置。
- notify_on_release, 文件可写入,当文件内容为1时,会在cgroup退出(cgroup包含进程和子cgroup为空)时调用
release_agent
中记录的命令 - release_agent, 该文件只存在于
root cgroup
下,里面包含了cgroup退出时执行的命令,调用时会将cgroup的路径当做参数传入。 - sane_behavior, 该文件只存在于
root cgroup
下,用于控制cgroup->flag
一个叫CGRP_ROOT_SANE_BEHAVIOR
的位,可以控制开启和关闭系统某些特性。大部分Linux发行版下该特性都是默认关闭的,我对它的具体用法也不是特别的了解,如果感兴趣可以参考 内核提交记录
创建和删除子cgroup
创建子cgroup
root@worker-1:~ $ ls cgroup/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
root@worker-1:~ $ mkdir cgroup/child
root@worker-1:~ $ ls cgroup/child/
cgroup.clone_children cgroup.procs notify_on_release tasks
在cgroup挂载的路径下创建目录就在该节点下创建了一个子cgroup
删除子cgroup
root@worker-1:~ $ rmdir cgroup/child/
root@worker-1:~ $ ls cgroup/
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
删除创建的子目录就删除了子cgroup,但是删除需要满足条件:
- 删除子cgroup时,该cgroup必须不包含任何进程和子cgroup
添加进程
我们可以通过写入cgroup.procs或者cgroup.tasks
文件来将某个进程添加到当前cgroup中
- 在一个
cgroup hierarchy
中,系统中的一个进程必须要属于其中某一个节点(也就是某个cgroup)。所以新创建的cgroup会默认包含系统中所有的进程,同时也无法单纯的将某个进程从cgroup中移除,必须将进程移动到树中其他的cgroup节点才能将其从当前cgroup中移除。 - 新创建的进程会默认维持父进程的cgroup节点配置。
创建新的子cgroup,将当前bash进程添加到新建的cgroup中
root@worker-1:~ $ mkdir cgroup/child
root@worker-1:~ $ cd cgroup/
root@worker-1:cgroup $ cat cgroup.procs | grep $$ # 可以看到bash进程在root cgroup中
6281
root@worker-1:cgroup $ echo $$ > child/cgroup.procs # 将bash进程加入子cgroup
root@worker-1:cgroup $ cat cgroup.procs | grep $$ # root cgroup中找不到当前bash进程了
root@worker-1:cgroup $ cat child/cgroup.procs # 进程出现在子cgroup中,并且cat命令的进程也在子cgroup中
6281
7411
cgroup权限
将进程从一个cgroup移动到另一个cgroup,只需要有目标cgroup的权限即可,内核不会校验源cgroup的权限。并且普通用户只能操作自己的进程。
root@master:cgroup $ echo $$
24433
root@master:cgroup $ su user
user@master:/root/cgroup/cgroup$ echo $$
25670
user@master:/root/cgroup/cgroup$ echo 24433 > ./user/cgroup.procs
bash: echo: write error: Permission denied
user@master:/root/cgroup/cgroup$ echo $$ > ./user/cgroup.procs
release_agent
当一个cgroup中最后一个进程退出且没有子cgroup时,会触发cgroup的release
。若当前cgroup的notify_on_release
文件的内容为1,则会执行root cgroup
下的release_agent
中预设置的命令。
我们创建以下结构目录
root@worker-1:~ $ tree -L 3
.
├── cgroup
│ ├── cgroup.clone_children
│ ├── cgroup.procs
│ ├── cgroup.sane_behavior
│ ├── child
│ │ ├── cgroup.clone_children
│ │ ├── cgroup.procs
│ │ ├── notify_on_release
│ │ └── tasks
│ ├── notify_on_release
│ ├── release_agent
│ └── tasks
└── release_opr.sh
其中release_opr.sh
的内容如下:
#!/bin/bash
echo "$0,$1" >> /root/release_opr.log
child cgroup的notify_on_release为0时
root@worker-1:cgroup $ echo 0 > child/notify_on_release
root@worker-1:cgroup $ echo $$ > child/cgroup.procs
root@worker-1:cgroup $ echo $$ > ./cgroup.procs
root@worker-1:cgroup $ ls ../
cgroup release_opr.sh
当child cgroup退出时,由于notify_on_release为1,并没有触发release_agent中的预设的命令。
设置child cgroup的notify_on_release为1
root@worker-1:cgroup $ echo 1 > child/notify_on_release
root@worker-1:cgroup $ echo $$ > child/cgroup.procs
root@worker-1:cgroup $ echo $$ > ./cgroup.procs
root@worker-1:cgroup $ ls ../
cgroup release_opr.log release_opr.sh
root@worker-1:cgroup $ cat ../release_opr.log
/root/release_opr.sh,/child
当我们设置notify_on_release为1后,预设的命令触发,生成了日志文件。文件的内容为脚本路径,cgroup相对路径