## 简介
Cgroups 全称 Control Groups,是 Linux 内核提供的物理资源隔离机制,通过这种机制,可以针对一组进程或线程设置资源权重,统计资源使用量,操纵启停等
Cgroups 可以限制,记录一组进程或者线程所使用的的物理资源(CPU, Memory, IO 等),是 LXC 实现虚拟化所使用的的资源管理手段
**本质上来讲,cgroups 是内核附加在程序上的一系列钩子(hook), 通过程序运行时对资源的调度触发相应的钩子已达到资源追踪和限制的目的**
### Cgroups 概念
> **很多文章都写 `task` 代表的是系统的进程,其实是很不准确的,task id 其实是线程 id 会被写入到 task 文件中,而 cgroup.procs 中记录的是进程 id**
* **process(进程)**: 表示系统中的进程
* **task(任务/线程)**: 表示系统中一个线程
* **cgroup(控制组)**: cgroups 以 `控制组` 为单位来进行资源空值的,`控制组` 指明了资源的配额限制,一个进程可以加入到某个 `控制组`,也可以迁移到另一个 `控制组` 中,`控制组`是有树状结构关系的,`子控制组`会继承`父控制组`的属性(资源配额,限制等)
* **hierarchy(层级)**: `层级`是作为控制组的根目录,来绑定`controller`,来达到对资源的控制
* **subsystem(子系统)/controller(控制器)**: 也可以称其为 `resource controllers (资源控制器)`,比如 memory controller 可以控制进程内存的使用,这些 controller 可以统称为 cgroup controllers
**每个`层级`需要绑定`controller`来进行资源控制**
**系统中可以存在多个`层级`,整个 Cgroups 的结构应该是多个树状结构**
**`子控制组`分配的资源不能超过`父控制组`分配的资源**
#### 层级, 控制组, 任务,控制器的关系
1. 同一个`层级`可以附加绑定一个或多个`controller`
2. `controller`是可以同时附加到多个`层级`,但是一个已经附加到某个`层级`的`controller`不能附加到其他含有其他`controller`的`层级`上,也就是说绑定多个`层级`后,这些`层级`都是只有唯一`controller`
3. 一个`任务`不能存在于同一个层级的不同控制组,但是一个`任务`可以存在于不同`层级`中的多个`控制组`中
4. 系统每创建一个`层级`时,该系统上的所有`任务`都会默认加入到这个`层级`的`根控制组`
5. fork 或 clone 一个`子任务`时,会自动加入到`父任务`的`控制组`中,允许`子任务`移动到其他`控制组`, 没有限制
**当把一个`任务` 加入到同一个层级中另一个`控制组`后,会从原来的`控制组`中移除**
### [Cgroups V1](http://man7.org/linux/man-pages/man7/cgroups.7.html) 和 [Cgroups V2](https://www.kernel.org/doc/Documentation/cgroup-v2.txt)
> 本文主要介绍 Cgroups V1, Cgroups V2 将来另一篇文章介绍
Cgroups 实现的最初版本是在 Linux 2.6.24,随着时间的推移,添加了越来越多的`controller`,而这些`controller`又各自独立开发,导致了`controller`之间很大的不一致
Linux 3.10 提供了作为试验特性的 Cgroups V1, 到了 Linux kernel 4.5 后 Cgroups V2 才成为正式特性
Cgroups v2 希望完全取代 Cgroups v1, 但是为了兼容,v1 并没有被移除,而且很多场景下都会作为系统的默认设置
**系统中可以同时使用 Cgroups v2 和 v1,但是一个 `controller` 只能选择一个版本使用**
##### v1 的缺陷
v1 中,`task(线程)`可以被划分到不同的`控制组(cgroup)`中,在一些场景中,会存在一些问题,比如对于 memory controller,相同进程的线程都是使用同样的内存地址空间,配置不同的资源是没有意义的
V2 提供了 线程模式,修复了该问题
### Cgroups V1 Controller(控制器) 介绍
`控制器`实际上是 Cgroups 的资源控制系统,每种`控制器`独立的控制一种资源
|控制器|用途|
|----|----|
|[blkio](https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt)|限制`控制组`中`任务`的块设备 io|
|[cpu](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)|限制`控制组`下所有`任务`对 CPU 的使用|
|[cpuacct](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt)|自动生成`控制组`中`任务`对 CPU 资源使用情况的报告|
|[cpuset](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt)|为`控制组`中`任务`分配独立 CPU(针对多处理器系统) 和内存|
|[devices](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt)|控制`任务`对设备的访问|
|[freezer](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)|挂起或恢复 `控制组` 中的`任务`|
|[memory](https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt)|限制`控制组`的内存使用量,自动生成任务对内存的使用情况报告|
|[pids](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt)|限制`控制组`中`进程`可以派生出的进程数量|
|[net_cls](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt)|通过使用等级识别符(classid)标记网络数据包,从而允许 Linux 流量控制程序(Traffic Controller, TC) 识别从具体 cgroup 中生成的数据包|
|[net_prio](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt)|限制`任务`中网络流量的优先级|
|[perf_event](https://perf.wiki.kernel.org/index.php/Main_page)|可以对`控制组`中的`任务`进行统一的性能测试|
|[huge_tlb](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt)|限制对 Huge page 的使用|
|[rdma](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt)|限制 RDMA/IB 资源|
### Cgroups 文件系统
Linux 通过(伪)文件系统的方式将功能和配置暴露给用户,这得益于 Linux 的虚拟文件系统(VFS)
> VFS 将具体文件系统的细节隐藏起来,给用户态一个统一的问题件系统 API 接口
使用 mount 挂载 cgroup 文件系统就可以使用配置这些`controller`了,系统通常已经挂载好了
> 许多系统 v1 控制器会自动挂载在 /sys/fs/cgroup 下,特别是 [systemd](http://man7.org/linux/man-pages/man1/systemd.1.html)
```shell
root@IceberX:/sys/fs/cgroup# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
```
进入到`controller`挂载的`层级`目录下,就可以看到`根控制组`用来配置`controller`的文件,以及`子控制组`
```shell
root@IceberX:/sys/fs/cgroup/cpu# ll
total 0
dr-xr-xr-x 9 root root 0 May 11 12:00 ./
drwxr-xr-x 15 root root 380 Oct 16 2019 ../
-rw-r--r-- 1 root root 0 Nov 7 2019 cgroup.clone_children
-rw-r--r-- 1 root root 0 Nov 7 2019 cgroup.procs
-r--r--r-- 1 root root 0 Nov 7 2019 cgroup.sane_behavior
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.stat
-rw-r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_all
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Nov 7 2019 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Nov 7 2019 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Nov 7 2019 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Nov 7 2019 cpu.shares
-r--r--r-- 1 root root 0 Nov 7 2019 cpu.stat
drwxr-xr-x 2 root root 0 Apr 30 16:21 docker/
drwxr-xr-x 4 root root 0 Nov 20 16:46 kubepods/
-rw-r--r-- 1 root root 0 Nov 7 2019 notify_on_release
-rw-r--r-- 1 root root 0 Nov 7 2019 release_agent
drwxr-xr-x 3 root root 0 May 8 18:11 systemd/
drwxr-xr-x 64 root root 0 May 11 16:32 system.slice/
-rw-r--r-- 1 root root 0 Nov 7 2019 tasks
drwxr-xr-x 3 root root 0 May 11 12:03 test/
drwxr-xr-x 2 root root 0 Nov 7 2019 user.slice/
```
在`控制组`目录下创建新的目录就相当于创建了一个`子控制组`
#### 查看内核支持的 控制器(controller)
*`/proc/cgroups`* 可以列出内核支持的 cgroup controllers,以及使用情况
```bash
λ IceberX /proc → cat cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 2 29 1
cpu 4 95 1
cpuacct 4 95 1
blkio 12 92 1
memory 3 167 1
devices 7 92 1
freezer 11 28 1
net_cls 6 28 1
perf_event 8 28 1
net_prio 6 28 1
hugetlb 10 28 1
pids 5 99 1
rdma 9 1 1
```
controller名称、挂载位置ID(在cgroup v2中都为0)、使用该controller的cgroup数量、是否启用。
#### 查看进程所属的 cgroup
查看 *`/proc/[pid]/cgroup`*
```bash
λ IceberX 24876 → cat cgroup
12:blkio:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
11:freezer:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
10:hugetlb:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
9:rdma:/
8:perf_event:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
7:devices:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
6:net_cls,net_prio:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
5:pids:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
4:cpu,cpuacct:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
3:memory:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
2:cpuset:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
1:name=systemd:/kubepods/besteffort/pod14f31514-524d-421f-b8e1-3f55c5be986b/055f70da1d4b5578e442013b1429cf9eb10648f3657ad912976c17adf980d735
0::/system.slice/k3s.service
```
第一列 `层级` ID,与 `/proc/cgroups`第二列对应,在 Cgroups v2 中都为0
第二列为 `controller` 名称,第三列是控制组的目录路径
## Cgroups V1 使用
v1 中,cgroups 层级目录可以挂载一个或者多个`controller`
注意:使用 *`CONFIG_CGROUP`* 选项构建的内核,才可以使用 cgroups
### 挂载
cgroups 是通过 tmpfs 文件系统的形式挂载到指定目录,通常会挂载到 *`/sys/fs/cgroup`* 目录
```bash
mount -t tmpfs cgroup /sys/fs/cgroup
```
挂载`controller`,挂载类型为 *`cgroup`*,-o 指定`controller`,可以有多个
```bash
root@IceberX:~# mkdir -p /tmp/cgroup/cpu,cpuacct
root@IceberX:~# mount -t cgroup -o cpu,cpuacct none /tmp/cgroup/cpu,cpuacct
```
进入到 /tmp/cgroup/cpu,cpuacct 目录下可以看到和 /sys/fs/cgroup/cpu,cpuacct 相同的内容
一次挂载所有`controller`,可以省略 -o all,挂载所有控制器是默认选项
```bash
mount -t cgroup -o all cgroup /tmp/cgroup
```
不挂载任何`controller`,必须提供 name,可以挂载多次,但是要求名字唯一
```bash
mount -t cgroup -o none,name=somename none /tmp/cgroup/none
```
可以使用 cgroups 的回调通知机制,来跟踪进程
> Linux 5.0 后可以通过内核引导选项*`cgroup_no_v1`*可以禁止挂载这种命名的Cgroup V1 层级
### 卸载
需要将所有子目录卸载,否则 umount 只会让挂载点不可见,并不会真正的卸载
```bash
umount /tmp/cgroup/cpu,cpuacct
```
### 创建控制组(cgroup)
`控制组`是以目录形式来呈现的,`/` 是控制组的根目录,注意根目录和挂载目录是两回事
`层级` 可能挂载在 /sys/fs/cgroup 之类的目录下,但是无论挂载到哪里,`层级`中`控制组`的根目录都是 `/`
比如
/sys/fs/cgroup/cpu/kubepods 的目录应该是 /kubepods
### 控制进程和线程
> **cgroup 目录下 cgroup.procs 记录的是 进程 ID, tasks 文件记录的是 线程 ID**
将`进程 id` 写入指定`控制组`目录下的 *`cgroup.procs`* 文件中,便将进程绑定到相应的`控制组`了
```bash
echo $$ > /sys/fs/cgroup/cpu/test1/cgroup.procs
```
**绑定进程后,进程下的线程也会一并绑到管理组中,可以查看 tasks 文件**
*`cgroup.procs`* 文件中的进程 id 可以重复,并且没有顺序要求
把线程 id 写入 tasks 文件中,也可以绑定指定线程
### 删除控制组
`控制组`下的`子控制组`都删除了,并且没有绑定任何进程,线程,则直接删除目录即可
### 回调通知
每个 `控制组`目录下都有两个文件
```bash
-rw-r--r-- 1 root root 0 May 11 11:33 notify_on_release
-rw-r--r-- 1 root root 0 May 11 11:33 release_agent
```
当`控制组`绑定的所有进程或线程都结束后,根据 **notify_on_release** 文件内容,判断为否调用 **release_agent** 指定的命令,该命令的参数只有当前为空的`控制组`目录
**notify_on_release** 文件内容是 0 或 1, 表示 cgroup 为空后,是否发出通知,1 为通知
**release_agent** 文件内容为通知时调用的命令路径
release_agent 的内容也可以在挂载的时候设置
```bash
mount -o release_agent=pathname ...
```
## 控制器(Controller) 配置参数
### cpu: CPU 资源控制
> 在 2.6.24 开始,限制CPU份额,只会在cpu忙碌的时候限制cpu使用,如果cpu空闲不做限制。[CFS Scheduler](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt)
> 3.2.0后, 引入了`CON‐FIG_CFS_BANDWIDTH`编译选项,限制进程在每个调度周期占用的时间,无论CPU是否空闲。[CFS Bandwidth Control](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
>
CPU 资源控制有两种策略
|策略|描述|
|----|----|
|完全公平调度(Completely Fair Scheduler,CFS)|按限额和比例分配两种方式进行资源限制|
|实时调度(Real-Time Scheduler)|针对实时任务按周期分配固定的运行时间|
* 设置CPU使用周期和使用上限
|配置|描述|
|----|----|
|cpu.cfs_period_us|设定周期时间,必须和cfs_quota_us配合使用|
|cpu.cfs_quota_us|设定周期内最多可使用的时间,指对单个cpu的使用上限,如果 cfs_quota_us 是 cfs_period_us 的两倍,表示在两个核上完全使用 1000 - 1000000 ms|
|cpu.stat| 用于统计信息,包含 nr_periods (经历了几个 cfs_period_us 周期),nr_throttled (任务被限制的次数),throttled_time(任务被限制的总时长)
* 按权重比例设定 CPU 的分配
|配置|描述|
|----|----|
|cpu.shares|设置一个整数(必须大于或等于2)表示相对权重,最后除以权重综合,算出相对比例,按比例分配 CPU 时间|
> cgroup A设置为100, cgroup B设置为300,那么cgroupA中的任务运行25%的CPU时间。对于一个四核CPU的系统来说,cgroup A中的任务可以100%占有某一个CPU,这个比例是相对整体的一个值
### cpuacct: CPU资源报告
提供了 CPU 资源用量的统计,单位为 ns
|配置|描述|
|----|----|
|cpuacct.usage|统计控制组中所有任务的CPU使用时长
|cpuacct.stat|统计控制组中所有任务在用户态和内核态分别使用的 CPU 时长
|cpuact.usage_percpu|统计控制组中所有任务使用每个 CPU 的时长
### cpuset: 绑定CPU
|配置|描述|
|----|----|
|cpuset.cpus| 控制组可以使用的CPU编号,0, 1 代表0,1 号 cpu
|cpuset.mems | 表示控制组可以使用的 memory node
### memory: 限制管理内存
* 限制和管理配置
|配置|描述|
|----|----|
|memory.limit_in_bytes| 强制限制最大的内存使用量,单位可以使 k,m,g,-1 表示无限制
|memory.soft_limit_in_bytes|表示软限制,只有比limit_in_bytes小时才有意义,当整体内存紧张时,任务获取的内存会被限制在软限制内
|memory.memsw.limit_in_bytes | 用于设定最大内存和swap内存之和的限制
|memory.oom_control|当控制组中的任务使用超过界限时立即杀死任务,0 表示开启, 1 表示不开启,当不启用但实际使用内存超过界限时,任务会被暂停知道有空闲的内存资源
* 统计
|配置|描述|
|----|----|
|memory.usage_in_bytes|报告控制组中进程当前使用的总内存量,单位为字节
|memory.max_usage_in_bytes|报告控制组中进程使用的最大内存量
|memory.failcnt| 报告内存到达memory.limit_in_bytes限制的次数
|memory.stat| 内存统计的数据
### pids: 限制进程创建
|配置|描述|
|----|----|
|pids.max| 允许创建的最大进程数量
|pids.current| 表示当前总的进程数量
### device: 限制对 device 的使用
```bash
// 格式 type device_types:node_numbers accesstype
// 设备类型 a|b|C all/block/character
// 访问类型r|w|m read/write/create
echo "a 1:5 r" > devices.deny
```
|配置|描述|
|----|----|
|device.allow|白名单,允许使用的设备|
|device.deny|黑名单,不允许使用的设备|
### freezer: 暂停/恢复管理组中的任务
|配置|描述|
|----|----|
|freezer.state|表示管理组中任务的状态,修改状态,可以停止或者恢复任务
FORZEN: 停止
FREEZING: 正在停止,只读状态,不能写入此值
THAWED: 恢复,表示任务处于正常状态
Linux Cgroups V1 介绍与使用