Linux的cgroup详细介绍


本文转自小鸟技术笔记,共分三篇,感谢原作者。
Linux的cgroup功能(一):初级入门使用方法
Linux的cgroup功能(二):资源限制cgroup v1和cgroup v2的详细介绍
Linux的cgroup功能(三):cgroup controller汇总和控制器的参数(文件接口)
Linux 命名空间和控制组:实现资源隔离与管理的双重利器
Linux 命名空间(Namespace)和控制组(Cgroups)这是实现资源控制管理的两个关键技术。通过命名空间可以实现对资源的隔离,让进程在独立的空间中运行,增强系统的安全性。而通过控制组可以有效地管理和限制进程的资源使用,避免资源浪费,提高了系统性能。
Linux 命名空间 (Namespace)
这是一种隔离机制,允许将全局系统资源划分为多个独立的、相互隔离的部分,使得在不同的命名空间中运行的进程感知不到其他命名空间的存在。从而实现了对进程、网络、文件系统、IPC(进程间通信)等资源的隔离,减少了潜在的安全风险。例如,在容器中运行应用程序可以避免对主机系统的直接影响,从而提高了系统的安全性。
Linux 控制组 (Cgroups)
控制组是一种资源管理机制,允许对进程组或任务组应用资源限制和优先级设置。它可以用来限制一组进程的资源使用,如 CPU、内存、磁盘 I/O 等,从而实现资源的分配和控制。
简单来说 Cgroups 可以理解为是房子的土地面积,限制了房子的大小 ,而 Namespace 是房子的墙,与邻居互相隔离。通过使用命名空间和控制组,可以更有效地使用系统资源,避免资源浪费,并确保关键任务获得足够的资源支持,从而提高系统性能和效率。这些功能对于现代的云计算和容器化部署是至关重要的。最典型的容器技术 Docker 就是利用 namespace 和 cgroup 实现的。
命名空间类型
下面是 Linux 提供的 Namespace 类型,通过这些命名空间的组合,可以实现复杂的隔离和虚拟化配置

PID 命名空间
Linux PID 命名空间是 Linux Namespace 的一种类型,用于隔离进程 ID。在一个 PID 命名空间中,每个进程拥有独立的进程 ID,这样在不同的命名空间中可以有相同的进程 ID,而不会产生冲突。每个子 PID 命名空间中都有 PID 为 1 的 init 进程,对应父命名空间中的进程,父命名空间对子命名空间运行状态是不隔离的,但是每一个子命名空间是互相隔离的。
如下图:在子命名空间 A 和 B 中都有一个进程 ID=1 的 init 进程,这两个进程实际上是父命名空间的 55 号和 66 号进程 ID, 虚拟化出来的空间而已。

UTS 命名空间
Linux UTS 命名空间用于隔离主机名和域名。在 UTS 命名空间中,每个进程可以拥有独立的主机名和域名 (nodename,domainname),这样可以在不同的命名空间中拥有不同的标识,从而实现了主机名和域名的隔离。
nodename: 是用于标识主机的独特名称,通常也被称为主机名。它用于在网络中唯一地标识一台计算机
domainname: 是主机的域名部分,通常用于标识所属的网络域。域名通常由多个部分组成,按照从右到左的顺序,每个部分之间用点号 "." 分隔。域名用于将主机名与特定的网络域关联起来,从而帮助在全球范围内定位和访问计算机
在容器技术中,利用 UTS Namespace 隔离后,容器内的进程可以拥有独立的主机名和域名,而不会与宿主系统或其他容器中的进程产生冲突。这样,容器内的应用程序可以认为它们在独立的主机中运行,从而更容易进行配
Mount 命名空间
Linux Mount Namespace 用于隔离文件系统挂载点。通过 Mount Namespace,不同的进程可以在不同的挂载点上看到不同的文件系统层次结构,即使在同一台主机上运行。这种隔离使得进程在一个 Mount Namespace 中的挂载操作对其他 Mount Namespace 中的进程不可见,从而实现了文件系统层面的隔离。
在容器技术中,利用 Mount Namespace 隔离后,容器内部的文件系统挂载与宿主系统和其他容器相互隔离。这样,每个容器可以拥有独立的文件系统视图,容器内的进程只能访问自己的文件系统层次结构,而无法访问其他容器或宿主系统的文件系统。
Network 命名空间
Linux Network Namespace 用于隔离网络栈。通过 Network Namespace,不同的进程可以拥有独立的网络设备、IP 地址、路由表、网络连接和网络命名空间中的其他网络资源。这种隔离使得进程在一个 Network Namespace 中的网络配置和状态对其他 Network Namespace 中的进程不可见,从而实现了网络层面的隔离。
在容器技术中,利用 Network Namespace 隔离后,容器内部的进程拥有独立的网络环境,从而使得容器在网络上彼此隔离。每个容器可以有自己的网络设备、IP 地址、路由表和网络连接,容器之间不会干扰彼此,也不会干扰宿主系统。
User 命名空间
Linux User Namespace 用于隔离用户和用户组 ID。通过 User Namespace,不同的进程可以拥有独立的用户和用户组 ID,这样可以在不同的命名空间中拥有不同的身份标识,从而实现了用户和用户组的隔离。
在容器技术中,利用 User Namespace 隔离后,容器内的进程可以拥有独立的用户和用户组 ID,而不会与宿主系统或其他容器中的用户产生冲突。这样容器内的应用程序可以以普通用户身份运行,而不需要在宿主系统中创建相同的用户账号。
在 Docker 中默认是不启用 User Namespace 隔离的,主要是因为开启后需要做很多特殊的配合和管理,例如隔离后容器内的用户和宿主上的用户已经不是相同的身份了,那么可能会影响访问文件系统。
IPC 命名空间
Linux IPC 命名空间用于隔离进程间通信资源。在 IPC 命名空间中,每个命名空间都有独立的 IPC 资源,如消息队列、信号量和共享内存,使得不同命名空间中的进程无法直接访问其他命名空间的 IPC 资源,从而实现了 IPC 资源的隔离。
在容器技术中,利用 IPC Namespace 隔离后,容器内的进程拥有独立的 IPC 资源,从而避免不同容器之间的进程干扰和资源冲突。每个容器都可以有自己的 IPC 命名空间,使得容器内的进程在进行进程间通信时只能访问属于同一命名空间的 IPC 资源,而无法直接访问其他容器的 IPC 资源。
在 Linux 系统中提供了以下几种常用的创建和管理命名空间的 API:
1.clone:使用 clone 系统调用创建一个新进程时可以通过指定一个或多个上面列出的命名空间标志参数来创建新的命名空间,并且新进程的子进程也会默认被包含在新的命名空间内
2.unshare:使用 unshare 系统调用将一个已存在的进程放入新的命名空间。它可以指定一个或多个上面列出的命名空间标志参数,创建具有指定类型的命名空间,并将当前进程或其他指定进程放入其中
3.setns: 使用 setns 系统调用允许进程将自己放入已经存在的命名空间中,而无需创建新的进程。通过 setns 系统调用,进程可以切换到指定类型的命名空间中,与其他已存在于该命名空间中的进程共享同一个隔离环境
Linux 控制组(Cgroups)
Cgroups 介绍
在 Linux 中,cgroups(Control Groups)是一种用于资源管理和限制进程资源使用的机制。它允许管理员将一组进程组织在一个或多个 cgroups 中,并为每个 cgroup 分配特定的资源限制,如 CPU、内存、磁盘 I/O、网络带宽等。通过 cgroups,可以更好地控制系统中各个进程的资源使用,实现资源隔离和公平共享,防止某些进程占用过多资源导致系统负载过高。
核心组件
Cgroups 主要有三个核心组件组成:
子系统(subsystem):子系统是用于管理特定类型资源的模块,每个子系统负责管理一类资源,例如 CPU、内存、磁盘 I/O、网络带宽等。
Cgroup Hierarchies(Cgroup 层级树):Hierarchies 是用于组织和管理 Cgroup 的结构。一个 Hierarchies 由一个或多个 subsystem 组成。Hierarchies 形成树状结构,每个节点都是一个 Cgroup,同一层级中的 Cgroup 之间是平级的。Hierarchies 允许 Cgroup 在不同的 subsystem 中进行组合和嵌套,形成多层的资源管理结构。系统会默认为每个 subsystem 创建一个默认的 Hierarchy。
Cgroups(控制组):Cgroup 是最终的资源管理单元,它是一组进程的集合,被组织在一个特定的 Cgroup 层级中,并受到该层级中控制器的资源限制和管理。
核心组件关系

