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 树):

1
2
root@ubuntu:~# mkdir cgroup-test
root@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 以内。