Linux Cgroups的资源控制

今天主要是说说Linux的Cgroup技术,之前介绍的是构建Linux容器的namespace技术,它能够很好的帮助进程隔离出自己单独的空间,但Docker又是怎么限制每个空间的大小,保证他们不会互相争抢呢?这就要用到Linux的Cgroups技术。

mark

Linux Cgroups(Control Groups) 提供了对一组进程及将来的子进程的资源的限制 ,控制和统计的能力,这些资源包括CPU,内存,存储,网络等。通过Cgroups,可以方便的限制某个进程的资源占用,并且可以实时的监控进程的监控和统计信息。

Cgroups三个组件

1、cgroup

cgroup 是对进程分组管理的一种机制,一个cgroup包含一组进程,并可以在这个cgroup上增加Linux subsystem的各种参数的配置,将一组进程和一组subsystem的系统参数关联起来。亦即,管理“组进程”并关联参数

2、subsystem

subsystem 是一组资源控制的模块,一般包含有:

  • blkio 设置对块设备(比如硬盘)的输入输出的访问控制(block/io)
  • cpu 设置cgroup中的进程的CPU被调度的策略
  • cpuacct 可以统计cgroup中的进程的CPU占用(cpu account)
  • cpuset 在多核机器上设置cgroup中的进程可以使用的CPU和内存(此处内存仅使用于NUMA架构)
  • devices 控制cgroup中进程对设备的访问
  • freezer 用于挂起(suspends)和恢复(resumes) cgroup中的进程
  • memory 用于控制cgroup中进程的内存占用
  • net_cls 用于将cgroup中进程产生的网络包分类(classify),以便Linux的tc(traffic controller) (net_classify) 可以根据分类(classid)区分出来自某个cgroup的包并做限流或监控。
  • net_prio 设置cgroup中进程产生的网络流量的优先级
  • ns 这个subsystem比较特殊,它的作用是cgroup中进程在新的namespace fork新进程(NEWNS)时,创建出一个新的cgroup,这个cgroup包含新的namespace中进程。

每个subsystem会关联到定义了相应限制的cgroup上,并对这个cgroup中的进程做相应的限制和控制,这些subsystem是逐步合并到内核中的

可以通过安装cgroup的命令行工具(apt-get install cgroup-bin)看到当前的内核支持哪些subsystem,然后通过lssubsys看到kernel支持的subsystem。

mark

3、hierarchy

hierarchy 的功能是把一组cgroup串成一个树状的结构,一个这样的树便是一个hierarchy,通过这种树状的结构,Cgroups可以做到继承。比如我的系统对一组定时的任务进程通过cgroup1限制了CPU的使用率,然后其中有一个定时dump日志的进程还需要限制磁盘IO,为了避免限制了影响到其他进程,就可以创建cgroup2继承于cgroup1并限制磁盘的IO,这样cgroup2便继承了cgroup1中的CPU的限制,并且又增加了磁盘IO的限制而不影响到cgroup1中的其他进程。

三个组件之间的关系

Cgroups的是靠这三个组件的相互协作实现的,那么三个组件之间的关系是怎样的呢?

  • 系统在创建新的hierarchy之后,系统中所有的进程都会加入到这个hierarchy的根cgroup节点中,这个cgroup根节点是hierarchy默认创建,后面在这个hierarchy中创建cgroup都是这个根cgroup节点的子节点。

  • 一个subsystem只能附加到一个hierarchy上面

  • 一个hierarchy可以附加多个subsystem

  • 一个进程可以作为多个cgroup的成员,但是这些cgroup必须是在不同的hierarchy中

  • 一个进程fork出子进程的时候,子进程是和父进程在同一个cgroup中的,也可以根据需要将其移动到其他的cgroup中

mark

其实上图很能说明问题,Cgroup包括了一组进程,subsystem(子系统)负责控制资源,资源控制包括CPU调度策略、块设备访问控制、内存占用、设备访问、进程的挂起和恢复等,很多Cgroup组成树状结构形成hierarchy,subsystem不但可以控制一个Cgroup,也可以控制多个Cgroup,Cgroup与Cgroup之间形树状,可以有继承关系,进程不但可以属于一个Cgroup、还可以属于多个Cgroup,但是肯定不是在同一个hierarchy中,Cgroup中的进程可以移动

调用Kernel配置Cgroups

之前了解到Cgroups中的hierarchy是一种树状的组织结构,Kernel为了让对Cgroups的配置更直观,Cgroups通过一个虚拟的树状文件系统去做配置的,通过层级的目录虚拟出cgroup树,下面我们就以一个配置的例子来了解下如何操作Cgroups。

首先,我们要创建并挂载一个hierarchy(cgroup树):

1root@ubuntu:~# mkdir cgroup-test
2root@ubuntu:~# mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test 

如上图,接下来挂载后我们就可以看到系统在这个目录下生成了一些默认文件 :cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks

这些文件就是这个hierarchy中根节点cgroup配置项 :

  • cgroup.clone_children cpuset的subsystem会读取这个配置文件,如果这个的值是1(默认是0),子cgroup才会继承父cgroup的cpuset的配置。

  • cgroup.procs是树中当前节点的cgroup中的进程组ID,现在我们在根节点,这个文件中会有现在系统中所有进程组ID。

  • notify_on_release和release_agent会一起使用,notify_on_release表示当这个cgroup最后一个进程退出的时候是否执行release_agent,release_agent则是一个路径,通常用作进程退出之后自动清理掉不再使用的cgroup。

  • tasks也是表示该cgroup下面的进程ID,如果把一个进程ID写到tasks文件中,便会将这个进程加入到这个cgroup中。

如上图,接下来我们创建在刚才创建的hierarchy的根cgroup中扩展出两个子cgroup,一个是cgroup-1,一个是cgroup-2,可以看到,在一个cgroup 的目录下创建文件夹时, Kernel 会把文件夹标记为这个cgroup的子cgroup ,它们会继承父cgroup 的属性。

接下来尝试一下在cgroup中添加和移动进程 :

  • 一个进程在一个Cgroups的hierarchy中只能存在在一个cgroup节点上,
  • 系统的所有进程默认都会在根节点,
  • 可以将进程在cgroup节点间移动,只需要将进程ID写到移动到的cgroup节点的tasks文件中。

可以看出我的当前进程是1449,通过把进程号写入task中的方式已经成功把当前进程,也就是Bash进程加入到了cgroup-test中,又开了一个Bash,把Bash进程加入到cgroup-1中:

mark

mark

接下来看看如何通过subsystem限制cgroup中进程的资源 ?

上面我们创建hierarchy的时候,但这个hierarchy并没有关联到任何subsystem,所以没办法通过那个hierarchy中的cgroup限制进程的资源占用,其实系统默认就已经把每个subsystem创建了一个默认的hierarchy,比如memory的hierarchy:

mark

可以看到,在/sys/fs/cgroup/memory目录便是挂在了memory subsystem的hierarchy。下面我们就通过在这个hierarchy中创建cgroup,限制下占用的进程占用的内存:

首先,我们不做限制启动一个占用内存的stress进程, stress 命令主要用来模拟系统负载较高时的场景,关于stress指令可以看看这篇文章 《Linux stress 命令》

mark

可以看到通过cgroup,我们成功的将stress进程的最大内存占用限制到了100m以内。