关系说明如下:
1.默认系统会为所有的 subsystem 创建 Hierarchy 树,默认 cgroup 根路径在 /sys/fs/cgroup 目录下,在 /sys/fs/cgroup 下可以看到全部的 subsystem 对应的 Hierarchy 树
2.系统创建了新的 Hierarchy 后,默认所有进程都会加入到树中根节点的 cgroup 中,例如可以到 /sys/fs/cgroup/cpu/tasks 文件中看到所有的进程列表
3.一个 subsystem 只能附加到一个 Hierarchy 树上
4.一个 Hierarchy 树可以对应多个 subsystem
5.一个进程可以作为多个 cgroup 的成员,但是这些 cgroup 必须在不同的 Hierarchy 树中
6.一个进程 fork 出子进程时,子进程和父进程默认是在同一个 cgroup 中的,也可以自行移动到其他 cgroup 中
Subsystem (常见子系统) 列表
cpu
用于管理 CPU 资源。它允许设置进程的 CPU 使用率和时间片配额,从而限制进程的 CPU 占用,以下是 cpu 子系统的一些常见控制文件:
1.cpu.cfs_quota_us:这个文件用于设置 cgroup 中进程的 CPU 配额。单位为微秒(μs),表示在每个 cpu.cfs_period_us 微秒内,cgroup 中的进程可以使用的 CPU 时间量。如果 cpu.cfs_quota_us 的值为 -1,则表示 cgroup 中的进程没有 CPU 使用限制。
2.cpu.cfs_period_us:这个文件用于设置 cgroup 中进程的 CPU 时间周期。单位为微秒(μs),表示在一个周期内,cgroup 中的进程可以使用的 CPU 时间总量。
cpuacct
cpuacct:用于统计和记录进程组(cgroup)中的 CPU 使用情况,以下是 cpuacct 子系统的一些常见统计文件:
1.cpuacct.usage: 这个文件记录了 cgroup 中所有进程的 CPU 使用时间总和,以纳秒为单位。可以通过读取这个文件来查看 cgroup 中的所有进程所消耗的 CPU 时间。
2.cpuacct.usage_percpu: 这个文件记录了 cgroup 中每个 CPU 核心的 CPU 使用时间,以纳秒为单位。如果系统有多个 CPU 核心,这个文件会显示每个核心的 CPU 使用时间。
3.cpuacct.stat: 这个文件提供了关于 CPU 使用时间的详细统计信息,包括用户态和内核态的 CPU 时间。
cpuset
用于管理 CPU 亲和性和节点分配。可以设置进程在哪些 CPU 核心上运行,以及可以使用哪些内存节点,以下是 cpuset 子系统的一些常见控制文件
1.cpuset.cpus:这个文件用于设置 cgroup 中进程可以使用的 CPU 核心。可以使用 CPU 核心的列表、范围(如 "0-2,4"),或使用特定的 CPU 标识符(如 "0-2,4,^1" 表示使用 0、2、4 号核心,但不使用 1 号核心)
2.cpuset.mems:这个文件用于设置 cgroup 中进程可以使用的内存节点(NUMA Node)。可以使用节点的列表或范围,如 "0,1" 表示使用节点 0 和节点 1
3.cpuset.cpu_exclusive:这个文件用于设置是否将 cgroup 中的 CPU 分配限制为独占。设置为 1 表示独占,只有在没有其他 cgroup 使用时,才允许使用指定的 CPU 核心
4.cpuset.mem_exclusive:这个文件用于设置是否将 cgroup 中的内存节点分配限制为独占。设置为 1 表示独占,只有在没有其他 cgroup 使用时,才允许使用指定的内存节点。
memory
memory:用于管理和限制进程组(cgroup)中的内存资源使用。memory 子系统允许为每个 cgroup 设置内存限制,并控制进程在 cgroup 中使用的内存量。 以下是 memory 子系统的一些常见控制文件:
1.memory.limit_in_bytes:这个文件用于设置 cgroup 中进程的内存限制。可以设置一个整数值,表示 cgroup 中所有进程可使用的内存上限,单位为字节。超过该限制的内存请求将被拒绝,进程可能会受到 OOM(Out of Memory)事件的影响。
2.memory.soft_limit_in_bytes:这个文件用于设置 cgroup 中进程的软内存限制。软内存限制是一个较低的限制值,当系统内存不足时,它可以防止进程抢占过多的内存资源,但通常不会导致 OOM 事件
3.memory.memsw.limit_in_bytes:这个文件用于设置 cgroup 中进程的内存 + 交换空间(swap)的限制。可以设置一个整数值,表示 cgroup 中所有进程可使用的内存 + swap 的上限,单位为字节。
4.memory.memsw.usage_in_bytes:这个文件记录了 cgroup 中所有进程当前的内存 + swap 使用量,以字节为单位。
devices
用于管理和限制进程组(cgroup)中的设备访问权限。devices 子系统允许在 cgroup 中配置哪些设备可以被进程访问以及如何访问这些设备。 以下是 devices 子系统的一些常见控制文件:
1.devices.allow: 这个文件用于设置允许进程在 cgroup 中访问的设备列表。可以通过设备的主设备号和次设备号来指定具体的设备。例如,c 1:3 rwm 表示允许进程在 cgroup 中读取、写入和执行设备号为 1:3 的字符设备
2.devices.deny: 这个文件用于设置不允许进程在 cgroup 中访问的设备列表。与 devices.allow 相反,管理员可以在这里指定不允许访问的设备。
net_cls
用于将进程组(cgroup)中的网络流量标记(classify)为特定的网络类别(class)。net_cls 子系统允许为 cgroup 中的进程设置一个网络类别标记,从而可以在 Linux 内核的网络层对网络流量进行分类和管理。 以下是 net_cls 子系统的常见控制文件:
1.net_cls.classid: 这个文件用于设置 cgroup 中进程的网络类别标记。网络类别标记是一个 32 位无符号整数,用于标识特定的网络类别。当进程发送或接收网络流量时,Linux 内核会根据这个标记来对网络流量进行分类。
net_cls 子系统只负责将进程的网络流量标记为特定的网络类别,它本身并不限制网络带宽或执行其他网络控制。网络流量的实际控制需要依赖其他工具(如 tc)来完成。因此,在使用 net_cls 子系统时,需要结合其他网络管理工具来实现更全面的网络控制和管理。
net_prio
用于为进程组(cgroup)中的网络流量设置网络优先级(network priority)。net_prio 子系统允许管理员为 cgroup 中的进程设置特定的网络优先级,以控制其在网络传输中的优先级。 以下是 net_prio 子系统的常见控制文件:
1.net_prio.ifpriomap: 这个文件用于设置 cgroup 中进程的网络优先级映射(interface priority map)。通过配置映射关系,可以将 cgroup 中的进程的网络优先级映射到特定的网络接口(network interface)上。
2.net_prio.prioidx: 这个文件用于设置 cgroup 中进程的默认网络优先级索引(priority index)。优先级索引是一个整数值,表示进程的默认网络优先级。
net_prio 子系统在容器技术中特别有用,当多个容器运行在同一主机上时,可以为每个容器的 cgroup 设置不同的网络优先级,以实现容器之间的网络隔离和资源控制。这样可以确保不同容器之间的网络传输不会相互干扰,提高系统的网络性能和稳定性。
blkio
用于管理和限制进程组(cgroup)中的块设备(Block Device)I/O(Input/Output)资源使用。blkio 子系统允许管理员为每个 cgroup 设置块设备的 I/O 限制和控制。以下是 blkio 子系统的常见控制文件:
1.blkio.weight: 这个文件用于设置 cgroup 中进程的块设备 I/O 权重。块设备 I/O 权重用于在多个 cgroup 之间进行块设备 I/O 资源的分配,权重越高的 cgroup 获得越多的块设备 I/O 资源。
2.blkio.time: 这个文件用于设置 cgroup 中进程的块设备 I/O 时间片(time slice)配额。可以限制 cgroup 中进程在一定时间内的块设备 I/O 操作
3.blkio.throttle.read_bps_device: 这个文件用于设置 cgroup 中进程的块设备读取速率限制。可以设置块设备的主设备号和次设备号以及读取速率的限制,防止进程过度读取设备。
4.blkio.throttle.write_bps_device: 这个文件用于设置 cgroup 中进程的块设备写入速率限制。可以设置块设备的主设备号和次设备号以及写入速率的限制,防止进程过度写入设备。
pids
用于限制进程组(cgroup)中的进程数量。pids 子系统允许管理员为每个 cgroup 设置允许的最大进程数,从而控制 cgroup 中可以运行的进程数量。pids 子系统只限制进程数量,并不限制其他资源, 以下是 net_prio 子系统的常见控制文件:
1.pids.max: 这个文件用于设置 cgroup 中允许的最大进程数。可以设置一个整数值,表示 cgroup 中可以运行的最大进程数量。
2.pids.current: 这个文件显示当前 cgroup 中的进程数量。
应用场景
1.容器化部署:最典型的使用命名空间和控制组的场景就是容器化部署,如 Docker
2.资源管理:在多租户或共享资源的环境中,使用命名空间和控制组可以实现对资源的细粒度管理,如按服务分级,对不同级别进程按级别划分资源,避免低优先级进程抢占高优先级进程资源。
3.安全隔离:防止恶意程序对系统的攻击。例如,将不可信的应用程序运行在特定的命名空间中,限制其访问敏感文件或系统资源,以确保主机系统的安全性。
4.进程侵入:命名空间的隔离特性使得进程可以在不同的命名空间中运行,这为进程的监控和调试以及故障注入提供了便利。可以在特定的命名空间中追踪和调试以及故障注入进程,而不会对其他命名空间的进程造成干扰。
5.其他场景...
(一):初级入门使用方法
这篇笔记记录时间较早,当时对cgroup了解十分有限,笔记中存在有一些表述不对的地方,譬如进程的关联与解除一节中,写入task文件的应该是线程号(cgroup v1支持task文件),绑定进程应该使用接口文件cgroup.procsi。
介绍
内核官方文档参考页上关于cgroup-v1和cgroup-v2中做了很详细的介绍,链接可见文章尾部。
To mount a cgroup hierarchy with all available subsystems.
cgroup有多个subsystem,通过下面的命令可以挂载所有的subsystem:
mount -t cgroup xxx /sys/fs/cgroup
xxx可以是任意字符,CentOS默认为tmpfs,例如:
$df |grep cgroup
tmpfs 1940928 0 1940928 0% /sys/fs/cgroup
/sys/fs/cgroup/目录下就是所有可用的subsystem:
$ls /sys/fs/cgroup/
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb
memory net_cls net_cls,net_prio net_prio perf_event pids systemd
不同的subsystem有不同的用法。
进程的关联与解除
可以在每个subsystem目录下建立多个目录,每个目录就是一个cgroup,可以分别设置每个cgroup。
cgroup中可以继续创建cgroup。
将进程号写入对应的一个cgroup目录的task文件中,即将进程纳入了对应的cgroup。
纠错:写入task文件的是线程号,并且只有cgroup v1支持task文件,进程号应该写入接口文件cgroup.procs,见cgroups v1:cgroup的创建和进程绑定:@2019-02-18 11:45:26
将进程号写入另一个cgroup的task或者cgroup.procs文件后,自动将其从原先的cgroup的移除。
cgroup.procs会将同一个threadgroup中所有的进程都关联到cgroup。
pids
pids用来限制一个进程可以派生出的进程数量。
如果系统中没有挂载pids,先挂载:
mkdir -p /sys/fs/cgroup/pids
mount -t cgroup -o pids none /sys/fs/cgroup/pids
然后创建一个名为parent的目录,也就是一个cgroup:
mkdir -p /sys/fs/cgroup/pids/parent
设置最大进程数为3:
echo 3 > /sys/fs/cgroup/pids/parent/pids.max
将当前的shell进程关联到cgroup:
echo $$ > /sys/fs/cgroup/pids/parent/cgroup.procs
(二):资源限制cgroup v1和cgroup v2的详细介绍
cgroups - Linux control groups
之前简单学习过cgroup,当时了解地太浅了,遇到问题的时候,还是无法下手,于是深入学习下。 这篇笔记中的内容主要来自Linux手册:man 7 cgroups,cgroups - Linux control groups。
术语
这篇笔记有可能是第一篇详细、全面介绍了cgroup v1和cgroup v2的中文资料,有必要约定术语、统一口径,可以减少交流障碍。
process是“进程”,task是“线程”。
subsystem或者resource controllers是cgroup中某一类资源的管理器,例如管理cpu的叫做cpu controller,管理内存的叫做memory controller,统一称呼为“cgroup控制器”。
controller要使用mount -t cgroup样式的命令挂载到一个目录中,这个操作称呼为“挂载cgroup controller”。
从linux kernel 4.14开始,cgroup v2 引入了thread mode(线程模式),controller被分为domain controller和threaded controller,前者称为“cgroup进程控制器”,后者称为“cgroup线程控制器”。
从使用的角度看,cgroup就是一个目录树,目录中可以创建子目录,这些目录称为“cgroup 目录”,在一些场景中为了体现层级关系,还会称为“cgroup 子目录”。
每个目录中有一些用来设置对应controller的文件,这些文件称呼为“cgroup控制器的文件接口”。
cgroup v2引入了thread mode(线程模式)之后,cgroup目录有了类型之分:只管理进程的cgroup目录是domain cgroup,称为“进程(子)目录”;新增的管理线程的cgroup目录是threaded cgroup,称为“线程子目录”。
一句话介绍cgroup:把一个cgroup目录中的资源划分给它的子目录,子目录可以把资源继续划分给它的子目录,为子目录分配的资源之和不能超过父目录,进程或者线程可以使用的资源受到它们委身的目录中的资源的限制。
版本
cgroup有v1和v2两个版本,这是一个非常重要的信息。
v1版本是最早的实现,当时resource controllers的开发各自为政,导致controller间存在不一致,并且controller的嵌套挂载使cgroup的管理非常复杂。
Linux kernel 3.10 开始提供v2版本cgroup(Linux Control Group v2)。开始是试验特性,隐藏在挂载参数-o __DEVEL__sane_behavior中,直到Linuxe Kernel 4.5.0的时候,cgroup v2才成为正式特性。
cgroup v2希望完全取代cgroup v1,但是为了兼容,cgroup v1没有被移除。
cgroup v2实现的controller是cgroup v1的子集,可以同时使用cgroup v1和cgroup v2,但一个controller不能既在cgroup v1中使用,又在cgroup v2中使用。
cgroups version 1
cgroup v1中,controller可以独立挂载到一个cgroup目录中,也可以和其它controller联合挂载到同一个cgroup目录,cgroup v2也是采用挂载的方式,但是有一些不同(见后文)。
在cgroup v1中,task也就是线程可以被划分到不同的cgroup组中,在一些场景中,这样做是有问题的。
例如在memory controller中,所有task使用的都是同样的内存地址空间,为它们设置不同memory cgroup是没有意义的。(cgroup v2最初将task功能去掉了,后来引入了thread mode来限制线程占用的资源)
cgroups v1:controller 挂载
controller以tmpfs文件系统的样式挂载到任意目录,通常将其挂载到/sys/fs/cgroup目录。linux系统通常已经将多个controller挂载在/sys/fs/cgroup目录中了,下面的例子用另一个目录演示。
将多个controller挂载到同一个目录,如下:
mkdir -p /tmp/cgroup/cpu,cpuacct
mount -t cgroup -o cpu,cpuacct none /tmp/cgroup/cpu,cpuacct
-t cgroup指定挂载类型,-o指定挂载的controller(可以有多个,用“,”间隔)。
单独挂载cpu时,提示“已经挂载或者/tmp/cgroup/cpu is busy,暂时不清楚是怎么回事,可能是有一些controller不允许重复挂载。
$ mkdir /tmp/cgroup/cpu
$ mount -t cgroup -o cpu none /tmp/cgroup/cpu
mount: none is already mounted or /tmp/cgroup/cpu busy
挂载后,可以在挂载目录中看到controller的文件接口:
$ ls -F /tmp/cgroup/cpu,cpuacct/
cgroup.clone_children cpuacct.stat cpu.cfs_quota_us cpu.stat kube-proxy/ tasks
cgroup.event_control cpuacct.usage cpu.rt_period_us docker/ notify_on_release user.slice/
cgroup.procs cpuacct.usage_percpu cpu.rt_runtime_us kubelet/ release_agent
cgroup.sane_behavior cpu.cfs_period_us cpu.shares kubepods/ system.slice/
可以一次挂载所有的controller:
mount -t cgroup -o all cgroup /tmp/cgroup
还可以不挂载任何controller:
mount -t cgroup -o none,name=somename none /some/mount/point
没有挂载controller的cgroup可以用来跟踪进程,例如在进程消失导致cgroup为空时,cgroup的通知回调会被触发。
cgroups v1:controller 卸载
直接用umount卸载:
umount /sys/fs/cgroup/pids
卸载的时候要注意,需要先将所有子目录卸载,否则,umount只会让挂载点不可见,而不是真正将其卸载。
cgroups v1:支持的 controller
这个是重点,使用cgroup主要就是和各种controller打交道:
cpu, CFS Scheduler、 CFS Bandwidth Control :
since 2.6.24,限制CPU份额,只会在cpu忙碌的时候限制cpu使用,如果cpu空闲不做限制。
since 3.2.0, 引入了`CON‐FIG_CFS_BANDWIDTH`编译选项,限制进程在每个调度周期占用的时间,无论CPU是否空闲。
cpuacct,Documentation/cgroup-v1/cpuacct.txt:
since 2.6.24,统计一组task的cpu使用情况。
cpuset,Documentation/cgroup-v1/cpusets.txt:
since 2.6.24,绑定到特定的cpu
memory,Documentation/cgroup-v1/memory.txt:
since 2.6.25,报告和限制内存使用情况
devices,Documentation/cgroup-v1/devices.txt:
since 2.6.26,限制设备文件的创建,和对设备文件的读写
freezer,Documentation/cgroup-v1/freezer-subsystem.txt:
since 2.6.28,暂停、重载指定cgroup目录中的、以及它的子目录中的task
net_cls,Documentation/cgroup-v1/net_cls.txt:
since 2.6.29,为该cgroup内的task产生的报文打上classid
blkio,Documentation/cgroup-v1/blkio-controller.txt:
since 2.6.33,限制和控制对块设备的读写
perf_event,tools/perf/Documentation/perf-record.txt:
since 2.6.39,允许perf观测cgroup中的task
net_prio,Documentation/cgroup-v1/net_prio.txt:
since 3.3,设置net优先级
hugetlb,Documentation/cgroup-v1/hugetlb.txt:
since 3.5,限制huge page的使用
pids,Documentation/cgroup-v1/pids.txt:
since 4.3,限制cgroup中可创建的进程数
rdma,Documentation/cgroup-v1/rdma.txt:
since 4.11,限制RDMA/IB 资源
cgroups v1:cgroup的创建和进程绑定
cgroup是以目录的形式呈现的,/是cgroup的根目录,注意cgroup的根目录和挂载目录不是一回事,cgroup可能挂载在/sys/fs/cgroup或者/tmp/cgroup等任意目录,无论挂载在哪里,cgroup的根目录都是“/”。
假设cgroup的cpu controller的挂载点是/sys/fs/cgroup/cpu,那么目录/sys/fs/cgroup/cpu/cg1对应的cgroup的目录是/cg1。
为什么要强调这个,因为在调查一个kubelet的问题的时候,不确定--runtime-cgroups参数值中要不要包含挂载点路径,直到用cadvisor查出所有cgroup后,才确定不应该带有挂载路径。现在从Linux手册中找到支持了:
A cgroup filesystem initially contains a single root cgroup, '/',which all processes belong to. A new cgroup is created by creating a directory in the cgroup filesystem:
mkdir /sys/fs/cgroup/cpu/cg1
将进程的进程号直接写入到对应cgroup.procs文件中,就完成了进程与cgroup的绑定,例如:
echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
将进程绑定到cgroup之后,该进程创建的线程也一同被绑定到同一个cgroup。
每个进程只能绑定每个controller的一个cgroup目录,把进程的ID加到controller中的另一个cgroup目录的cgroup.procs文件中时,会自动将其从原先的cgroup.procs文件中移除。
每次只能向cgroup.procs中添加一个进程号,不能批量添加。
从cgroup.procs中读取的进程号的顺序是随意的,并且是可以重复的。
cgroup v1支持将线程(task)绑定到cgroup目录,只需将线程的线程ID写入目标cgroup目录的tasks文件中。
注意,tasks文件中是线程ID,cgroup.procs文件中是进程ID。
cgroups v1:cgroup的删除
在cgroup子目录已经全部删除,并且没有绑定任何进程、线程的情况下,直接删除cgroup目录即可。
cgroups v1:cgroup为空时,进行回调通知
每个cgroup中目录中都有下面两个文件:
release_agent notify_on_release
notify_on_release的内容是0或者1,指示kernel是否要在cgroup为空时(没有cgroup子目录、没有绑定任何进程)发出通知,0为不通知,1为通知。
release_agent是自行设置的、kernel通知时调用的命令, 它的命令行参数有且只有一个,就是当前为空的cgroup目录。
release_agent也可以在挂载cgroup的时候设置:
mount -o release_agent=pathname ...
cgroups version 2
cgroups v2支持的所有controller使用统一约定的、固定格式的cgroup目录,并且进程只能绑定到cgroup的“/”目录和目录树中的叶子节点,不能绑定到中间目录。变化还是比较大的,需要特别注意:
cgorup v2支持的controller默认自动挂载,并且挂载点中的目录结构是固定的
进程只能绑定到cgroup的“/”目录和cgroup目录树中的叶子节点
在cgroup.controllers和cgroup.subtree_control中设置cgroup目录中可用的controller
cgroup v1中的tasks文件被移除,cpuset controller中的cgroup.clone_children文件也被移除
cgroup目录为空时的通知机制得到改进,通过cgroup.events文件通知
内核文档Documentation/cgroup-v2.txt中有更详细的描述。
cgroups v2:controller 挂载
类型为cgroup2:
mount -t cgroup2 none /mnt/cgroup2
一个controller如果已经在cgroup v1中挂载了,那么在cgroup v2中就不可用。如果要在cgroup v2中使用,需要先将其从cgroup v1中卸载。
systemd用到了cgroup v1,会在系统启动时自动挂载controller,因此要在cgroup v2中使用的controller,最好通过内核启动参数cgroup_no_v1=list禁止cgroup v1使用:
cgroup_no_v1=list # list是用逗号间隔的多个controller
cgroup_no_v1=all # all 将所有的controller设置为对cgroup v1不可用
cgroups v2:支持的controller
从Linux kernel 4.14开始,cgroup v2支持线程模式(thread mode),引入该特性后,controller开始分为两类:domain controller(以进程为管理目标的控制器)和 threaded controller(以线程为管理目标的控制器)。
domain controller
io
since 4.5,cgroup v1 的 blkio 的继任者
memory
since 4.5,cgroup v1 的 memory 的继任者
pids
since 4.5,与 cgroup v1 中的 pids 是同一个
perf_event
since 4.5,与 cgroup v1 中的 perf_event 是同一个
rdma
since 4.11,与 cgroup v1 中的 rdma 是同一个
cpu
since 4.15,cgroup v1 的 cpu、cpuacct 的继任者
threaded controller
cpu
perf_event
pids
cgroups v2:cgroup目录
controller的启用/禁止文件
cgroup v2的cgroup目录中有一个cgroup.controllers文件和cgroup.subtree_control文件。
cgroup.controllers文件是只读的,内容是当前目录可用的controller。
cgroup.subtree_control是当前目录中可用的controller的子集,规定了直接子目录可用的controller,即定义了直接子目录中的cgroup.controllers文件的内容。
cgroup.subtree_control的内容格式如下,列出的controller用空格间隔,前面用“+”表示启用,用“-”表示不启用:
echo '+pids -memory' > x/y/cgroup.subtree_control
如果向cgroup.subtree_control中写入cgroup.controllers中不存在的controller,会得到ENOENT错误。
将进程绑定到叶子目录
在cgroup v2中,所有进程都只能绑定到cgroup的叶子目录中。假设cgroup的目录如下,这时候只能在CG1.1、CG1.2和CG2.1中绑定进程:
/
__ __
___/ \___
__/ \__
CG1 CG2
__ __ __
___/ \___ \_
__/ \__ \_
CG1.1 CG1.2 CG2.1
man 7 cgroups建议在每个目录下都建立一个名为leaf的目录,这个目录没有任何子目录,专门用来绑定进程。
根据这个建议,上面的目录应该调整成:
/
_______
_____/ \_____
___/ \___
CG1 CG2
__|__ ____
___/ | \___ ___/ \___
__/ | \__ __/ \__
CG1.1 leaf CG1.2 CG2.1 leaf
| | |
leaf leaf leaf
需要绑定cgroup进程的进程号都写在名为leaf的目录中。
进程只能绑定到目录树的叶子结点只是一个表面规则,本质规则是:
一个cgroup目录如果绑定了进程,那么它的cgroup.subtree_control必须为空,或者反过来必须不绑定进程,cgroup.subtree_control中才可以有内容。
More precisely, the rule is that a (nonroot) cgroup can't both (1) have member processes, and (2) distribute resources into child cgroups—that is, have a nonempty cgroup.subtree_control file.
回调通知设置文件
cgroup v1 中的release_agent和notify_on_release被移除了,cgroup v2 提供一个更通用的cgroup.events。
cgroup.events文件是只读的,每行一个key-value对,key和value之间用空格间隔。
现在(2019-01-29 17:39:09),cgroup.events文件中只存在一个名为populated的key,这个key对应的value是这个cgroup中包含的进程数。
如果使用cgroup v2,要用监控cgroup.events文件的方式感知cgroup的变化。
When monitoring this file using inotify(7), transitions generate IN_MODIFY events When monitoring the file using poll(2), transitions cause the bits POLLPRI and POLLERR to be returned in the revents field.
cgroup目录中的其它文件
前面已经接触到文件有:
cgroup.controllers
只读,当前目录中可用的controller
cgroup.subtree_control
读写,子目录中可用的controller
cgroup.events
只读,事件文件,每行一对key value,现在只有一个key
populated:value是cgroup中包含的进程数
除此之外还有:
cgroup.stat
只读,since 4.14,每行一对key value
nr_descendants:该cgroup中存活的子cgroup的数量
nr_dying_descendants:该cgroup中已经死亡的cgroup的数量
一个cgroup目录被删除后,先进入dying状态,等待系统回收。
cgroup.max.depth
since 4.14,当前目录的子目录的最大深度,0表示不能创建子目录,默认值"max"表示不限制,超过限制,返回EAGAIN
cgroup.max.descendants
since 4.14,当前目录中可以创建的活跃cgroup目录的最大数量,默认值"max"表示不限制,超过限制,返回EAGAIN
cgroup.type
since 4.14,位于"/"以外的cgroup目录,标记当前cgroup目录的类型,支持修改,支持以下类型:
domain:进程子目录,控制进程资源的cgroup
threaded:线程子目录,控制同一个进程下的线程资源
domain threaded:线程子目录的根目录,threaded root
domain invalid:线程子目录中处于无效状态的cgroup目录
domain invalid是一个中间状态,对类型为domain invalid的cgroup目录,只能做一个操作:将其类型修改threaded。 它存在的目的是为线程模式的扩展预留空间。
cgroups v2:授予非特权用户管理权限
授权方法:修改文件或目录的所有者
特权用户(root用户)可以授予非特权用户(普通用户)管理某个cgroup的权利。
授予方法很简单,直接将一些文件或目录的所有者改成被授权人就可以了,修改的文件或目录不同,被授权人拥有的权限不同。
按照文档中中例子,将cgroup目录/dlgt_grp的管理权授予普通用户时,可以有以下几种授权方式:
/dlgt_grp目录的所有者改为被授权人,被授权人拥有该目录下所有新建cgroup目录的管理权。
/dlgt_grp/cgroup.procs文件的所有者改为被授权人,被授权人拥有将进程绑定到该cgroup的权利。
/dlgt_grp/cgroup.subtree_control文件的所有者改为被授权人,被授权人可以决定在子目录中启用哪些出现在/dlgt_grp/cgroup.controllers中controller。
/dlgt_grp/cgroup.threads文件的所有者改为被授权人,线程模式下用到,授予将线程绑定到线程子目录的权利。
需要注意的是/dlgt_grp目录中controller的接口文件的所有者不应当被修改,这是上层cgroup目录授予当前目录的。
授权边界:用挂载参数nsdelegate开启
在挂载cgroup v2的时候,使用参数nsdelegate开启授权边界,如果cgroup v2已经挂载,可以remount开启,如下:
mount -t cgroup2 -o remount,nsdelegate none /sys/fs/cgroup/unified
使用该选项之后,cgroup namespace成为授权边界,会对cgroup中的进程进行下面的限制:
1. cgroup中的进程不能对所属cgroup目录中的controller接口文件进行写操作,否则报错`EPERM`
2. cgroup中的进程不能设置namespace边界外的cgroup中的绑定进程,否则报错`ENOENT`
cgroup namespace是什么?@2019-01-30 14:28:27
引入cgroup namespace作为授权边界是为了应对下面的情况:
1. 用户用cecilia得到了一个cgroup目录的管理权限,这个目录这里称为当前目录
2. 普通用户cecilia,在当前目录下创建了一个cgroup子目录,并将这个子目录授权给另一个人
这种情况下,cecilia同时拥有当前目录和子目录的操作权限,子目录中的进程也是cecilia用户的进程,会出现下面两种非法的操作:
1. 绑定到cecilia的cgroup子目录中的进程能够更改所属cgroup子目录中的controller接口文件
2. 绑定到cecilia的cgroup子目录中的进程能够将进程移出所在cgroup子目录,绑定到cecilia的当前cgroup目录
文档中举的例子是,cecilia创建一个容器,容器内的进程绑定到cecilia创建的cgroup子目录。意思似乎是在说:如果不以namespace为授权边界,那么容器内的进程就能够自己修改controller接口,并且具备脱离所属的cgroup目录、将自己绑定到上层cgroup目录的能力。
这样一来cecilia对容器内进程所做的cgroup限制就形同虚设。这样理解正好和授权边界引入的两个限制对应(或许正确 @2019-01-30 14:36:38)。
nsdelegate只在最初挂载的mount namespace中有效,在其它mount namespace中默认忽略:
The nsdelegate mount option only has an effect when performed in the initial mount namespace; in other mount namespaces, the option is silently ignored.
一个非特权进程只有下面的情况都满足时,才能向cgroup.procs中添加进程:
1. 拥有cgroup.procs文件的写权限
2. 拥有进程原先所属cgroup目录和将要加入的cgroup目录的共同上级目录中cgroup.procs文件的写权限
3. 如果启用了授权边界(nsdelegate),执行操作的进程必须能够看到被操作的进程原先所属cgroup目录和将要加入的cgroup目录
4. Linux kernel 4.11之前,执行操作的进程的UID和被操作进程的User ID或者Set-User-ID匹配
以上四条约束带来的直接结果是:被授权给非特权用户的cgroup绑定的第一个进程,只能是授权者设置的,不能由被授权者添加。因为被授权者不能对cgroup子目录中的第一个进程原先所在的cgroup中的cgroup.procs文件进行写操作。
cgroups v2:线程模式(THREAD MODE)
Linux kernerl 4.14开始,cgroup v2支持线程模式。
线程模式是指:
可以创建线程子目录(threaded subtrees),容纳绑定到该线程子目录的进程创建的所有线程,每个线程可以绑定线程子目录的cgroup子目录。
通过线程控制器(threaded controllers)为线程子目录中的cgroup目录分配资源。
在线程子目录中,非叶子目录可以绑定线程,注意,“只能绑定到叶子目录”的限制在这里放松了。
cgroups v2:线程controller
cpu
perf_event
pids
在线程子目录中不能使用domain controllers。
cgroups v2:线程模式的使用
创建线程子目录的方法
方法1
直接修改cgroup目录中的cgroup.type,写入threaded
上层cgroup目录(/目录除外)的类型自动变为domain threaded
当前目录的子目录和新建子目录的类型自动变成domain invalid,需要自行将其修改为threaded
方法2
在当前cgroup目录中启用一个或多个threaded controller,在当前目录中绑定一个进程
当前目录的类型自动变成domain threaded
当前目录的所有非threaded类型的子目录类型自动变成domain invalid,需要自行将其修改为threaded
注意如果上层目录的类型是domain invalid,当前目录的类型不能被修改。此外,将当前目录的类型修改为threaded时,需要满足两个条件:
当前目录的子目录都是空的,没有绑定任何进程
当前目录中的cgroup.subtree_control文件中没有启用domain controller配置,否则报错ENOTSUP。
即domain controllers不能在线程子目录中使用。
绑定到线程子目录
将进程ID直接写入线程子目录的cgroup.procs文件中,该进程以及它的线程就都一同绑定到了线程子目录中。然后可以将这个进程创建的多个线程ID,写入到它绑定的线程子目录的以及子目录的子目录中的cgroup.threads文件,实现线程的绑定。
线程只能绑定到创建它的进程所在的线程子目录树。
domain controller是不关心线程的,它看到的都是进程。
实行线程绑定操作时,需要满足以下条件:
执行绑定操作的进程有目标目录中的cgroup.threads文件的写权限
执行绑定操作的进程需要有源目录和目标目录的共同祖先目录中的cgroup.procs文件的写权限
源目录和目标目录必须在同一个线程子目录中,即创建线程的进程所在的线程子目录中,即线程不能与进程分离,否则报错EOPNOTSUPP。
进程子目录中的根目录中cgroup.procs是可读写的,进程子目录中非根目录中的cgroup.procs文件是不可读写的。
cgroup.threads在每个cgroup目录中都存在,包括类型为domain的cgroup目录。
cgroup的内核接口文件
/proc/cgroups(since 2.6.24)列出了内核支持的cgroup contoller,以及它们的使用情况:
#subsys_name hierarchy num_cgroups enabled
cpuset 4 1 1
cpu 8 1 1
cpuacct 8 1 1
blkio 6 1 1
memory 3 1 1
devices 10 84 1
freezer 7 1 1
net_cls 9 1 1
perf_event 5 1 1
net_prio 9 1 1
hugetlb 0 1 0
pids 2 1 1
四列数据的含义分别是:controller名称、挂载位置ID(在cgroup v2中都为0)、使用该controller的cgroup数量、是否启用。
/proc/[pid]/cgroup(since 2.6.24)进程所属的cgroup,内容格式如下:
hierarchy-ID:controller-list:cgroup-path
例如一个容器内进程的cgroup:
# cat /proc/1427/cgroup
11:blkio:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
10:freezer:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
9:net_prio,net_cls:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
8:devices:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
7:cpuset:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
6:memory:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
5:perf_event:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
4:pids:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
3:cpuacct,cpu:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
2:hugetlb:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
1:name=systemd:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
第一列hierarchy-ID与/proc/cgroups的第二列对应,在cgroups v2中hierarchy-ID都为0;第二列是controller名称;第三列是cgroup目录路径。
/sys/kernel/cgroup/delegate(since 4.15):cgroups v2 中可以授权给其它用户的cgroup文件。
/sys/kernel/cgroup/features(since 4.15):cgroups v2 支持的功能特性。
手上没有kernel 4.15的环境,下面是文档中的例子:
$ cat /sys/kernel/cgroup/delegate
cgroup.procs
cgroup.subtree_control
cgroup.threads
$ cat /sys/kernel/cgroup/features
nsdelegate
(三):cgroup controller汇总和控制器的参数(文件接口)
这里将罗列cgroup支持的controllers,每个controller的文件接口或者说是配置参数,以及它们的含义。这是一项持续性工作,这篇笔记不会一次性完成,而是逐渐补充丰富:@2019-02-18 15:55:04
又找到一些关于cgroup的好资料,感谢google:
Managing system resources on Red Hat Enterprise Linux 7
Namespace and cgroups, the Building Blocks of Linux containers
这两篇文档都对cgroup进行了全景式介绍,第一篇侧重cgroup在Red Hat中的应用,从Red Hat用户的角度讲述,第二篇侧重cgroup的内核实现,从内核开发者的角度讲述。
cgroup controller列表
Linux kernel 4.4 支持的cgroup controller,cgroup v1,总计12个controller @2019-02-18 16:25:41:
Name Kernel Object name Module
-------------------------------------------------------------------------------
blkio io_cgrp_subsys block/blk-cgroup.c
cpu cpu_cgrp_subsys kernel/sched/core.c
cpuacct cpuacct_cgrp_subsys kernel/sched/cpuacct.c
cpuset cpuset_cgrp_subsys kernel/cpuset.c
devices devices_cgrp_subsys security/device_cgroup.c
freezer freezer_cgrp_subsys kernel/cgroup_freezer.c
memory memory_cgrp_subsys mm/memcontrol.c
net_cls net_cls_cgrp_subsys net/core/netclassid_cgroup.c
perf_event perf_event_cgrp_subsys kernel/events/core.c
hugetlb hugetlb_cgrp_subsys mm/hugetlb_cgroup.c
pids pids_cgrp_subsys kernel/cgroup_pids.c
net_prio net_prio_cgrp_subsys net/core/netprio_cgroup.c
Red Hat Enterprise Linux 7中可用cgroup controllers,总计10个 @2019-02-18 15:50:06:
blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、perf_event、hugetlb、
Kernel 4.4支持、Red Hat Enterprise Linux 7还没有支持的cgroup controllers,总计2个 @2019-02-18 15:50:16:
pids、net_prio
cgroup v2 支持的controller,总计6个(2019-02-18 15:49:51):
cpu、memory、io、pid、rdma、perf_event
cgroup v1 支持的controller
blkio
sets limits on input/output access to and from block devices;
内核文档:Block IO Controller
cpu
uses the CPU scheduler to provide cgroup tasks access to the CPU. It is mounted together with the cpuacct controller on the same mount;
内核文档:Real-Time group scheduling、CFS Bandwidth Control。
cpu:实时任务调度相关参数
cpu.rt_period_us和cpu.rt_runtime_us是Real-Time group调度的参数。
cpu.rt_period_us:含义与sched_rt_period_us相同。
cpu.rt_runtime_us:含义与sched_rt_runtime_us相同。
Real-Time group是一些需要间歇性实时运行的任务(后面简称实时任务),这些任务不是一直运行的,而是周期性运行,但是运行周期到来以后,它们必须在运行,不能因为其它进程正在使用CPU而被延迟运行。
有两个内核参数用来调节实时任务占用的CPU资源:
/proc/sys/kernel/sched_rt_period_us:定义100% CPU对应的时间,可以理解为计算CPU使用率时使用的基准时间段,单位是微秒,默认是1000000(1s)。
3.12. Real Time Throttling中对这个参数介绍是最可理解的,并且能和sched_rt_runtime_us互相印证的,原句是Defines the period in μs (microseconds) to be considered as 100% of CPU bandwidth.,重点是to be considered,一些中文翻译感觉翻译的走样了。
/proc/sys/kernel/sched_rt_runtime_us:实时任务最高占据的CPU带宽,单位是微秒,默认值950000(0.95s)表示95%的CPU(注意的sched_rt_period_us的数值1s对上了)。如果值为-1,实时任务将占用100%的CPU,会存在非实时任务完全得不到执行的风险,设置时需要高度谨慎。
内核文档Real-Time group scheduling中举了两个例子,让人不太理解:
第一个例子是每秒钟要渲染25帧画面的任务,每一帧的渲染周期是0.04秒,如果分配给它80%的CPU时间,运行时间是0.04 * 0.8=0.32s,这个让人有点不理解。
假设把80%的cpu分配给该实时任务,那么为了保证每秒依旧产出25帧,0.04 * 0.8 = 0.32的意思说应该是,渲染周期需要被压缩到0.32s,但是kernerl中说这个时间是运行时间。
This way the graphics group will have a 0.04s period with a 0.032s run time limit”
揣测一下,原意应该是说:每间隔0.04秒渲染一帧,这0.04s中只有80%的时间用于渲染帧,所以实时任务的运行时间是0.32s。
这样理解是否正确只能通过扒内核源码验证了,现在无力验证..丧…(2019-02-19 11:14:08),第二个例子类似,这里不罗列了。
注意sched_rt_runtime_us是实时任务的保证时间和最高占用时间,如果实时任务没有使用,可以分配给非实时任务,并且实时任务最终占用的时间不能超过这个数值,参考Linux-85 关于sched_rt_runtime_us 和 sched_rt_period_us。
对cpu.rt_period_us参数的限制是必须小于父目录中的同名参数值。
对cpu.rt_runtime_us的限制是:
\Sum_{i} runtime_{i} / global_period <= global_runtime / global_period
即:
\Sum_{i} runtime_{i} <= global_runtime
当前的实时进程调度算法可能导致部分实时进程被饿死,如下A和B是并列的,A的运行时时长正好覆盖了B的运行时间:
* group A: period=100000us, runtime=50000us
- this runs for 0.05s once every 0.1s
* group B: period= 50000us, runtime=25000us
- this runs for 0.025s twice every 0.1s (or once every 0.05 sec).
Real-Time group scheduling中提出正在开发SCHED_EDF (Earliest Deadline First scheduling),优先调度最先结束的实时进程。
cpu:CPU带宽控制CFS的相关参数
CFS Bandwidth Control控制一组进程可以使用的cpu时间,它针对的非实时的进程,约定了一组进程在一个时间周期中可以使用的CPU的时长,如果使用时间超了,该组进程会被限制运行,直到进入下一个周期,重新获得CPU时间。
cpu.cfs_period_us:一个周期的时长,单位微秒,默认值100毫秒,最大值1s,最小1ms。
cpu.cfs_quota_us:在一个周期内的可以使用的cpu时间,单位微秒,最小1ms,默认值-1表示CPU使用没有限制。
cpu.stat:限流统计,包含三个值:
- nr_periods: Number of enforcement intervals that have elapsed.
- nr_throttled: Number of times the group has been throttled/limited.
- throttled_time: The total time duration (in nanoseconds) for which entities of the group have been throttled.
内核参数/proc/sys/kernel/sched_cfs_bandwidth_slice_us(默认值5秒)的用途没明白,摘录原文:
For efficiency run-time is transferred between the global pool and CPU local "silos" in a batch fashion. This greatly reduces global accounting pressure on large systems. The amount transferred each time such an update is required is described as the "slice".
如果父目录中分配的CPU时间用尽了,子目录即使还有CPU运行时间,也会被限制。
以250ms为周期,占用1个CPU:
# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
# echo 250000 > cpu.cfs_period_us /* period = 250ms */
以500ms为周期,占用2个CPU,quota > period:
# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
# echo 500000 > cpu.cfs_period_us /* period = 500ms */
以50ms为周期,占用20%CPU:
# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
# echo 50000 > cpu.cfs_period_us /* period = 50ms */
cpuacct
creates automatic reports on CPU resources used by tasks in a cgroup. It is mounted together with the cpu controller on the same mount;
内核文档:CPU Accounting Controller
cpuacct.usage:该cgroup实际获得的cpu时间,单位是纳秒,/sys/fs/cgroup/cpuacct/cpuacct.usage是系统上所有进程获得的cpu时间。
cpuacct.stat:将该cgroup获得的时间按照user time和system time分开呈现,单位是USER_HZ:
user 24
system 9
cpuacct.usage_percpu:该cgroup在每个cpu上占用的时间,单位是纳秒,累加值等于 cpuacct.usage:
//系统上一共有4个CPU,为容器指定cpuset 0,1,--cpuset-cpus "1,2"
325517080 54277298 0 0
//系统上一共有4个CPU,为容器指定cpuset 1,2,--cpuset-cpus "1,2"
0 21977223 28985453 0
cpuset
assigns individual CPUs (on a multicore system) and memory nodes to tasks in a cgroup;
内核文档:CPUSETS
devices
allows or denies access to devices for tasks in a cgroup;
内核文档:Device Whitelist Controller
freezer
suspends or resumes tasks in a cgroup;
内核文档:freezer-subsystem
memory
sets limits on memory use by tasks in a cgroup and generates automatic reports on memory resources used by those tasks;
内核文档:Memory Resource Controller、memcg_test.txt
net_cls
tags network packets with a class identifier (classid) that allows the Linux traffic controller (the tc command) to identify packets originating from a particular cgroup task. A subsystem of net_cls, the net_filter (iptables) can also use this tag to perform actions on such packets. The net_filter tags network sockets with a firewall identifier (fwid) that allows the Linux firewall (the iptables command) to identify packets (skb->sk) originating from a particular cgroup task;
内核文档:Network classifier cgroup
perf_event
enables monitoring cgroups with the perf tool;
hugetlb
allows to use virtual memory pages of large sizes and to enforce resource limits on these pages.
pids
内核文档:Process Number Controller
net_prio
内核文档:Network priority cgroup
rdma
内核文档:RDMA Controller
cgroup v2 支持的controller
内核文档:Control Group v2。
cpu
memory
io
pid
rdma
perf_event
参考链接
Control Group v1
Control Group v2
Linux control groups
CFS Bandwidth Control
3.12. Real Time Throttling
Real-Time group scheduling
Linux资源管理之cgroups简介
Managing system resources on Red Hat Enterprise Linux 7
Namespace and cgroups, the Building Blocks of Linux containers
Linux的cgroup功能(一):初级入门使用方法
Linux的cgroup功能(二):资源限制cgroup v1和cgroup v2的详细介绍
Linux的cgroup功能(三):cgroup controller汇总和控制器的参数(文件接口)
Linux 命名空间和控制组:实现资源隔离与管理的双重利器
Linux 命名空间(Namespace)和控制组(Cgroups)这是实现资源控制管理的两个关键技术。通过命名空间可以实现对资源的隔离,让进程在独立的空间中运行,增强系统的安全性。而通过控制组可以有效地管理和限制进程的资源使用,避免资源浪费,提高了系统性能。
Linux 命名空间 (Namespace)
这是一种隔离机制,允许将全局系统资源划分为多个独立的、相互隔离的部分,使得在不同的命名空间中运行的进程感知不到其他命名空间的存在。从而实现了对进程、网络、文件系统、IPC(进程间通信)等资源的隔离,减少了潜在的安全风险。例如,在容器中运行应用程序可以避免对主机系统的直接影响,从而提高了系统的安全性。
Linux 控制组 (Cgroups)
控制组是一种资源管理机制,允许对进程组或任务组应用资源限制和优先级设置。它可以用来限制一组进程的资源使用,如 CPU、内存、磁盘 I/O 等,从而实现资源的分配和控制。
简单来说 Cgroups 可以理解为是房子的土地面积,限制了房子的大小 ,而 Namespace 是房子的墙,与邻居互相隔离。通过使用命名空间和控制组,可以更有效地使用系统资源,避免资源浪费,并确保关键任务获得足够的资源支持,从而提高系统性能和效率。这些功能对于现代的云计算和容器化部署是至关重要的。最典型的容器技术 Docker 就是利用 namespace 和 cgroup 实现的。
命名空间类型
下面是 Linux 提供的 Namespace 类型,通过这些命名空间的组合,可以实现复杂的隔离和虚拟化配置

