ToB企服应用市场:ToB评测及商务社交产业平台

标题: 初探 Linux Cgroups:资源控制的奇妙世界 [打印本页]

作者: 罪恶克星    时间: 2024-3-26 21:16
标题: 初探 Linux Cgroups:资源控制的奇妙世界
Cgroups 是 linux 内核提供的功能,由于牵涉的概念比较多,所以不太容易理解。本文试图通过简单的描述和 Demo 帮助大家理解 Cgroups 。
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅

1. 什么是 Cgroups

Cgroups 是 Linux 下的一种将进程按组进行管理的机制,它提供了对一组进程及将来子进程的资源限制控制和统计的能力
这些资源包括 CPU、内存、存储、网络等。通过 Cgroups 可以方便地限制某个进程的资源占用,并且可以实时地监控进程的监控与统计信息
Cgroups 分 v1v2 两个版本:
v1 和 v2 可以混合使用,但是这样会更复杂,所以一般没人会这样用。
1. 三部分组件

Cgroups 主要包括下面几部分:

3 个部分间的关系
个人理解:
注:后续的 cgroup 树就指的是 hierarchy,cgroup 则指 hierarchy 上的节点。
2. 具体架构

看完上面的描述,可能还是搞不清具体的关系,下面几幅图比较清晰的展示了 cgroup 中几部分组件的关系。
这部分内容参考:美团技术团队
hierarchy、cgroup、subsystem 3 者的关系:

比如上图表示两个 hierarchiy,每一个 hierarchiy 中是一颗树形结构,树的每一个节点是一个 cgroup (比如 cpu_cgrp, memory_cgrp)。
在每一个 hierarchiy 中,每一个节点(cgroup)可以设置对资源不同的限制权重(即自定义配置)。比如上图中 cgrp1 组中的进程可以使用 60%的 cpu 时间片,而 cgrp2 组中的进程可以使用 20%的 cpu 时间片。
cgroups 和 进程间的关系:
上面这个图从整体结构上描述了进程与 cgroups 之间的关系。最下面的P代表一个进程。
一个节点的控制列表中的所有进程都会受到当前节点的资源限制。同时某一个进程也可以被加入到不同的 cgroups 层级结构的节点中,因为不同的 cgroups 层级结构可以负责不同的系统资源。所以说进程和 cgroup 结构体是一个多对多的关系。
2. 如何使用 Cgroups

注:本文所有操作在 Ubuntu20.04 下进行。
cgroup 相关的所有操作都是基于内核中的 cgroup virtual filesystem,使用 cgroup 很简单,挂载这个文件系统就可以了。
一般情况下都是挂载到/sys/fs/cgroup 目录下,当然挂载到其它任何目录都没关系。
cgroups 以文件的方式提供应用接口,我们可以通过 mount 命令来查看 cgroups 默认的挂载点:
  1. [root@iZ2zefmrr626i66omb40ryZ ~]# mount | grep cgroup
  2. tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
  3. cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
  4. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
  5. cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
  6. cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
  7. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
  8. cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
  9. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  10. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
  11. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
  12. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
  13. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
  14. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
复制代码
需要注意的是,在使用 systemd 系统的操作系统中,/sys/fs/cgroup 目录都是由 systemd 在系统启动的过程中挂载的,并且挂载为只读的类型。换句话说,系统是不建议我们在 /sys/fs/cgroup 目录下创建新的目录并挂载其它子系统的。这一点与之前的操作系统不太一样。
查看 subsystem 列表

可以通过查看/proc/cgroups(since Linux 2.6.24)知道当前系统支持哪些 subsystem,下面是一个例子:
  1. DESKTOP-9K4GB6E# cat /proc/cgroups
  2. #subsys_name    hierarchy       num_cgroups     enabled
  3. cpuset          11              1               1
  4. cpu             3               64              1
  5. cpuacct         3               64              1
  6. blkio           8               64              1
  7. memory          9               104             1
  8. devices         5               64              1
  9. freezer         10              4               1
  10. net_cls         6               1               1
  11. perf_event      7               1               1
  12. net_prio        6               1               1
  13. hugetlb         4               1               1
  14. pids            2               68              1
复制代码
从左到右,字段的含义分别是:
hierarchy 相关操作

挂载
Linux 中,用户可以使用 mount 命令挂载 cgroups 文件系统:
语法为: mount -t cgroup -o subsystems name /cgroup/name
这条命令同在内核中创建了一个 hierarchy 以及一个默认的 root cgroup。
示例:
挂载一个和 cpuset subsystem 关联的 hierarchy 到 ./cg1 目录
  1. # 首先肯定是创建对应目录
  2. mkdir cg1
  3. # 具体挂载操作--参数含义如下
  4. # -t cgroup 表示操作的是 cgroup 类型,
  5. # -o cpuset 表示要关联 cpuset subsystem,可以写0个或多个,0个则是关联全部subsystem,
  6. # cg1 为 cgroup 的名字,
  7. # ./cg1 为挂载目标目录。
  8. mount -t cgroup -o cpuset cg1 ./cg1
