本文参考网上一些资料,结合实际应用,简要介绍一下cgroup。

为什么要有cgroup

Linux系统中经常有个需求就是希望能限制某个或者某些进程的分配资源。也就是能完成一组容器的概念,在这个容器中,有分配好的特定比例的cpu时间,IO时间,可用内存大小等。于是就出现了cgroup的概念,cgroup就是controller group,可用来限制、记录、隔离进程组的物理资源,最初由google的工程师提出,后来被整合进Linux内核中。

cgroup的功能

cgroup是将任意进程进行分组化管理的Linux内核功能。cgroup本身提供将进程进行分组化管理的功能和接口的基础结构。实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了五大功能:

  1. 资源限制(Resource limiting ):比如memory子系统可以为进程组设定一个memory使用上限,进程组使用的内存达到限额再申请内存,就会出发OOM(out of memory)
  2. 优先级控制(Prioritization):比如可以使用cpu子系统为某个进程组分配cpu share
  3. 资源统计(Accounting ):比如使用cpuacct子系统记录某个进程组使用的cpu时间
  4. 进程组隔离(Isolation):比如使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间
  5. 进程组控制(Control):比如使用freezer子系统可以将进程组挂起和恢复

cgroup相关概念

Task(任务): task就是表示系统的一个进程。
Cgroup(控制组): 资源控制的单位,表示一整个任务组,每个任务组被划分相应的资源,包含了一个或者多个子系统。一个任务既可以加入到这个 cgroup 中,也可以迁移到另外一个 cgroup 中去。
Subsystem(子系统): 一个资源调度器(Resource Controller,控制着CPU,内存,访问,输入输出等资源和权限。
Hierarchy(层级树):一种操作系统的组织结构,可以理解为是一个 cgroup 树,将 cgroup 串成树状结构,通过虚拟端口暴露给用户。

subsystem

用于控制cgroup中的进程行为的内核组件,可以在/proc/cgroups查看所有支持的subsystem,subsystem也别称为resource controller;第二列为croup id;第三列为cgroup中进程数目。

#cat /proc/cgroups
subsys_name    hierarchy   num_cgroups enabled
    cpuset          8           6           1
    cpu             7           105         1
    cpuacct         7           105         1
    blkio           5           105         1
    memory          3           327         1
    devices         6           106         1
    freezer         4           6           1
    net_cls         2           6           1
    perf_event      11          6           1
    net_prio        2           6           1
    hugetlb         9           6           1
    pids            12          106         1
	rdma            10          1           1

也可以通过lssubsys来查看。
在这里插入图片描述

hierarchy

由cgroup组成的层级树,每个hierarchy都对应一个cgroup虚拟文件系统,每个hierarchy都有系统上的所有task,此外低level的hierarchy不能超过高level设定的资源上限。每次在系统中创建新层级(hierarchy)时,该系统中的所有任务都是那个层级的默认cgroup(我们称之为root cgroup,此cgroup在创建层级时自动创建,后面在该层级中创建的cgroup都是此cgroup的后代)的初始成员;

在这里插入图片描述

cgroup文件系统接口

cgroup以文件的方式提供应用接口,我们可以通过 mount 命令来查看 cgroups 默认的挂载点。系统默认会挂载cgroup,路径为/sys/fs/cgroup,查看当前系统挂载的cgroup,可以看到在默认路径下挂载了所有的子系统。后续可以直接使用下述hierarchy作为父hierarchy。进程的cgroup可以在/proc/$pid/cgroup文件中查看。

#mount|grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)

第一行的 tmpfs 说明 /sys/fs/cgroup 目录下的文件都是存在于内存中的临时文件。
第二行的挂载点 /sys/fs/cgroup/systemd 用于 systemd 系统对 cgroups 的支持。
其余的挂载点则是内核支持的各个子系统的根级层级结构。
需要注意的是,在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。换句话说,系统是不建议我们在 /sys/fs/cgroup 目录下创建新的目录并挂载其它子系统的。这一点与之前的操作系统不太一样。
看一下 /sys/fs/cgroup 目录及其子目录下都是些什么:
在这里插入图片描述

/sys/fs/cgroup 目录下是各个子系统的根目录。我们以 memory 子系统为例,看看 memory 目录下都有什么:
在这里插入图片描述

这些文件就是 cgroups 的 memory 子系统中的根级设置。比如 memory.limit_in_bytes 中的数字用来限制进程的最大可用内存,memory.swappiness 中保存着使用 swap 的权重等等。

既然 cgroups 是以这些文件作为 API 的,那么我就可以通过创建或者是修改这些文件的内容来应用 cgroups。具体该怎么做呢?比如我们怎么才能限制某个进程可以使用的资源呢?接下来我们就通过简单的 demo 来演示如何使用 cgroups 限制进程可以使用的资源。

查看进程所属的 cgroup

可以通过 /proc/[pid]/cgroup 来查看指定进程属于哪些 cgroup:
在这里插入图片描述

每一行包含用冒号隔开的三列,他们的含义分别是:

  1. cgroup 树的 ID, 和 /proc/cgroups 文件中的 ID 一一对应。
  2. 和 cgroup 树绑定的所有 subsystem,多个 subsystem 之间用逗号隔开。这里 name=systemd 表示没有和任何 subsystem 绑定,只是给他起了个名字叫 systemd。
  3. 进程在 cgroup 树中的路径,即进程所属的 cgroup,这个路径是相对于挂载点的相对路径。
    可以使用cgroup-bin工具包中的cgexec来实现限制进程可以使用的资源。

cgroup相关规则

  1. 一个hierarchy可以有一个或多个subsystem,这个从/sys/fs/cgroup中可以看出来cpu和cpuacct可以同属于一个hierarchy,而memory则仅属于一个hierarchy;
  2. 一个subsystem不能挂载到一个已经挂载了不同subsystem的hierarchy上;
  3. 一个task不能同时存在于同一个hierarchy下的两个cgoup中,但可以存在于不同类型的hierarchy中;如下例中,在hierarchy memory中创建2个cgroup mem1和mem2,可以看到将当前bash进程写入到mem2/tasks之后,mem1/tasks中的内容就会被清空。
    注:删除cgroup之前需要退出所有attach到该cgroup的进程,如下面的进程为bash,exit退出即可。
[root@ memory]# echo $$
9439
[root@ memory]# echo $$ > mem1/tasks 
[root@ memory]# cat mem1/tasks 
9439
9680
[root@ memory]# echo $$ > mem2/tasks 
[root@ memory]# cat mem2/tasks 
9439
9693
[root@ memory]# cat mem1/tasks 
[root@ memory]#
相同类型subsystem的hierarchy为同一个hierarchy,如下例中创建一个包含memory subsystem的hierarchy,它与/sys/fs/cgroup下面的memory是一致的,在cgrp1中创建一个名为mem1的cgroup。在/sys/fs/cgroup/memory下可以看到新创建的mem1
[root@ cgroup]# mount -t cgroup -o memory mem cgrp1/
[root@ cgroup]# cd cgrp1/
[root@ cgrp1]# mkdir mem1
?子进程会继承父进程的hierarchy,但可以将子进程调整到其他cgroup。下例中可以看到子进程同样受到父进程hierarchy的限制
[root@ mem1]# echo $$
10928
[root@ mem1]# echo $$>tasks
[root@ mem1]# cat /proc/10928/cgroup 
11:devices:/user.slice
10:perf_event:/
9:pids:/user.slice
8:freezer:/
7:cpuacct,cpu:/
6:hugetlb:/
5:memory:/mem1
4:cpuset:/
3:net_prio,net_cls:/
2:blkio:/
1:name=systemd:/user.slice/user-1000.slice/session-1.scope

[root@ mem1]# bash #创建一个子进程
[root@ mem1]# echo $$
11402
[root@ mem1]# cat /proc/11402/cgroup 
11:devices:/user.slice
10:perf_event:/
9:pids:/user.slice
8:freezer:/
7:cpuacct,cpu:/
6:hugetlb:/
5:memory:/mem1
4:cpuset:/
3:net_prio,net_cls:/
2:blkio:/
1:name=systemd:/user.slice/user-1000.slice/session-1.scope

从上面可以看到,subsystem相同的hierarchy是被重复使用的;当创建一个新的hierarchy时,如果使用的subsystem被其他hierarchy使用,则会返回EBUSY错误。如/sys/fs/cgroup中已经在cpuset和memory中单独使用了名为cpuset和memory的subsystem,则重新创建一个包含了它们的hierarchy会返回错误。

[root@ cgroup]# mount -t cgroup -o cpuset,memory mem1 cgrp1/
mount: mem1 is already mounted or /cgroup/cgrp1 busy

可以创建没有subsystem的hierarchy,默认包含如下文件:

  1. tasks:包含了attach到该cgoup的pid。如上述例子中所示,将进程pid写入到该文件会将进程转移到该cgroup。(cgroupv2中移除了该文件,使用cgroup.procs)
  2. cgroup.procs:包含了线程的group id。将一个线程的group id写入该文件,会将该group下的所有线程转移到该cgroup。(cgroupv2中该文件的定义与cgroupv1中tasks文件的意义类似)
  3. notify_on_release:flag文件,用来判断是否执行release_agent
  4. release_agent:如果cgroup中使能notify_on_release,cgroup中的最后一个进程被移除,最后一个子cgroup也被删除时,cgroup会主动通知kernel。接收到消息的kernel会执行release_agent文件中指定的程序
[root@ cgroup]# mount -t cgroup -onone,name=cgrp1 mycgroup  cgrp1/
[root@ cgroup]# cd cgrp1/
[root@ cgrp1]# ll
total 0
-rw-r--r--. 1 root root 0 Jan  2 23:54 cgroup.clone_children
--w--w--w-. 1 root root 0 Jan  2 23:54 cgroup.event_control
-rw-r--r--. 1 root root 0 Jan  2 23:54 cgroup.procs
-r--r--r--. 1 root root 0 Jan  2 23:54 cgroup.sane_behavior
-rw-r--r--. 1 root root 0 Jan  2 23:54 notify_on_release
-rw-r--r--. 1 root root 0 Jan  2 23:54 release_agent
-rw-r--r--. 1 root root 0 Jan  2 23:54 tasks

创建hierarchy 层级并挂载子系统

1.挂载一颗和所有subsystem关联的cgroup树到/sys/fs/cgroup

mount -t cgroup xxx /sys/fs/cgroup

2.挂载一颗和cpuset subsystem关联的cgroup树到/sys/fs/cgroup/cpuset

mkdir /sys/fs/cgroup/cpuset
mount -t cgroup -o cpuset xxx /sys/fs/cgroup/cpuset

3.挂载一颗与cpu和cpuacct subsystem关联的cgroup树到/sys/fs/cgroup/cpu,cpuacct

mkdir /sys/fs/cgroup/cpu,cpuacct
mount -t cgroup -o cpu,cpuacct xxx /sys/fs/cgroup/cpu,cpuacct

4.挂载一棵cgroup树,但不关联任何subsystem

mkdir /sys/fs/cgroup/systemd
mount -t cgroup -o none,name=systemd xxx /sys/fs/cgroup/systemd

cgconfig.conf

在使用yum install libcgroup安装了cgroups模块之后,在 /etc/cgconfig.conf 文件中会自动生成 cgroups 子系统的挂载点:

mount {
    cpuset  = /cgroup/cpuset;
    cpu = /cgroup/cpu;
    cpuacct = /cgroup/cpuacct;
    memory  = /cgroup/memory;
    devices = /cgroup/devices;
    freezer = /cgroup/freezer;
    net_cls = /cgroup/net_cls;
    blkio   = /cgroup/blkio;
}

上面的每一条配置都等价于展开的 mount 命令,例如mount -t cgroup -o cpuset cpuset /cgroup/cpuset。这样系统启动之后会自动把这些子系统挂载到相应的挂载点上。

参考文献

https://www.cnblogs.com/charlieroro/p/10180827.html
http://www.360doc.com/content/15/0316/13/21786663_455538198.shtml
https://www.jb51.net/article/146162.htm
https://blog.csdn.net/Fitz_q/article/details/108561537

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