PID 命名空间
Linux PID 命名空间是 Linux Namespace 的一种类型,用于隔离进程 ID。在一个 PID 命名空间中,每个进程拥有独立的进程 ID,这样在不同的命名空间中可以有相同的进程 ID,而不会产生冲突。每个子 PID 命名空间中都有 PID 为 1 的 init 进程,对应父命名空间中的进程,父命名空间对子命名空间运行状态是不隔离的,但是每一个子命名空间是互相隔离的。
如下图:在子命名空间 A 和 B 中都有一个进程 ID=1 的 init 进程,这两个进程实际上是父命名空间的 55 号和 66 号进程 ID, 虚拟化出来的空间而已。

UTS 命名空间
Linux UTS 命名空间用于隔离主机名和域名。在 UTS 命名空间中,每个进程可以拥有独立的主机名和域名 (nodename,domainname),这样可以在不同的命名空间中拥有不同的标识,从而实现了主机名和域名的隔离。
nodename: 是用于标识主机的独特名称,通常也被称为主机名。它用于在网络中唯一地标识一台计算机
domainname: 是主机的域名部分,通常用于标识所属的网络域。域名通常由多个部分组成,按照从右到左的顺序,每个部分之间用点号 "." 分隔。域名用于将主机名与特定的网络域关联起来,从而帮助在全球范围内定位和访问计算机
在容器技术中,利用 UTS Namespace 隔离后,容器内的进程可以拥有独立的主机名和域名,而不会与宿主系统或其他容器中的进程产生冲突。这样,容器内的应用程序可以认为它们在独立的主机中运行,从而更容易进行配
Mount 命名空间
Linux Mount Namespace 用于隔离文件系统挂载点。通过 Mount Namespace,不同的进程可以在不同的挂载点上看到不同的文件系统层次结构,即使在同一台主机上运行。这种隔离使得进程在一个 Mount Namespace 中的挂载操作对其他 Mount Namespace 中的进程不可见,从而实现了文件系统层面的隔离。
在容器技术中,利用 Mount Namespace 隔离后,容器内部的文件系统挂载与宿主系统和其他容器相互隔离。这样,每个容器可以拥有独立的文件系统视图,容器内的进程只能访问自己的文件系统层次结构,而无法访问其他容器或宿主系统的文件系统。
Network 命名空间
Linux Network Namespace 用于隔离网络栈。通过 Network Namespace,不同的进程可以拥有独立的网络设备、IP 地址、路由表、网络连接和网络命名空间中的其他网络资源。这种隔离使得进程在一个 Network Namespace 中的网络配置和状态对其他 Network Namespace 中的进程不可见,从而实现了网络层面的隔离。
在容器技术中,利用 Network Namespace 隔离后,容器内部的进程拥有独立的网络环境,从而使得容器在网络上彼此隔离。每个容器可以有自己的网络设备、IP 地址、路由表和网络连接,容器之间不会干扰彼此,也不会干扰宿主系统。
User 命名空间
Linux User Namespace 用于隔离用户和用户组 ID。通过 User Namespace,不同的进程可以拥有独立的用户和用户组 ID,这样可以在不同的命名空间中拥有不同的身份标识,从而实现了用户和用户组的隔离。
在容器技术中,利用 User Namespace 隔离后,容器内的进程可以拥有独立的用户和用户组 ID,而不会与宿主系统或其他容器中的用户产生冲突。这样容器内的应用程序可以以普通用户身份运行,而不需要在宿主系统中创建相同的用户账号。
在 Docker 中默认是不启用 User Namespace 隔离的,主要是因为开启后需要做很多特殊的配合和管理,例如隔离后容器内的用户和宿主上的用户已经不是相同的身份了,那么可能会影响访问文件系统。
IPC 命名空间
Linux IPC 命名空间用于隔离进程间通信资源。在 IPC 命名空间中,每个命名空间都有独立的 IPC 资源,如消息队列、信号量和共享内存,使得不同命名空间中的进程无法直接访问其他命名空间的 IPC 资源,从而实现了 IPC 资源的隔离。
在容器技术中,利用 IPC Namespace 隔离后,容器内的进程拥有独立的 IPC 资源,从而避免不同容器之间的进程干扰和资源冲突。每个容器都可以有自己的 IPC 命名空间,使得容器内的进程在进行进程间通信时只能访问属于同一命名空间的 IPC 资源,而无法直接访问其他容器的 IPC 资源。
在 Linux 系统中提供了以下几种常用的创建和管理命名空间的 API:
1.clone:使用 clone 系统调用创建一个新进程时可以通过指定一个或多个上面列出的命名空间标志参数来创建新的命名空间,并且新进程的子进程也会默认被包含在新的命名空间内
2.unshare:使用 unshare 系统调用将一个已存在的进程放入新的命名空间。它可以指定一个或多个上面列出的命名空间标志参数,创建具有指定类型的命名空间,并将当前进程或其他指定进程放入其中
3.setns: 使用 setns 系统调用允许进程将自己放入已经存在的命名空间中,而无需创建新的进程。通过 setns 系统调用,进程可以切换到指定类型的命名空间中,与其他已存在于该命名空间中的进程共享同一个隔离环境
Linux 控制组(Cgroups)
Cgroups 介绍
在 Linux 中,cgroups(Control Groups)是一种用于资源管理和限制进程资源使用的机制。它允许管理员将一组进程组织在一个或多个 cgroups 中,并为每个 cgroup 分配特定的资源限制,如 CPU、内存、磁盘 I/O、网络带宽等。通过 cgroups,可以更好地控制系统中各个进程的资源使用,实现资源隔离和公平共享,防止某些进程占用过多资源导致系统负载过高。
核心组件
Cgroups 主要有三个核心组件组成:
子系统(subsystem):子系统是用于管理特定类型资源的模块,每个子系统负责管理一类资源,例如 CPU、内存、磁盘 I/O、网络带宽等。
Cgroup Hierarchies(Cgroup 层级树):Hierarchies 是用于组织和管理 Cgroup 的结构。一个 Hierarchies 由一个或多个 subsystem 组成。Hierarchies 形成树状结构,每个节点都是一个 Cgroup,同一层级中的 Cgroup 之间是平级的。Hierarchies 允许 Cgroup 在不同的 subsystem 中进行组合和嵌套,形成多层的资源管理结构。系统会默认为每个 subsystem 创建一个默认的 Hierarchy。
Cgroups(控制组):Cgroup 是最终的资源管理单元,它是一组进程的集合,被组织在一个特定的 Cgroup 层级中,并受到该层级中控制器的资源限制和管理。
核心组件关系

