Linux CGroup学习

Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去。

Linux CGroupCgroup 可​​​让​​​您​​​为​​​系​​​统​​​中​​​所​​​运​​​行​​​任​​​务​​​(进​​​程​​​)的​​​用​​​户​​​定​​​义​​​组​​​群​​​分​​​配​​​资​​​源​​​ — 比​​​如​​​ CPU 时​​​间​​​、​​​系​​​统​​​内​​​存​​​、​​​网​​​络​​​带​​​宽​​​或​​​者​​​这​​​些​​​资​​​源​​​的​​​组​​​合​​​。​​​您​​​可​​​以​​​监​​​控​​​您​​​配​​​置​​​的​​​ cgroup,拒​​​绝​​​ cgroup 访​​​问​​​某​​​些​​​资​​​源​​​,甚​​​至​​​在​​​运​​​行​​​的​​​系​​​统​​​中​​​动​​​态​​​配​​​置​​​您​​​的​​​ cgroup。

主要提供了如下功能:

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

使​​​用​​​ cgroup,系​​​统​​​管​​​理​​​员​​​可​​​更​​​具​​​体​​​地​​​控​​​制​​​对​​​系​​​统​​​资​​​源​​​的​​​分​​​配​​​、​​​优​​​先​​​顺​​​序​​​、​​​拒​​​绝​​​、​​​管​​​理​​​和​​​监​​​控​​​。​​​可​​​更​​​好​​​地​​​根​​​据​​​任​​​务​​​和​​​用​​​户​​​分​​​配​​​硬​​​件​​​资​​​源​​​,提​​​高​​​总​​​体​​​效​​​率​​​。

在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程 分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

概念、术语

  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上。从cgroup文件系统看,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。

Subsystem介绍

subsystem 是一组资源控制的模块。包含以下几项。

  • blkio 设置对块设备输入输出的访问控制。例如磁盘
  • cpu 设置cgroup中进程的cpu被调度策略。
  • cpuacct 可以统计cgroup中进程的cpu占用。
  • cpuset 在多核机器上,设置cgroup中进程可以使用的cpu和内存。此处仅限于NUMA架构。
  • devices 控制cgroup对设备的访问。
  • freezer 挂起(suspend)和恢复(resue) cgroup中的进程。
  • memory 用于控制cgroup中进程的内存占用。
  • net_cls 将cgroup中进程产生的网络包分类,便于linux tc(traffic controller)可以根据分类区分出来自某个cgroup包并做监控。
  • net_prio 设置cgroup中进程产生的网络流量的优先级。
  • ns 使cgroup中的进程在新的Namespace中fork新进程时,创建一个新的cgroup,这个cgroup包含新的Namespace中的进程。

Hierarchy与cgroup介绍

Hierarchy是一棵树,每个节点都是一个cgroup,cgroup中是属于该控制组的进程,一个Hierarchy中包含系统中所有的进程,一开始挂载生成一个Hierarchy树时,Hierarchy中只有一个根节点,也就是只有一个root cgroup,系统中所有的进程都在这个cgroup中,随后,可以根据需求,创建新的cgroup,并可以按需求将进程加入到相应的cgroup中。新的cgroup在Hierarchy表现为一个节点,当一个进程迁移到一个cgroup时,就自动脱离了之前的cgroup,也就是在一个Hierarchy中,一个进程只会在一个cgroup节点中。进行进程的cgroup迁移时,需要满足的条件是,对目标cgroup有写入权限,执行者为root或目标进程的拥有者。但进程fork子进程时,子进程将会加入到fork时父进程所在的cgroup中,当对父进程进行了cgroup的迁移时,不会影响到之前已经存在的子进程的cgroup归属。

1
Hierarchy决定了进程归属于哪一个cgroup,而subsystem确定进行什么限制,当两者关联在一起,就决定了对哪些进程进行哪些限制。

CGroup的使用

cgroups 是通过 VFS 把功能暴露给用户态的。

1
2
3
4
5
6
7
VFS 是一个内核抽象层,能够隐藏具体文件系统的实现细节,从而给用户态进程提供一套统一的 API 接口。VFS 使用了一种通用文件系统的设计,具体的文件系统只要实现了 VFS 的设计接口,就能够注册到 VFS 中,从而使内核可以读写这种文件系统。

cgroups 通过实现 VFS 的通用文件系统模型,把维护 cgroups 层级结构的细节,隐藏在 cgroups 文件系统的这些实现函数中。