复制代码
  1. # 挂载一颗和所有subsystem关联的cgroup树到cg1目录
  2. mkdir cg1
  3. mount -t cgroup cg1 ./cg1
  4. #挂载一颗与cpu和cpuacct subsystem关联的cgroup树到 cg1 目录
  5. mkdir cg1
  6. mount -t cgroup -o cpu,cpuacct cg1 ./cg1
  7. # 挂载一棵cgroup树,但不关联任何subsystem,这systemd所用到的方式
  8. mkdir cg1
  9. mount -t cgroup -o none,name=cg1 cg1 ./cg1
复制代码
卸载
作为文件系统,同样是使用umount 命令卸载。
  1. # 指定路径来卸载,而不是名字。
  2. $ umount /path/to/your/hierarchy
复制代码
例如
  1. umount /sys/fs/cgroup/hierarchy
复制代码
cgroup 相关操作

创建 cgroup 比较简单,直接在 hierarchy 或 cgroup 目录下创建子目录(mkdir)即可。
删除则是删除对应目录(rmdir)。
注:不能直接递归删除对应目录,因为目录中的文件是虚拟的,递归删除时会报错。
也可以借助 libcgroup 工具来创建或删除。
使用 libcgroup 工具前,请先安装 libcgroup 和 libcgroup-tools 数据包
redhat 系统安装:
  1. $ yum install libcgroup
  2. $ yum install libcgroup-tools
复制代码
ubuntu 系统安装:
  1. $ apt-get install cgroup-bin
  2. # 如果提示cgroup-bin找不到,可以用 cgroup-tools 替换
  3. $ apt-get install cgroup-tools
复制代码
具体语法:
  1. # controllers就是subsystem
  2. # path可以用相对路径或者绝对路径
  3. $ cgdelete controllers:path
复制代码
例如:
  1. cgdelete cpu:./mycgroup
复制代码
3. 演示

分别演示以下直接在某个已存在的 hierarchy 下创建子 cgroup 或者直接创建一个新的 hierarchy 两种方式。
1. 新 hierarchy 方式

创建 hierarchy
首先,要创建并挂载一个 hierarchy。
  1. # 创建一个目录作为挂载点
  2. lixd  ~ $ mkdir cgroup-test
  3. # 创建一个不挂载任何subsystem的hierarchy,由于 name=cgroup-test 的 cgroup 不存在,所以这里会由hierarchy默认创建出来
  4. ✘ lixd  ~ $ sudo mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test
  5. lixd  ~ $ cd cgroup-test
  6. lixd  ~/cgroup-test $ ls
  7. # 可以发现多了几个文件
  8. cgroup.clone_children  cgroup.procs  cgroup.sane_behavior  notify_on_release  release_agent  tasks
复制代码
这些文件就是 hierarchy 中 cgroup 根节点的配置项。具体含义如下:
创建子 cgroup
然后,从刚创建好的 hierarchy 上 cgroup 根节点中扩展出两个子 cgroup:
  1. # 创建子cgroup cgroup-1
  2. lixd  ~/cgroup-test $ sudo mkdir cgroup-1
  3.   # 创建子cgroup cgroup-1
  4. lixd  ~/cgroup-test $ sudo mkdir cgroup-2
  5. lixd  ~/cgroup-test $ tree
  6. .
  7. ├── cgroup-1
  8. │   ├── cgroup.clone_children
  9. │   ├── cgroup.procs
  10. │   ├── notify_on_release
  11. │   └── tasks
  12. ├── cgroup-2
  13. │   ├── cgroup.clone_children
  14. │   ├── cgroup.procs
  15. │   ├── notify_on_release
  16. │   └── tasks
  17. ├── cgroup.clone_children
  18. ├── cgroup.procs
  19. ├── cgroup.sane_behavior
  20. ├── notify_on_release
  21. ├── release_agent
  22. └── tasks
复制代码
可以看到,在一个 cgroup 的目录下创建文件夹时,Kernel 会把文件夹标记为这个 cgroup 的子 cgroup,它们会继承父 cgroup 的属性。
在 cgroup 中添加和移动进程
一个进程在一个 Cgroups 的 hierarchy 中,只能在一个 cgroup 节点上存在,系统的所有进程都会默认在根节点上存在。
想要将进程移动到其他 cgroup 节点,只需要将进程 ID 写到目标 cgroup 节点的 tasks 文件中即可。
将当前 shell 所在进程添加到 tasks:
  1. cgroup-test#cd cgroup-1
  2. # 需要 root 权限
  3. cgroup-1# echo $$ >> tasks
  4. cgroup-1# cat tasks
  5. 7575
  6. cgroup-1# cat /proc/7575/cgroup
  7. 14:name=cgroup-test:/cgroup-1 # 可以看到该进程已经被加入到cgroup中了
  8. 13:rdma:/
  9. 12:pids:/
  10. 11:hugetlb:/
  11. 10:net_prio:/
  12. 9:perf_event:/
  13. 8:net_cls:/
  14. 7:freezer:/
  15. 6:devices:/
  16. 5:blkio:/a
  17. 4:cpuacct:/
  18. 3:cpu:/
  19. 2:cpuset:/
  20. 1:memory:/
  21. 0::/
复制代码
通过 subsystem 限制 cgroup 中的进程
在上面创建 hierarchy 的时候,这个 hierarchy 并没有关联到任何的 subsystem ,所以没办法通过那个 hierarchy 中的 cgroup 节点限制进程的资源占用。
即 只能在创建 hierarchy 时指定要关联哪些 subsystem,创建后就无法修改。
其实系统默认已经为每个 subsystem 创建了一个默认的 hierarchy,比如 memory 的 hierarchy。
2. 子 cgroup 方式

在很多使用 systemd 的系统中,systemd 已经帮我们将各个 subsystem 和 cgroup 树关联并挂载好了:
  1. DESKTOP-9K4GB6E# mount |grep cgroup
  2. tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
  3. cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
  4. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
  5. cgroup on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
  6. cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct)
  7. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
  8. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  9. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
  10. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
  11. cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
  12. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
  13. cgroup on /sys/fs/cgroup/net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio)
  14. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
  15. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
  16. cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
  17. cgroup-test on /home/lixd/cgroup-test type cgroup (rw,relatime,name=cgroup-test)
复制代码
因此我们可以直接在对应 cgroup 树下创建子 cgroup 即可。
直接进到 /sys/fs/cgroup/cpu 目录创建 cgroup-cpu 子目录即可:
  1. DESKTOP-9K4GB6E# cd /sys/fs/cgroup/cpu
  2. DESKTOP-9K4GB6E# mkdir cgroup-cpu
  3. DESKTOP-9K4GB6E# cd cgroup-cpu
  4. DESKTOP-9K4GB6E# ls
  5. cgroup.clone_children  cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
  6. cgroup.procs           cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
复制代码
简单跑个程序测试一下,执行下面这条命令
  1. DESKTOP-9K4GB6E# while : ; do : ; done &
  2. [1] 12887
复制代码
显然,它执行了一个死循环,可以把计算机的 CPU 吃到 100%,根据它的输出,我们可以看到这个脚本在后台运行的进程号(PID)是 12887。
查看一下 CPU 占用:
  1.   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  2. 12887 root      25   5   14912   1912      0 R 100.0   0.0   0:33.31 zsh
复制代码
果然这个 PID=12887 的进程占用了差不多 100% 的 CPU。
结下来我们就通过 Cgroups 对其进行限制,这里就用前面创建的 cgroup-cpu 控制组。
我们可以通过查看 container 目录下的文件,看到 container 控制组里的 CPU quota 还没有任何限制(即:-1),CPU period 则是默认的 100 ms(100000 us):
  1. DESKTOP-9K4GB6E# cat /sys/fs/cgroup/cpu/cgroup-cpu/cpu.cfs_quota_us
  2. -1
  3. DESKTOP-9K4GB6E# cat /sys/fs/cgroup/cpu/cgroup-cpu/cpu.cfs_period_us
  4. 100000
复制代码
接下来,我们可以通过修改这些文件的内容来设置限制。比如,向 container 组里的 cfs_quota 文件写入 20 ms(20000 us):
  1. $ echo 20000 > /sys/fs/cgroup/cpu/cgroup-cpu/cpu.cfs_quota_us
复制代码
这样意味着在每 100 ms 的时间里,被该控制组限制的进程只能使用 20 ms 的 CPU 时间,也就是说这个进程只能使用到 20% 的 CPU 带宽。
接下来,我们把被限制的进程的 PID 写入 container 组里的 tasks 文件,上面的设置就会对该进程生效了:
  1. $ echo 12887 > /sys/fs/cgroup/cpu/cgroup-cpu/tasks
复制代码
使用 top 指令查看一下
  1. PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  2. 12887 root      25   5   14912   1912      0 R  20.3   0.0   2:51.05 zsh
复制代码
果然 CPU 被限制到了 20%。
4. 小结

Cgroups 是 Linux 下的一种将进程按组进行管理的机制,它提供了对一组进程及将来子进程的资源限制控制和统计的能力
cgroups 分为以下三个部分:
使用步骤:
如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。
搜索公众号【探索云原生】即可订阅

5. 参考

cgroups(7) — Linux manual page
Control groups series by Neil Brown
美团技术团队---Linux 资源管理之 cgroups 简介
Red Hat---资源管理指南
Linux Cgroup 系列(01):Cgroup 概述

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4