关系说明如下:
1.默认系统会为所有的 subsystem 创建 Hierarchy 树,默认 cgroup 根路径在 /sys/fs/cgroup 目录下,在 /sys/fs/cgroup 下可以看到全部的 subsystem 对应的 Hierarchy 树
2.系统创建了新的 Hierarchy 后,默认所有进程都会加入到树中根节点的 cgroup 中,例如可以到 /sys/fs/cgroup/cpu/tasks 文件中看到所有的进程列表
3.一个 subsystem 只能附加到一个 Hierarchy 树上
4.一个 Hierarchy 树可以对应多个 subsystem
5.一个进程可以作为多个 cgroup 的成员,但是这些 cgroup 必须在不同的 Hierarchy 树中
6.一个进程 fork 出子进程时,子进程和父进程默认是在同一个 cgroup 中的,也可以自行移动到其他 cgroup 中
Subsystem (常见子系统) 列表
cpu
用于管理 CPU 资源。它允许设置进程的 CPU 使用率和时间片配额,从而限制进程的 CPU 占用,以下是 cpu 子系统的一些常见控制文件:
1.cpu.cfs_quota_us:这个文件用于设置 cgroup 中进程的 CPU 配额。单位为微秒(μs),表示在每个 cpu.cfs_period_us 微秒内,cgroup 中的进程可以使用的 CPU 时间量。如果 cpu.cfs_quota_us 的值为 -1,则表示 cgroup 中的进程没有 CPU 使用限制。
2.cpu.cfs_period_us:这个文件用于设置 cgroup 中进程的 CPU 时间周期。单位为微秒(μs),表示在一个周期内,cgroup 中的进程可以使用的 CPU 时间总量。
cpuacct
cpuacct:用于统计和记录进程组(cgroup)中的 CPU 使用情况,以下是 cpuacct 子系统的一些常见统计文件:
1.cpuacct.usage: 这个文件记录了 cgroup 中所有进程的 CPU 使用时间总和,以纳秒为单位。可以通过读取这个文件来查看 cgroup 中的所有进程所消耗的 CPU 时间。
2.cpuacct.usage_percpu: 这个文件记录了 cgroup 中每个 CPU 核心的 CPU 使用时间,以纳秒为单位。如果系统有多个 CPU 核心,这个文件会显示每个核心的 CPU 使用时间。
3.cpuacct.stat: 这个文件提供了关于 CPU 使用时间的详细统计信息,包括用户态和内核态的 CPU 时间。
cpuset
用于管理 CPU 亲和性和节点分配。可以设置进程在哪些 CPU 核心上运行,以及可以使用哪些内存节点,以下是 cpuset 子系统的一些常见控制文件
1.cpuset.cpus:这个文件用于设置 cgroup 中进程可以使用的 CPU 核心。可以使用 CPU 核心的列表、范围(如 "0-2,4"),或使用特定的 CPU 标识符(如 "0-2,4,^1" 表示使用 0、2、4 号核心,但不使用 1 号核心)
2.cpuset.mems:这个文件用于设置 cgroup 中进程可以使用的内存节点(NUMA Node)。可以使用节点的列表或范围,如 "0,1" 表示使用节点 0 和节点 1
3.cpuset.cpu_exclusive:这个文件用于设置是否将 cgroup 中的 CPU 分配限制为独占。设置为 1 表示独占,只有在没有其他 cgroup 使用时,才允许使用指定的 CPU 核心
4.cpuset.mem_exclusive:这个文件用于设置是否将 cgroup 中的内存节点分配限制为独占。设置为 1 表示独占,只有在没有其他 cgroup 使用时,才允许使用指定的内存节点。
memory
memory:用于管理和限制进程组(cgroup)中的内存资源使用。memory 子系统允许为每个 cgroup 设置内存限制,并控制进程在 cgroup 中使用的内存量。 以下是 memory 子系统的一些常见控制文件:
1.memory.limit_in_bytes:这个文件用于设置 cgroup 中进程的内存限制。可以设置一个整数值,表示 cgroup 中所有进程可使用的内存上限,单位为字节。超过该限制的内存请求将被拒绝,进程可能会受到 OOM(Out of Memory)事件的影响。
2.memory.soft_limit_in_bytes:这个文件用于设置 cgroup 中进程的软内存限制。软内存限制是一个较低的限制值,当系统内存不足时,它可以防止进程抢占过多的内存资源,但通常不会导致 OOM 事件
3.memory.memsw.limit_in_bytes:这个文件用于设置 cgroup 中进程的内存 + 交换空间(swap)的限制。可以设置一个整数值,表示 cgroup 中所有进程可使用的内存 + swap 的上限,单位为字节。
4.memory.memsw.usage_in_bytes:这个文件记录了 cgroup 中所有进程当前的内存 + swap 使用量,以字节为单位。
devices
用于管理和限制进程组(cgroup)中的设备访问权限。devices 子系统允许在 cgroup 中配置哪些设备可以被进程访问以及如何访问这些设备。 以下是 devices 子系统的一些常见控制文件:
1.devices.allow: 这个文件用于设置允许进程在 cgroup 中访问的设备列表。可以通过设备的主设备号和次设备号来指定具体的设备。例如,c 1:3 rwm 表示允许进程在 cgroup 中读取、写入和执行设备号为 1:3 的字符设备
2.devices.deny: 这个文件用于设置不允许进程在 cgroup 中访问的设备列表。与 devices.allow 相反,管理员可以在这里指定不允许访问的设备。
net_cls
用于将进程组(cgroup)中的网络流量标记(classify)为特定的网络类别(class)。net_cls 子系统允许为 cgroup 中的进程设置一个网络类别标记,从而可以在 Linux 内核的网络层对网络流量进行分类和管理。 以下是 net_cls 子系统的常见控制文件:
1.net_cls.classid: 这个文件用于设置 cgroup 中进程的网络类别标记。网络类别标记是一个 32 位无符号整数,用于标识特定的网络类别。当进程发送或接收网络流量时,Linux 内核会根据这个标记来对网络流量进行分类。
net_cls 子系统只负责将进程的网络流量标记为特定的网络类别,它本身并不限制网络带宽或执行其他网络控制。网络流量的实际控制需要依赖其他工具(如 tc)来完成。因此,在使用 net_cls 子系统时,需要结合其他网络管理工具来实现更全面的网络控制和管理。
net_prio
用于为进程组(cgroup)中的网络流量设置网络优先级(network priority)。net_prio 子系统允许管理员为 cgroup 中的进程设置特定的网络优先级,以控制其在网络传输中的优先级。 以下是 net_prio 子系统的常见控制文件:
1.net_prio.ifpriomap: 这个文件用于设置 cgroup 中进程的网络优先级映射(interface priority map)。通过配置映射关系,可以将 cgroup 中的进程的网络优先级映射到特定的网络接口(network interface)上。
2.net_prio.prioidx: 这个文件用于设置 cgroup 中进程的默认网络优先级索引(priority index)。优先级索引是一个整数值,表示进程的默认网络优先级。
net_prio 子系统在容器技术中特别有用,当多个容器运行在同一主机上时,可以为每个容器的 cgroup 设置不同的网络优先级,以实现容器之间的网络隔离和资源控制。这样可以确保不同容器之间的网络传输不会相互干扰,提高系统的网络性能和稳定性。
blkio
用于管理和限制进程组(cgroup)中的块设备(Block Device)I/O(Input/Output)资源使用。blkio 子系统允许管理员为每个 cgroup 设置块设备的 I/O 限制和控制。以下是 blkio 子系统的常见控制文件:
1.blkio.weight: 这个文件用于设置 cgroup 中进程的块设备 I/O 权重。块设备 I/O 权重用于在多个 cgroup 之间进行块设备 I/O 资源的分配,权重越高的 cgroup 获得越多的块设备 I/O 资源。
2.blkio.time: 这个文件用于设置 cgroup 中进程的块设备 I/O 时间片(time slice)配额。可以限制 cgroup 中进程在一定时间内的块设备 I/O 操作
3.blkio.throttle.read_bps_device: 这个文件用于设置 cgroup 中进程的块设备读取速率限制。可以设置块设备的主设备号和次设备号以及读取速率的限制,防止进程过度读取设备。
4.blkio.throttle.write_bps_device: 这个文件用于设置 cgroup 中进程的块设备写入速率限制。可以设置块设备的主设备号和次设备号以及写入速率的限制,防止进程过度写入设备。
pids
用于限制进程组(cgroup)中的进程数量。pids 子系统允许管理员为每个 cgroup 设置允许的最大进程数,从而控制 cgroup 中可以运行的进程数量。pids 子系统只限制进程数量,并不限制其他资源, 以下是 net_prio 子系统的常见控制文件:
1.pids.max: 这个文件用于设置 cgroup 中允许的最大进程数。可以设置一个整数值,表示 cgroup 中可以运行的最大进程数量。
2.pids.current: 这个文件显示当前 cgroup 中的进程数量。
应用场景
1.容器化部署:最典型的使用命名空间和控制组的场景就是容器化部署,如 Docker
2.资源管理:在多租户或共享资源的环境中,使用命名空间和控制组可以实现对资源的细粒度管理,如按服务分级,对不同级别进程按级别划分资源,避免低优先级进程抢占高优先级进程资源。
3.安全隔离:防止恶意程序对系统的攻击。例如,将不可信的应用程序运行在特定的命名空间中,限制其访问敏感文件或系统资源,以确保主机系统的安全性。
4.进程侵入:命名空间的隔离特性使得进程可以在不同的命名空间中运行,这为进程的监控和调试以及故障注入提供了便利。可以在特定的命名空间中追踪和调试以及故障注入进程,而不会对其他命名空间的进程造成干扰。
5.其他场景...
(一):初级入门使用方法
这篇笔记记录时间较早,当时对cgroup了解十分有限,笔记中存在有一些表述不对的地方,譬如进程的关联与解除一节中,写入task文件的应该是线程号(cgroup v1支持task文件),绑定进程应该使用接口文件cgroup.procsi。
介绍
内核官方文档参考页上关于cgroup-v1和cgroup-v2中做了很详细的介绍,链接可见文章尾部。
To mount a cgroup hierarchy with all available subsystems.
cgroup有多个subsystem,通过下面的命令可以挂载所有的subsystem:
mount -t cgroup xxx /sys/fs/cgroup
xxx可以是任意字符,CentOS默认为tmpfs,例如:
$df |grep cgroup
tmpfs 1940928 0 1940928 0% /sys/fs/cgroup
/sys/fs/cgroup/目录下就是所有可用的subsystem:
$ls /sys/fs/cgroup/
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb
memory net_cls net_cls,net_prio net_prio perf_event pids systemd
不同的subsystem有不同的用法。
进程的关联与解除
可以在每个subsystem目录下建立多个目录,每个目录就是一个cgroup,可以分别设置每个cgroup。
cgroup中可以继续创建cgroup。
将进程号写入对应的一个cgroup目录的task文件中,即将进程纳入了对应的cgroup。
纠错:写入task文件的是线程号,并且只有cgroup v1支持task文件,进程号应该写入接口文件cgroup.procs,见cgroups v1:cgroup的创建和进程绑定:@2019-02-18 11:45:26
将进程号写入另一个cgroup的task或者cgroup.procs文件后,自动将其从原先的cgroup的移除。
cgroup.procs会将同一个threadgroup中所有的进程都关联到cgroup。
pids
pids用来限制一个进程可以派生出的进程数量。
如果系统中没有挂载pids,先挂载:
mkdir -p /sys/fs/cgroup/pids
mount -t cgroup -o pids none /sys/fs/cgroup/pids
然后创建一个名为parent的目录,也就是一个cgroup:
mkdir -p /sys/fs/cgroup/pids/parent
设置最大进程数为3:
echo 3 > /sys/fs/cgroup/pids/parent/pids.max
将当前的shell进程关联到cgroup:
echo $$ > /sys/fs/cgroup/pids/parent/cgroup.procs
(二):资源限制cgroup v1和cgroup v2的详细介绍
cgroups - Linux control groups
之前简单学习过cgroup,当时了解地太浅了,遇到问题的时候,还是无法下手,于是深入学习下。 这篇笔记中的内容主要来自Linux手册:man 7 cgroups,cgroups - Linux control groups。
术语
这篇笔记有可能是第一篇详细、全面介绍了cgroup v1和cgroup v2的中文资料,有必要约定术语、统一口径,可以减少交流障碍。
process是“进程”,task是“线程”。
subsystem或者resource controllers是cgroup中某一类资源的管理器,例如管理cpu的叫做cpu controller,管理内存的叫做memory controller,统一称呼为“cgroup控制器”。
controller要使用mount -t cgroup样式的命令挂载到一个目录中,这个操作称呼为“挂载cgroup controller”。
从linux kernel 4.14开始,cgroup v2 引入了thread mode(线程模式),controller被分为domain controller和threaded controller,前者称为“cgroup进程控制器”,后者称为“cgroup线程控制器”。
从使用的角度看,cgroup就是一个目录树,目录中可以创建子目录,这些目录称为“cgroup 目录”,在一些场景中为了体现层级关系,还会称为“cgroup 子目录”。
每个目录中有一些用来设置对应controller的文件,这些文件称呼为“cgroup控制器的文件接口”。
cgroup v2引入了thread mode(线程模式)之后,cgroup目录有了类型之分:只管理进程的cgroup目录是domain cgroup,称为“进程(子)目录”;新增的管理线程的cgroup目录是threaded cgroup,称为“线程子目录”。
一句话介绍cgroup:把一个cgroup目录中的资源划分给它的子目录,子目录可以把资源继续划分给它的子目录,为子目录分配的资源之和不能超过父目录,进程或者线程可以使用的资源受到它们委身的目录中的资源的限制。
版本
cgroup有v1和v2两个版本,这是一个非常重要的信息。
v1版本是最早的实现,当时resource controllers的开发各自为政,导致controller间存在不一致,并且controller的嵌套挂载使cgroup的管理非常复杂。
Linux kernel 3.10 开始提供v2版本cgroup(Linux Control Group v2)。开始是试验特性,隐藏在挂载参数-o __DEVEL__sane_behavior中,直到Linuxe Kernel 4.5.0的时候,cgroup v2才成为正式特性。
cgroup v2希望完全取代cgroup v1,但是为了兼容,cgroup v1没有被移除。
cgroup v2实现的controller是cgroup v1的子集,可以同时使用cgroup v1和cgroup v2,但一个controller不能既在cgroup v1中使用,又在cgroup v2中使用。
cgroups version 1
cgroup v1中,controller可以独立挂载到一个cgroup目录中,也可以和其它controller联合挂载到同一个cgroup目录,cgroup v2也是采用挂载的方式,但是有一些不同(见后文)。
在cgroup v1中,task也就是线程可以被划分到不同的cgroup组中,在一些场景中,这样做是有问题的。
例如在memory controller中,所有task使用的都是同样的内存地址空间,为它们设置不同memory cgroup是没有意义的。(cgroup v2最初将task功能去掉了,后来引入了thread mode来限制线程占用的资源)
cgroups v1:controller 挂载
controller以tmpfs文件系统的样式挂载到任意目录,通常将其挂载到/sys/fs/cgroup目录。linux系统通常已经将多个controller挂载在/sys/fs/cgroup目录中了,下面的例子用另一个目录演示。
将多个controller挂载到同一个目录,如下:
mkdir -p /tmp/cgroup/cpu,cpuacct
mount -t cgroup -o cpu,cpuacct none /tmp/cgroup/cpu,cpuacct
-t cgroup指定挂载类型,-o指定挂载的controller(可以有多个,用“,”间隔)。
单独挂载cpu时,提示“已经挂载或者/tmp/cgroup/cpu is busy,暂时不清楚是怎么回事,可能是有一些controller不允许重复挂载。
$ mkdir /tmp/cgroup/cpu
$ mount -t cgroup -o cpu none /tmp/cgroup/cpu
mount: none is already mounted or /tmp/cgroup/cpu busy
挂载后,可以在挂载目录中看到controller的文件接口:
$ ls -F /tmp/cgroup/cpu,cpuacct/
cgroup.clone_children cpuacct.stat cpu.cfs_quota_us cpu.stat kube-proxy/ tasks
cgroup.event_control cpuacct.usage cpu.rt_period_us docker/ notify_on_release user.slice/
cgroup.procs cpuacct.usage_percpu cpu.rt_runtime_us kubelet/ release_agent
cgroup.sane_behavior cpu.cfs_period_us cpu.shares kubepods/ system.slice/
可以一次挂载所有的controller:
mount -t cgroup -o all cgroup /tmp/cgroup
还可以不挂载任何controller:
mount -t cgroup -o none,name=somename none /some/mount/point
没有挂载controller的cgroup可以用来跟踪进程,例如在进程消失导致cgroup为空时,cgroup的通知回调会被触发。
cgroups v1:controller 卸载
直接用umount卸载:
umount /sys/fs/cgroup/pids
卸载的时候要注意,需要先将所有子目录卸载,否则,umount只会让挂载点不可见,而不是真正将其卸载。
cgroups v1:支持的 controller
这个是重点,使用cgroup主要就是和各种controller打交道:
cpu, CFS Scheduler、 CFS Bandwidth Control :
since 2.6.24,限制CPU份额,只会在cpu忙碌的时候限制cpu使用,如果cpu空闲不做限制。
since 3.2.0, 引入了`CON‐FIG_CFS_BANDWIDTH`编译选项,限制进程在每个调度周期占用的时间,无论CPU是否空闲。
cpuacct,Documentation/cgroup-v1/cpuacct.txt:
since 2.6.24,统计一组task的cpu使用情况。
cpuset,Documentation/cgroup-v1/cpusets.txt:
since 2.6.24,绑定到特定的cpu
memory,Documentation/cgroup-v1/memory.txt:
since 2.6.25,报告和限制内存使用情况
devices,Documentation/cgroup-v1/devices.txt:
since 2.6.26,限制设备文件的创建,和对设备文件的读写
freezer,Documentation/cgroup-v1/freezer-subsystem.txt:
since 2.6.28,暂停、重载指定cgroup目录中的、以及它的子目录中的task
net_cls,Documentation/cgroup-v1/net_cls.txt:
since 2.6.29,为该cgroup内的task产生的报文打上classid
blkio,Documentation/cgroup-v1/blkio-controller.txt:
since 2.6.33,限制和控制对块设备的读写
perf_event,tools/perf/Documentation/perf-record.txt:
since 2.6.39,允许perf观测cgroup中的task
net_prio,Documentation/cgroup-v1/net_prio.txt:
since 3.3,设置net优先级
hugetlb,Documentation/cgroup-v1/hugetlb.txt:
since 3.5,限制huge page的使用
pids,Documentation/cgroup-v1/pids.txt:
since 4.3,限制cgroup中可创建的进程数
rdma,Documentation/cgroup-v1/rdma.txt:
since 4.11,限制RDMA/IB 资源
cgroups v1:cgroup的创建和进程绑定
cgroup是以目录的形式呈现的,/是cgroup的根目录,注意cgroup的根目录和挂载目录不是一回事,cgroup可能挂载在/sys/fs/cgroup或者/tmp/cgroup等任意目录,无论挂载在哪里,cgroup的根目录都是“/”。
假设cgroup的cpu controller的挂载点是/sys/fs/cgroup/cpu,那么目录/sys/fs/cgroup/cpu/cg1对应的cgroup的目录是/cg1。
为什么要强调这个,因为在调查一个kubelet的问题的时候,不确定--runtime-cgroups参数值中要不要包含挂载点路径,直到用cadvisor查出所有cgroup后,才确定不应该带有挂载路径。现在从Linux手册中找到支持了:
A cgroup filesystem initially contains a single root cgroup, '/',which all processes belong to. A new cgroup is created by creating a directory in the cgroup filesystem:
mkdir /sys/fs/cgroup/cpu/cg1
将进程的进程号直接写入到对应cgroup.procs文件中,就完成了进程与cgroup的绑定,例如:
echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
将进程绑定到cgroup之后,该进程创建的线程也一同被绑定到同一个cgroup。
每个进程只能绑定每个controller的一个cgroup目录,把进程的ID加到controller中的另一个cgroup目录的cgroup.procs文件中时,会自动将其从原先的cgroup.procs文件中移除。
每次只能向cgroup.procs中添加一个进程号,不能批量添加。
从cgroup.procs中读取的进程号的顺序是随意的,并且是可以重复的。
cgroup v1支持将线程(task)绑定到cgroup目录,只需将线程的线程ID写入目标cgroup目录的tasks文件中。
注意,tasks文件中是线程ID,cgroup.procs文件中是进程ID。
cgroups v1:cgroup的删除
在cgroup子目录已经全部删除,并且没有绑定任何进程、线程的情况下,直接删除cgroup目录即可。
cgroups v1:cgroup为空时,进行回调通知
每个cgroup中目录中都有下面两个文件:
release_agent notify_on_release
notify_on_release的内容是0或者1,指示kernel是否要在cgroup为空时(没有cgroup子目录、没有绑定任何进程)发出通知,0为不通知,1为通知。
release_agent是自行设置的、kernel通知时调用的命令, 它的命令行参数有且只有一个,就是当前为空的cgroup目录。
release_agent也可以在挂载cgroup的时候设置:
mount -o release_agent=pathname ...
cgroups version 2
cgroups v2支持的所有controller使用统一约定的、固定格式的cgroup目录,并且进程只能绑定到cgroup的“/”目录和目录树中的叶子节点,不能绑定到中间目录。变化还是比较大的,需要特别注意:
cgorup v2支持的controller默认自动挂载,并且挂载点中的目录结构是固定的
进程只能绑定到cgroup的“/”目录和cgroup目录树中的叶子节点
在cgroup.controllers和cgroup.subtree_control中设置cgroup目录中可用的controller
cgroup v1中的tasks文件被移除,cpuset controller中的cgroup.clone_children文件也被移除
cgroup目录为空时的通知机制得到改进,通过cgroup.events文件通知
内核文档Documentation/cgroup-v2.txt中有更详细的描述。
cgroups v2:controller 挂载
类型为cgroup2:
mount -t cgroup2 none /mnt/cgroup2
一个controller如果已经在cgroup v1中挂载了,那么在cgroup v2中就不可用。如果要在cgroup v2中使用,需要先将其从cgroup v1中卸载。
systemd用到了cgroup v1,会在系统启动时自动挂载controller,因此要在cgroup v2中使用的controller,最好通过内核启动参数cgroup_no_v1=list禁止cgroup v1使用:
cgroup_no_v1=list # list是用逗号间隔的多个controller
cgroup_no_v1=all # all 将所有的controller设置为对cgroup v1不可用
cgroups v2:支持的controller
从Linux kernel 4.14开始,cgroup v2支持线程模式(thread mode),引入该特性后,controller开始分为两类:domain controller(以进程为管理目标的控制器)和 threaded controller(以线程为管理目标的控制器)。
domain controller
io
since 4.5,cgroup v1 的 blkio 的继任者
memory
since 4.5,cgroup v1 的 memory 的继任者
pids
since 4.5,与 cgroup v1 中的 pids 是同一个
perf_event
since 4.5,与 cgroup v1 中的 perf_event 是同一个
rdma
since 4.11,与 cgroup v1 中的 rdma 是同一个
cpu
since 4.15,cgroup v1 的 cpu、cpuacct 的继任者
threaded controller
cpu
perf_event
pids
cgroups v2:cgroup目录
controller的启用/禁止文件
cgroup v2的cgroup目录中有一个cgroup.controllers文件和cgroup.subtree_control文件。
cgroup.controllers文件是只读的,内容是当前目录可用的controller。
cgroup.subtree_control是当前目录中可用的controller的子集,规定了直接子目录可用的controller,即定义了直接子目录中的cgroup.controllers文件的内容。
cgroup.subtree_control的内容格式如下,列出的controller用空格间隔,前面用“+”表示启用,用“-”表示不启用:
echo '+pids -memory' > x/y/cgroup.subtree_control
如果向cgroup.subtree_control中写入cgroup.controllers中不存在的controller,会得到ENOENT错误。
将进程绑定到叶子目录
在cgroup v2中,所有进程都只能绑定到cgroup的叶子目录中。假设cgroup的目录如下,这时候只能在CG1.1、CG1.2和CG2.1中绑定进程:
/
__ __
___/ \___
__/ \__
CG1 CG2
__ __ __
___/ \___ \_
__/ \__ \_
CG1.1 CG1.2 CG2.1
man 7 cgroups建议在每个目录下都建立一个名为leaf的目录,这个目录没有任何子目录,专门用来绑定进程。
根据这个建议,上面的目录应该调整成:
/
_______
_____/ \_____
___/ \___
CG1 CG2
__|__ ____
___/ | \___ ___/ \___
__/ | \__ __/ \__
CG1.1 leaf CG1.2 CG2.1 leaf
| | |
leaf leaf leaf
需要绑定cgroup进程的进程号都写在名为leaf的目录中。
进程只能绑定到目录树的叶子结点只是一个表面规则,本质规则是:
一个cgroup目录如果绑定了进程,那么它的cgroup.subtree_control必须为空,或者反过来必须不绑定进程,cgroup.subtree_control中才可以有内容。
More precisely, the rule is that a (nonroot) cgroup can't both (1) have member processes, and (2) distribute resources into child cgroups—that is, have a nonempty cgroup.subtree_control file.
回调通知设置文件
cgroup v1 中的release_agent和notify_on_release被移除了,cgroup v2 提供一个更通用的cgroup.events。
cgroup.events文件是只读的,每行一个key-value对,key和value之间用空格间隔。
现在(2019-01-29 17:39:09),cgroup.events文件中只存在一个名为populated的key,这个key对应的value是这个cgroup中包含的进程数。
如果使用cgroup v2,要用监控cgroup.events文件的方式感知cgroup的变化。
When monitoring this file using inotify(7), transitions generate IN_MODIFY events When monitoring the file using poll(2), transitions cause the bits POLLPRI and POLLERR to be returned in the revents field.
cgroup目录中的其它文件
前面已经接触到文件有:
cgroup.controllers
只读,当前目录中可用的controller
cgroup.subtree_control
读写,子目录中可用的controller
cgroup.events
只读,事件文件,每行一对key value,现在只有一个key
populated:value是cgroup中包含的进程数
除此之外还有:
cgroup.stat
只读,since 4.14,每行一对key value
nr_descendants:该cgroup中存活的子cgroup的数量
nr_dying_descendants:该cgroup中已经死亡的cgroup的数量
一个cgroup目录被删除后,先进入dying状态,等待系统回收。
cgroup.max.depth
since 4.14,当前目录的子目录的最大深度,0表示不能创建子目录,默认值"max"表示不限制,超过限制,返回EAGAIN
cgroup.max.descendants
since 4.14,当前目录中可以创建的活跃cgroup目录的最大数量,默认值"max"表示不限制,超过限制,返回EAGAIN
cgroup.type
since 4.14,位于"/"以外的cgroup目录,标记当前cgroup目录的类型,支持修改,支持以下类型:
domain:进程子目录,控制进程资源的cgroup
threaded:线程子目录,控制同一个进程下的线程资源
domain threaded:线程子目录的根目录,threaded root
domain invalid:线程子目录中处于无效状态的cgroup目录
domain invalid是一个中间状态,对类型为domain invalid的cgroup目录,只能做一个操作:将其类型修改threaded。 它存在的目的是为线程模式的扩展预留空间。
cgroups v2:授予非特权用户管理权限
授权方法:修改文件或目录的所有者
特权用户(root用户)可以授予非特权用户(普通用户)管理某个cgroup的权利。
授予方法很简单,直接将一些文件或目录的所有者改成被授权人就可以了,修改的文件或目录不同,被授权人拥有的权限不同。
按照文档中中例子,将cgroup目录/dlgt_grp的管理权授予普通用户时,可以有以下几种授权方式:
/dlgt_grp目录的所有者改为被授权人,被授权人拥有该目录下所有新建cgroup目录的管理权。
/dlgt_grp/cgroup.procs文件的所有者改为被授权人,被授权人拥有将进程绑定到该cgroup的权利。
/dlgt_grp/cgroup.subtree_control文件的所有者改为被授权人,被授权人可以决定在子目录中启用哪些出现在/dlgt_grp/cgroup.controllers中controller。
/dlgt_grp/cgroup.threads文件的所有者改为被授权人,线程模式下用到,授予将线程绑定到线程子目录的权利。
需要注意的是/dlgt_grp目录中controller的接口文件的所有者不应当被修改,这是上层cgroup目录授予当前目录的。
授权边界:用挂载参数nsdelegate开启
在挂载cgroup v2的时候,使用参数nsdelegate开启授权边界,如果cgroup v2已经挂载,可以remount开启,如下:
mount -t cgroup2 -o remount,nsdelegate none /sys/fs/cgroup/unified
使用该选项之后,cgroup namespace成为授权边界,会对cgroup中的进程进行下面的限制:
1. cgroup中的进程不能对所属cgroup目录中的controller接口文件进行写操作,否则报错`EPERM`
2. cgroup中的进程不能设置namespace边界外的cgroup中的绑定进程,否则报错`ENOENT`
cgroup namespace是什么?@2019-01-30 14:28:27
引入cgroup namespace作为授权边界是为了应对下面的情况:
1. 用户用cecilia得到了一个cgroup目录的管理权限,这个目录这里称为当前目录
2. 普通用户cecilia,在当前目录下创建了一个cgroup子目录,并将这个子目录授权给另一个人
这种情况下,cecilia同时拥有当前目录和子目录的操作权限,子目录中的进程也是cecilia用户的进程,会出现下面两种非法的操作:
1. 绑定到cecilia的cgroup子目录中的进程能够更改所属cgroup子目录中的controller接口文件
2. 绑定到cecilia的cgroup子目录中的进程能够将进程移出所在cgroup子目录,绑定到cecilia的当前cgroup目录
文档中举的例子是,cecilia创建一个容器,容器内的进程绑定到cecilia创建的cgroup子目录。意思似乎是在说:如果不以namespace为授权边界,那么容器内的进程就能够自己修改controller接口,并且具备脱离所属的cgroup目录、将自己绑定到上层cgroup目录的能力。
这样一来cecilia对容器内进程所做的cgroup限制就形同虚设。这样理解正好和授权边界引入的两个限制对应(或许正确 @2019-01-30 14:36:38)。
nsdelegate只在最初挂载的mount namespace中有效,在其它mount namespace中默认忽略:
The nsdelegate mount option only has an effect when performed in the initial mount namespace; in other mount namespaces, the option is silently ignored.
一个非特权进程只有下面的情况都满足时,才能向cgroup.procs中添加进程:
1. 拥有cgroup.procs文件的写权限
2. 拥有进程原先所属cgroup目录和将要加入的cgroup目录的共同上级目录中cgroup.procs文件的写权限
3. 如果启用了授权边界(nsdelegate),执行操作的进程必须能够看到被操作的进程原先所属cgroup目录和将要加入的cgroup目录
4. Linux kernel 4.11之前,执行操作的进程的UID和被操作进程的User ID或者Set-User-ID匹配
以上四条约束带来的直接结果是:被授权给非特权用户的cgroup绑定的第一个进程,只能是授权者设置的,不能由被授权者添加。因为被授权者不能对cgroup子目录中的第一个进程原先所在的cgroup中的cgroup.procs文件进行写操作。
cgroups v2:线程模式(THREAD MODE)
Linux kernerl 4.14开始,cgroup v2支持线程模式。
线程模式是指:
可以创建线程子目录(threaded subtrees),容纳绑定到该线程子目录的进程创建的所有线程,每个线程可以绑定线程子目录的cgroup子目录。
通过线程控制器(threaded controllers)为线程子目录中的cgroup目录分配资源。
在线程子目录中,非叶子目录可以绑定线程,注意,“只能绑定到叶子目录”的限制在这里放松了。
cgroups v2:线程controller
cpu
perf_event
pids
在线程子目录中不能使用domain controllers。
cgroups v2:线程模式的使用
创建线程子目录的方法
方法1
直接修改cgroup目录中的cgroup.type,写入threaded
上层cgroup目录(/目录除外)的类型自动变为domain threaded
当前目录的子目录和新建子目录的类型自动变成domain invalid,需要自行将其修改为threaded
方法2
在当前cgroup目录中启用一个或多个threaded controller,在当前目录中绑定一个进程
当前目录的类型自动变成domain threaded
当前目录的所有非threaded类型的子目录类型自动变成domain invalid,需要自行将其修改为threaded
注意如果上层目录的类型是domain invalid,当前目录的类型不能被修改。此外,将当前目录的类型修改为threaded时,需要满足两个条件:
当前目录的子目录都是空的,没有绑定任何进程
当前目录中的cgroup.subtree_control文件中没有启用domain controller配置,否则报错ENOTSUP。
即domain controllers不能在线程子目录中使用。
绑定到线程子目录
将进程ID直接写入线程子目录的cgroup.procs文件中,该进程以及它的线程就都一同绑定到了线程子目录中。然后可以将这个进程创建的多个线程ID,写入到它绑定的线程子目录的以及子目录的子目录中的cgroup.threads文件,实现线程的绑定。
线程只能绑定到创建它的进程所在的线程子目录树。
domain controller是不关心线程的,它看到的都是进程。
实行线程绑定操作时,需要满足以下条件:
执行绑定操作的进程有目标目录中的cgroup.threads文件的写权限
执行绑定操作的进程需要有源目录和目标目录的共同祖先目录中的cgroup.procs文件的写权限
源目录和目标目录必须在同一个线程子目录中,即创建线程的进程所在的线程子目录中,即线程不能与进程分离,否则报错EOPNOTSUPP。
进程子目录中的根目录中cgroup.procs是可读写的,进程子目录中非根目录中的cgroup.procs文件是不可读写的。
cgroup.threads在每个cgroup目录中都存在,包括类型为domain的cgroup目录。
cgroup的内核接口文件
/proc/cgroups(since 2.6.24)列出了内核支持的cgroup contoller,以及它们的使用情况:
#subsys_name hierarchy num_cgroups enabled
cpuset 4 1 1
cpu 8 1 1
cpuacct 8 1 1
blkio 6 1 1
memory 3 1 1
devices 10 84 1
freezer 7 1 1
net_cls 9 1 1
perf_event 5 1 1
net_prio 9 1 1
hugetlb 0 1 0
pids 2 1 1
四列数据的含义分别是:controller名称、挂载位置ID(在cgroup v2中都为0)、使用该controller的cgroup数量、是否启用。
/proc/[pid]/cgroup(since 2.6.24)进程所属的cgroup,内容格式如下:
hierarchy-ID:controller-list:cgroup-path
例如一个容器内进程的cgroup:
# cat /proc/1427/cgroup
11:blkio:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
10:freezer:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
9:net_prio,net_cls:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
8:devices:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
7:cpuset:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
6:memory:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
5:perf_event:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
4:pids:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
3:cpuacct,cpu:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
2:hugetlb:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
1:name=systemd:/kubepods/besteffort/pode4f8b737-1ad1-11e9-a3a2-5254003b4ace/3647f5a8960c6c003fda011ea26dedcb8d45ce3b4c03cbec31b4f2c9cab7d221
第一列hierarchy-ID与/proc/cgroups的第二列对应,在cgroups v2中hierarchy-ID都为0;第二列是controller名称;第三列是cgroup目录路径。
/sys/kernel/cgroup/delegate(since 4.15):cgroups v2 中可以授权给其它用户的cgroup文件。
/sys/kernel/cgroup/features(since 4.15):cgroups v2 支持的功能特性。
手上没有kernel 4.15的环境,下面是文档中的例子:
$ cat /sys/kernel/cgroup/delegate
cgroup.procs
cgroup.subtree_control
cgroup.threads
$ cat /sys/kernel/cgroup/features
nsdelegate
(三):cgroup controller汇总和控制器的参数(文件接口)
这里将罗列cgroup支持的controllers,每个controller的文件接口或者说是配置参数,以及它们的含义。这是一项持续性工作,这篇笔记不会一次性完成,而是逐渐补充丰富:@2019-02-18 15:55:04
又找到一些关于cgroup的好资料,感谢google:
Managing system resources on Red Hat Enterprise Linux 7
Namespace and cgroups, the Building Blocks of Linux containers
这两篇文档都对cgroup进行了全景式介绍,第一篇侧重cgroup在Red Hat中的应用,从Red Hat用户的角度讲述,第二篇侧重cgroup的内核实现,从内核开发者的角度讲述。
cgroup controller列表
Linux kernel 4.4 支持的cgroup controller,cgroup v1,总计12个controller @2019-02-18 16:25:41:
Name Kernel Object name Module
-------------------------------------------------------------------------------
blkio io_cgrp_subsys block/blk-cgroup.c
cpu cpu_cgrp_subsys kernel/sched/core.c
cpuacct cpuacct_cgrp_subsys kernel/sched/cpuacct.c
cpuset cpuset_cgrp_subsys kernel/cpuset.c
devices devices_cgrp_subsys security/device_cgroup.c
freezer freezer_cgrp_subsys kernel/cgroup_freezer.c
memory memory_cgrp_subsys mm/memcontrol.c
net_cls net_cls_cgrp_subsys net/core/netclassid_cgroup.c
perf_event perf_event_cgrp_subsys kernel/events/core.c
hugetlb hugetlb_cgrp_subsys mm/hugetlb_cgroup.c
pids pids_cgrp_subsys kernel/cgroup_pids.c
net_prio net_prio_cgrp_subsys net/core/netprio_cgroup.c
Red Hat Enterprise Linux 7中可用cgroup controllers,总计10个 @2019-02-18 15:50:06:
blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、perf_event、hugetlb、
Kernel 4.4支持、Red Hat Enterprise Linux 7还没有支持的cgroup controllers,总计2个 @2019-02-18 15:50:16:
pids、net_prio
cgroup v2 支持的controller,总计6个(2019-02-18 15:49:51):
cpu、memory、io、pid、rdma、perf_event
cgroup v1 支持的controller
blkio
sets limits on input/output access to and from block devices;
内核文档:Block IO Controller
cpu
uses the CPU scheduler to provide cgroup tasks access to the CPU. It is mounted together with the cpuacct controller on the same mount;
内核文档:Real-Time group scheduling、CFS Bandwidth Control。
cpu:实时任务调度相关参数
cpu.rt_period_us和cpu.rt_runtime_us是Real-Time group调度的参数。
cpu.rt_period_us:含义与sched_rt_period_us相同。
cpu.rt_runtime_us:含义与sched_rt_runtime_us相同。
Real-Time group是一些需要间歇性实时运行的任务(后面简称实时任务),这些任务不是一直运行的,而是周期性运行,但是运行周期到来以后,它们必须在运行,不能因为其它进程正在使用CPU而被延迟运行。
有两个内核参数用来调节实时任务占用的CPU资源:
/proc/sys/kernel/sched_rt_period_us:定义100% CPU对应的时间,可以理解为计算CPU使用率时使用的基准时间段,单位是微秒,默认是1000000(1s)。
3.12. Real Time Throttling中对这个参数介绍是最可理解的,并且能和sched_rt_runtime_us互相印证的,原句是Defines the period in μs (microseconds) to be considered as 100% of CPU bandwidth.,重点是to be considered,一些中文翻译感觉翻译的走样了。
/proc/sys/kernel/sched_rt_runtime_us:实时任务最高占据的CPU带宽,单位是微秒,默认值950000(0.95s)表示95%的CPU(注意的sched_rt_period_us的数值1s对上了)。如果值为-1,实时任务将占用100%的CPU,会存在非实时任务完全得不到执行的风险,设置时需要高度谨慎。
内核文档Real-Time group scheduling中举了两个例子,让人不太理解:
第一个例子是每秒钟要渲染25帧画面的任务,每一帧的渲染周期是0.04秒,如果分配给它80%的CPU时间,运行时间是0.04 * 0.8=0.32s,这个让人有点不理解。
假设把80%的cpu分配给该实时任务,那么为了保证每秒依旧产出25帧,0.04 * 0.8 = 0.32的意思说应该是,渲染周期需要被压缩到0.32s,但是kernerl中说这个时间是运行时间。
This way the graphics group will have a 0.04s period with a 0.032s run time limit”
揣测一下,原意应该是说:每间隔0.04秒渲染一帧,这0.04s中只有80%的时间用于渲染帧,所以实时任务的运行时间是0.32s。
这样理解是否正确只能通过扒内核源码验证了,现在无力验证..丧…(2019-02-19 11:14:08),第二个例子类似,这里不罗列了。
注意sched_rt_runtime_us是实时任务的保证时间和最高占用时间,如果实时任务没有使用,可以分配给非实时任务,并且实时任务最终占用的时间不能超过这个数值,参考Linux-85 关于sched_rt_runtime_us 和 sched_rt_period_us。
对cpu.rt_period_us参数的限制是必须小于父目录中的同名参数值。
对cpu.rt_runtime_us的限制是:
\Sum_{i} runtime_{i} / global_period <= global_runtime / global_period
即:
\Sum_{i} runtime_{i} <= global_runtime
当前的实时进程调度算法可能导致部分实时进程被饿死,如下A和B是并列的,A的运行时时长正好覆盖了B的运行时间:
* group A: period=100000us, runtime=50000us
- this runs for 0.05s once every 0.1s
* group B: period= 50000us, runtime=25000us
- this runs for 0.025s twice every 0.1s (or once every 0.05 sec).
Real-Time group scheduling中提出正在开发SCHED_EDF (Earliest Deadline First scheduling),优先调度最先结束的实时进程。
cpu:CPU带宽控制CFS的相关参数
CFS Bandwidth Control控制一组进程可以使用的cpu时间,它针对的非实时的进程,约定了一组进程在一个时间周期中可以使用的CPU的时长,如果使用时间超了,该组进程会被限制运行,直到进入下一个周期,重新获得CPU时间。
cpu.cfs_period_us:一个周期的时长,单位微秒,默认值100毫秒,最大值1s,最小1ms。
cpu.cfs_quota_us:在一个周期内的可以使用的cpu时间,单位微秒,最小1ms,默认值-1表示CPU使用没有限制。
cpu.stat:限流统计,包含三个值:
- nr_periods: Number of enforcement intervals that have elapsed.
- nr_throttled: Number of times the group has been throttled/limited.
- throttled_time: The total time duration (in nanoseconds) for which entities of the group have been throttled.
内核参数/proc/sys/kernel/sched_cfs_bandwidth_slice_us(默认值5秒)的用途没明白,摘录原文:
For efficiency run-time is transferred between the global pool and CPU local "silos" in a batch fashion. This greatly reduces global accounting pressure on large systems. The amount transferred each time such an update is required is described as the "slice".
如果父目录中分配的CPU时间用尽了,子目录即使还有CPU运行时间,也会被限制。
以250ms为周期,占用1个CPU:
# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
# echo 250000 > cpu.cfs_period_us /* period = 250ms */
以500ms为周期,占用2个CPU,quota > period:
# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
# echo 500000 > cpu.cfs_period_us /* period = 500ms */
以50ms为周期,占用20%CPU:
# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
# echo 50000 > cpu.cfs_period_us /* period = 50ms */
cpuacct
creates automatic reports on CPU resources used by tasks in a cgroup. It is mounted together with the cpu controller on the same mount;
内核文档:CPU Accounting Controller
cpuacct.usage:该cgroup实际获得的cpu时间,单位是纳秒,/sys/fs/cgroup/cpuacct/cpuacct.usage是系统上所有进程获得的cpu时间。
cpuacct.stat:将该cgroup获得的时间按照user time和system time分开呈现,单位是USER_HZ:
user 24
system 9
cpuacct.usage_percpu:该cgroup在每个cpu上占用的时间,单位是纳秒,累加值等于 cpuacct.usage:
//系统上一共有4个CPU,为容器指定cpuset 0,1,--cpuset-cpus "1,2"
325517080 54277298 0 0
//系统上一共有4个CPU,为容器指定cpuset 1,2,--cpuset-cpus "1,2"
0 21977223 28985453 0
cpuset
assigns individual CPUs (on a multicore system) and memory nodes to tasks in a cgroup;
内核文档:CPUSETS
devices
allows or denies access to devices for tasks in a cgroup;
内核文档:Device Whitelist Controller
freezer
suspends or resumes tasks in a cgroup;
内核文档:freezer-subsystem
memory
sets limits on memory use by tasks in a cgroup and generates automatic reports on memory resources used by those tasks;
内核文档:Memory Resource Controller、memcg_test.txt
net_cls
tags network packets with a class identifier (classid) that allows the Linux traffic controller (the tc command) to identify packets originating from a particular cgroup task. A subsystem of net_cls, the net_filter (iptables) can also use this tag to perform actions on such packets. The net_filter tags network sockets with a firewall identifier (fwid) that allows the Linux firewall (the iptables command) to identify packets (skb->sk) originating from a particular cgroup task;
内核文档:Network classifier cgroup
perf_event
enables monitoring cgroups with the perf tool;
hugetlb
allows to use virtual memory pages of large sizes and to enforce resource limits on these pages.
pids
内核文档:Process Number Controller
net_prio
内核文档:Network priority cgroup
rdma
内核文档:RDMA Controller
cgroup v2 支持的controller
内核文档:Control Group v2。
cpu
memory
io
pid
rdma
perf_event
参考链接
Control Group v1
Control Group v2
Linux control groups
CFS Bandwidth Control
3.12. Real Time Throttling
Real-Time group scheduling
Linux资源管理之cgroups简介
Managing system resources on Red Hat Enterprise Linux 7
Namespace and cgroups, the Building Blocks of Linux containers