这样,用户在用户态对 cgroups 文件系统的操作,通过 VFS 转化为对 cgroups 层级结构的维护。通过这样的方式,内核把 cgroups 的功能暴露给了用户态的进程。

达到的效果就是,用户以目测的创建变更、文件的读写形式,完成与CGroup的交互和使用。通过读操作获取相关信息,通过写操作完成相关的控制操作。

用户可以通过对cgroup文件系统进行操作来实现与cgroup的交互。

cgroup文件系统挂载到目录后,呈现出的样子是,一棵Hierarchy树就是一棵目录树,每一个目录都是一个节点,对应一个cgroup,目录的名字就是cgroup的名字,每个cgroup目录下,会有一些文件,这些文件包括对该cgroup中进程的记录等、与该cgroup相关联的subsystem的配置信息等、子cgroup的目录。当挂载目录并与指定的subsytem相关联的时候,root cgroup目录下就会默认生成cgroup相关的记录文件,和subsystem相关的配置文件。当在cgroup目录下,建立子目录时,就创建了子cgroup,子目录中将会自动生成cgroup相关的记录文件,和与父cgroup所关联的subsystem相关的配置文件。理解了各个文件的含义,就可以通过对这些文件进行读写操作,完成与cgroup的交互。

对cgroup文件系统的操作,通常可以是一下几种形式:

  • 用户可以通过将cgroup文件系统挂载到系统中某个目录下,然后对该目录进行文件操作来实现与cgroup的交互。

    1
    2
    3
    4
    5
    6
    7
    8
    挂载cgroup文件系统:

    mount -t cgroup -o subsystems name /cgroup/name

    其中 subsystems 表示需要挂载的 cgroups 子系统, /cgroup/name 表示挂载点,这条命令同时在内核中创建了一个cgroups 层级结构。

    挂载一棵cgroup树,但不关联任何subsystem:
    mount -t cgroup -o none,name=xxxxx xxxxx /path_to_cgroup
    1
    2
    显示cgroup subsystem的挂载情况
    lssubsys -m
    1
    2
    3
    4
    5
    创建子cgroup,在父cgroup目录下创建子目录即可
    mkdir

    移除cgroup,删除对应的目录
    rmdir
    1
    2
    3
    4
    将进程888添加到指定进程组
    echo 888 > path_to_cgroup/cgroup.procs
    非root账号:
    sudo sh -c 'echo 888 > path_to_cgroup/cgroup.procs'
  • 在rh_6系列中,可以通过libcgroup工具来进行交互,安装该工具后, 启动cgconfig服务,将会自动进行cgroup文件系统的挂载,根据/etc/cgconfig.conf中的配置,将决定其挂载位置,以及挂载后创建相关的cgroup和相关的subsystem限制。通常默认的挂载位置为/cgroup/。同时libcgroup工具提供了一些命令,可以方便地进行进程的cgroup迁移,cgroup创建和销毁,进程创建并加入指定的cgroup等操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    启动cgconfig服务
    service cgconfig start

    设置 cgroups 子系统的参数
    cgset -r parameter=value path_to_cgroup

    在某一个 cgroups 下启动进程
    gexec -g subsystems:path_to_cgroup command arguments

    创建cgroup
    cgcreate
  • 在rh_7系统中,使用systemd对cgroup文件系统进行维护,systemd将在/sys/fs/cgroup/挂载cgroup文件系统。可以通过systemd提供的相关命令进行相关的资源监控和限制。

    1
    systemctl set-property XXX.service XXXXXXX=XX

由于一个subsystem只能附加到一个hierarchy上面。所以,如果系统中已经将一个subsystem附加到了一个Hierarchy上,当再次挂载与该subsystem相关联的Hierarchy时,将会失败。所以,倘若在一个系统中使用多种方式进行操作,将有相互干扰的风险,需要尤其注意。

Tips

1
2
3
4
1. 系统创建hierarchy 之后,所有的进程都会加入这个hierarchy的cgroup的根节点。在这个cgroup根节点是hierarchy默认创建的。
2. 一个subsystem只能附加到一个hierarchy上面。
3. 一个进程可以作为多个cgroup的成员,但是cgroup必须在不同的hierarchy中。
4. 一个进程fork的子进程和父进程在同一个cgroup中也可以根据需要移到其他cgroup中。

参考: