cgroups
简介
cgroups是control groups的缩写,它是linux内核提供的一种可以管理进程对资源(cpu、内存、网络设备等)的使用的机制。cgroups被LXC(Linux Containers)用于实现虚拟化的资源管理手段,可以说是LXC的实现基础。cgroups主要的作用针对一组进程(进程组)声明一个控制组,在这个控制组下可以设置各种控制参数,然后通过不同的子系统(资源控制器)来解析各自的控制参数来对进程组内的进程的资源使用进行控制。
cgroups主要提供以下几个方面的功能:
- 限制资源使用。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会出发OOM(out of memory)
- 优先级控制。比如:可以使用cpu子系统为某个进程组分配特定cpu share
- 资源使用审计。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间
- 进程隔离。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间
- 进程控制。比如:使用freezer子系统可以将进程组挂起和恢复
概念
cgroups中有许多概念,这里集中列举如下:
- 任务(task),也就是系统中的一个进程
- 控制组(control group),控制组就是一组进程。cgroups中的资源控制都以控制组为单位。一个进程可以加入某个控制组,也可以从某个控制组转移到另一个控制组。一个进程组的进程可以使用cgroups以控制组为单位分配的资源,同时受到cgroups以控制组为单位设定的限制
- 层级(hierarchy),各个控制组实际上是通过树形的结构关联起来的,控制组树上的子节点会继承父节点的属性。
- 子系统(subsystem),一个子系统实际上就是一个资源控制器。前面提到cgroup会声明各种资源的使用限制,而具体负责执行这些限制的就是各个子系统,比如CPU子系统就是控制CPU时间分配的一个控制器。cgroups的子系统有很多,并且也可以支持新增。子系统必须附加(attach)到一个层级上才能起作用,一个子系统attach到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。
各个子系统
- cpu 子系统,主要限制进程的 cpu 使用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的 memory 使用量。
- blkio 子系统,可以限制进程的块设备 io。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
- ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。
使用示例
说了这么多概念和介绍,接下来看一下如果查看和操作cgroup的相关配置。
查看cgroup文件系统
cgroup在实现上把用户接口实现为文件系统的形式,我们可以通过执行mount -t cgroup
来查看cgroup挂载的文件系统:
~$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
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/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
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/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
可以看到,在/sys/fs下有一个cgroup的目录,这是cgroup默认挂载的位置,下边有许多子目录,比如cpu、cpuset等等,这些就是不同的子系统的挂载点。每个挂载点即表示一棵与对应的子系统关联的cgroup树。
在一棵cgroup树里面,会包含系统中的所有进程,但每个进程只能属于树的其中一个节点(进程组)。系统中可以有很多cgroup树,每棵树都和不同的subsystem关联,一个进程可以属于多棵树,即一个进程可以属于多个进程组,只是这些进程组和不同的subsystem关联。
可以通过查看/proc/cgroups文件来确认当前系统支持的子系统,以及各个子系统绑定的cgroup树,比如:
~$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 11 1 1
cpu 3 64 1
cpuacct 3 64 1
blkio 8 64 1
memory 9 104 1
devices 5 64 1
freezer 10 4 1
net_cls 6 1 1
perf_event 7 1 1
net_prio 6 1 1
hugetlb 4 1 1
pids 2 68 1
输出结果的第一列是子系统的名称,比如cpuset等等,第二列是子系统绑定的cgroup树id,可以注意到cpu和cpuacct绑定到了同一个cgroup树,也就是说它们在/sys/fs/cgroup下挂载的路径是相同的,实际上是/sys/fs/cgroup/cpu,cpuacct,然后cgroup目录下还会有两个名为cpu和cpuacct的链接,链接到它们共同的挂载点。第三列和第四列的输出分别表示cgroup树下有多少控制组(也就是树的节点个数)以及是否开启(可以通过设置内核的启动参数“cgroup_disable”来控制子系统的开启)。
更新cgroup配置
假如我们想要控制某个进程的cpu使用不能超过30%,我们就可以这样操作:
1、在/sys/fs/cgroup/cpu下新建一个名为“demo”的目录,这个操作实际上就是在cpu这个子系统绑定的cgroup树下面创建了一个新的节点,也就是控制组。
创建完demo目录后,我们可以看到demo目录下会自动新增很多文件,实际上这些文件在上一层的cpu目录下也存在。这些自动生成的文件实际上就是cgroup的各个控制属性,而前面提到过,作为cgroup树的子节点会继承父节点的特定属性,所以这些自动生成的文件也就是从cpu目录继承过来的。
各个文件的作用简单介绍如下:
- cgroup.clone_children:这个文件只对cpuset(subsystem)有影响,当该文件的内容为1时,新创建的cgroup将会继承父cgroup的配置,即从父cgroup里面拷贝配置文件来初始化新cgroup
- cgroup.procs:当前cgroup中的所有进程ID,系统不保证ID是顺序排列的,且ID有可能重复
- release_agent:里面包含了cgroup退出时将会执行的命令,系统调用该命令时会将相应cgroup的相对路径当作参数传进去。 注意:这个文件只会存在于root cgroup下面,其他cgroup里面不会有这个文件。
- tasks当前cgroup中的所有线程ID,系统不保证ID是顺序排列的
2、将cpu限额设置为20%,这里需要修改cpu.cfs_quota_us文件里的内容:
cat /sys/fs/cgroup/cpu/demo/cpu.cfs_quota_us
-1
echo 20000 > /sys/fs/cgroup/cpu/demo/cpu.cfs_quota_us
3、将要限制的进程id(比如3529)添加到cgroup下,这里需要修改tasks文件的内容:
echo 3529 >> /sys/fs/cgroup/cpu/haoel/tasks
这样一来3529这个进程就会被cgroup的cpu子系统限制cpu的使用百分比为20%了。
值得注意的是这里将进程id加入了tasks文件而不是cgroup.procs下,这两者的区别在于tasks支持线程级别的控制,而cgroup.proc是进程级别的控制。
cgroups的实现
cgroups通过实现cgroup文件系统来为用户提供管理cgroup的工具,而cgroup文件系统是基于Linux VFS实现的。相应地,cgroups为控制文件定义了相应的数据结构cftype,对其操作由cgroup文件系统定义的通过操作捕获,再调用cftype定义的具体实现。所以说上述的例子中我们对相关的文件进行操作时,实际上被cgroup捕获并转换成了对内核中cgroup相应的数据结构的操作,也就完成了用户态操作到内核态操作的映射。