cgroups(control groups)是由 Linux 内核提供的一种特性,它能够限制、核算和隔离一组进程所使用的系统资源(如 CPU、内存、磁盘 I/O、网络等)。
在上一篇文章中我们已了解 Namespace 在容器技术中扮演的角色,如果说 Namespace 控制了容器中的进程能看到什么,那么 cgroups 则控制了容器中的进程能使用多少资源。Namespace 实现了进程的隔离,cgroups 则实现了资源的限制,后者同样是构建容器的基础。
本文将沿袭 Namespace 文章的行文思路,实际创建一个容器,观察宿主机中 cgroups 的变化,来实际展示 cgroups 如何工作,然后了解如何自行配置 cgroups。
cgroup 在何时创建
Linux 内核通过一个叫做 cgroupfs
的伪文件系统来提供管理 cgroup 的接口,我们可以通过 lscgroup
命令来列出系统中已有的 cgroup,该命令实际上遍历了 /sys/fs/cgroup/
目录中的文件:
|
|
如果你使用的 Linux 发行版没有 lscgroup
命令,可通过 command-not-found.com 提供的指令下载安装。
我们将输出结果保存到 cgroup.a
文件中。接着在另一窗口中根据 Namespace 文章中的步骤启动一个容器:
|
|
回到原来的窗口再次执行 lsgroup
命令:
|
|
现在对比两次 lscgroup
命令的输出结果:
|
|
从结果中可看到,mybox
容器创建后,系统中专门为其创建了所有类型的新的 cgroup。
cgroup 如何控制容器的资源
cgroup 所控制的对象是进程,它控制一个或一组进程所能使用多少内存/CPU/网络等等。一个 cgroup 的 tasks
列表中记录了其所控制进程的 PID,该 tasks
实际上也是 cgroupfs
中的一个文件。
init 进程
我们首先在宿主机中打印出容器中的进程信息,找到容器的 init
进程:
|
|
任意打印一些类型的 cgroup 的 tasks
列表:
|
|
这一过程简单明了:容器创建之后,容器的 init
进程会被加入到为该容器所创建的 cgroups 之中,我们可以通过 /proc/$PID/cgroup
得到更肯定的结果:
|
|
容器中的其他进程
接下来我们在 mybox
容器中运行一个新的进程:
|
|
看看是否会创建新的 cgroup:
|
|
没有输出任何结果,说明没有创建新的 cgroup。既然 cgroup 可以控制一组进程,我们猜测在已运行容器中新建的进程,也都会加入到 init
进程所属的 cgroups 中。
下面开始验证,首先找到新建进程的 PID:
|
|
新进程的 PID 是 2576,然后打印该进程的 cgroups 信息:
|
|
输出和 PID 2250 进程的输出完全一致,我们也可以打印其中一个 cgroup 的 tasks
列表:
|
|
完全符合预期。实际上向 tasks
文件直接写入进程的 PID 就实现了将进程加入到该 cgroup 中。当一个容器被创建时,将为每种类型的资源创建一个新的 cgroup,在容器中运行的所有进程都将加入到这些 cgroup 中。
通过控制容器中运行的所有进程,cgroups 实现了对容器的资源限制。
如何配置 cgroup
下面我们将以内存 cgroup 为例,了解如何配置 cgroup 以实现对 mybox
容器的内存限制。
配置 cgroup 有两种方式,一种是直接修改 cgroupfs
中的指定文件,另一种是通过 runc
或 docker
等高阶工具实现。
文件系统方式
通过 cgroupfs
的方式,查看/修改该 cgroup 目录下的特定文件即可查看/设置该 cgroup 的限额:
|
|
修改 memory.limit_in_bytes
文件即可设置最大可用内存,现在我们并未对该容器设置任何限制,因此内存限制的当前值是一个无意义的特别大的值,现在我们向该文件直接写入新的值:
|
|
这样就设置了新的内存限制。写入新的限制值后,容器中的所有进程不能使用总共超过 100M 的内存,超过后将根据 memory.oom_control
文件中设置的 OOM
策略 kill
或 sleep
容器中的进程。
高阶工具方式
通过高阶工具提供的途径来配置 cgroup 是一种更友好的方式,虽然这些工具背后的实现也是如上所述更改 cgroupfs
。
对于 runc
来说,需要修改 filesystem bundle
中的 config.json
文件来配置 cgroup。设置内存限制需要如下修改 JSON 对象中的 linux.resources
字段:
|
|
对于 docker
来说更为简单,它本身就是一个面向用户的封装好的工具,执行 docker run
命令时通过 --memory
选项即可指定内存限制。实际上该参数会被写入到 config.json
由运行时实现 runc
使用,再由 runc
去更改 cgroupfs
。