Linux Cgroups V1 介绍与使用

Posted by icebergu on 07-07,2020

简介

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 V1Cgroups V2

本文主要介绍 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限制控制组任务的块设备 io
cpu限制控制组下所有任务对 CPU 的使用
cpuacct自动生成控制组任务对 CPU 资源使用情况的报告
cpuset控制组任务分配独立 CPU(针对多处理器系统) 和内存
devices控制任务对设备的访问
freezer挂起或恢复 控制组 中的任务
memory限制控制组的内存使用量,自动生成任务对内存的使用情况报告
pids限制控制组进程可以派生出的进程数量
net_cls通过使用等级识别符(classid)标记网络数据包,从而允许 Linux 流量控制程序(Traffic Controller, TC) 识别从具体 cgroup 中生成的数据包
net_prio限制任务中网络流量的优先级
perf_event可以对控制组中的任务进行统一的性能测试
huge_tlb限制对 Huge page 的使用
rdma限制 RDMA/IB 资源

Cgroups 文件系统

Linux 通过(伪)文件系统的方式将功能和配置暴露给用户,这得益于 Linux 的虚拟文件系统(VFS)

VFS 将具体文件系统的细节隐藏起来,给用户态一个统一的问题件系统 API 接口

使用 mount 挂载 cgroup 文件系统就可以使用配置这些controller了,系统通常已经挂载好了

许多系统 v1 控制器会自动挂载在 /sys/fs/cgroup 下,特别是 systemd

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的文件,以及子控制组

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,以及使用情况

λ 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

λ 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 目录

mount -t tmpfs cgroup /sys/fs/cgroup

挂载controller,挂载类型为 cgroup,-o 指定controller,可以有多个

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,挂载所有控制器是默认选项

mount -t cgroup -o all cgroup /tmp/cgroup

不挂载任何controller,必须提供 name,可以挂载多次,但是要求名字唯一

mount -t cgroup -o none,name=somename none /tmp/cgroup/none

可以使用 cgroups 的回调通知机制,来跟踪进程

Linux 5.0 后可以通过内核引导选项*cgroup_no_v1*可以禁止挂载这种命名的Cgroup V1 层级

卸载

需要将所有子目录卸载,否则 umount 只会让挂载点不可见,并不会真正的卸载

umount /tmp/cgroup/cpu,cpuacct

创建控制组(cgroup)

控制组是以目录形式来呈现的,/ 是控制组的根目录,注意根目录和挂载目录是两回事
层级 可能挂载在 /sys/fs/cgroup 之类的目录下,但是无论挂载到哪里,层级控制组的根目录都是 /
比如
/sys/fs/cgroup/cpu/kubepods 的目录应该是 /kubepods

控制进程和线程

cgroup 目录下 cgroup.procs 记录的是 进程 ID, tasks 文件记录的是 线程 ID

进程 id 写入指定控制组目录下的 cgroup.procs 文件中,便将进程绑定到相应的控制组

echo $$ > /sys/fs/cgroup/cpu/test1/cgroup.procs

绑定进程后,进程下的线程也会一并绑到管理组中,可以查看 tasks 文件

cgroup.procs 文件中的进程 id 可以重复,并且没有顺序要求

把线程 id 写入 tasks 文件中,也可以绑定指定线程

删除控制组

控制组下的子控制组都删除了,并且没有绑定任何进程,线程,则直接删除目录即可

回调通知

每个 控制组目录下都有两个文件

-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 的内容也可以在挂载的时候设置

mount -o release_agent=pathname ...

控制器(Controller) 配置参数

cpu: CPU 资源控制

在 2.6.24 开始,限制CPU份额,只会在cpu忙碌的时候限制cpu使用,如果cpu空闲不做限制。CFS Scheduler
3.2.0后, 引入了CON‐FIG_CFS_BANDWIDTH编译选项,限制进程在每个调度周期占用的时间,无论CPU是否空闲。CFS Bandwidth Control

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 的使用

// 格式 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: 恢复,表示任务处于正常状态