【重识云原生】第六章容器6.1.7.4节——cgroups使用

《重识云原生系列》专题索引:
- 第一章——不谋全局不足以谋一域
- 第二章计算第1节——计算虚拟化技术总述
- 第二章计算第2节——主流虚拟化技术之VMare ESXi
- 第二章计算第3节——主流虚拟化技术之Xen
- 第二章计算第4节——主流虚拟化技术之KVM
- 第二章计算第5节——商用云主机方案
- 第二章计算第6节——裸金属方案
- 第三章云存储第1节——分布式云存储总述
- 第三章云存储第2节——SPDK方案综述
- 第三章云存储第3节——Ceph统一存储方案
- 第三章云存储第4节——OpenStack Swift 对象存储方案
- 第三章云存储第5节——商用分布式云存储方案
- 第四章云网络第一节——云网络技术发展简述
- 第四章云网络4.2节——相关基础知识准备
- 第四章云网络4.3节——重要网络协议
- 第四章云网络4.3.1节——路由技术简述
- 第四章云网络4.3.2节——VLAN技术
- 第四章云网络4.3.3节——RIP协议
- 第四章云网络4.3.4节——OSPF协议
- 第四章云网络4.3.5节——EIGRP协议
- 第四章云网络4.3.6节——IS-IS协议
- 第四章云网络4.3.7节——BGP协议
- 第四章云网络4.3.7.2节——BGP协议概述
- 第四章云网络4.3.7.3节——BGP协议实现原理
- 第四章云网络4.3.7.4节——高级特性
- 第四章云网络4.3.7.5节——实操
- 第四章云网络4.3.7.6节——MP-BGP协议
- 第四章云网络4.3.8节——策略路由
- 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
- 第四章云网络4.3.10节——VXLAN技术
- 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
- 第四章云网络4.3.10.3节——VXLAN隧道机制
- 第四章云网络4.3.10.4节——VXLAN报文转发过程
- 第四章云网络4.3.10.5节——VXlan组网架构
- 第四章云网络4.3.10.6节——VXLAN应用部署方案
- 第四章云网络4.4节——Spine-Leaf网络架构
- 第四章云网络4.5节——大二层网络
- 第四章云网络4.6节——Underlay 和 Overlay概念
- 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
- 第四章云网络4.7.2节——virtio网络半虚拟化简介
- 第四章云网络4.7.3节——Vhost-net方案
- 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
- 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
- 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
- 第四章云网络4.7.8节——SR-IOV方案
- 第四章云网络4.7.9节——NFV
- 第四章云网络4.8.1节——SDN总述
- 第四章云网络4.8.2.1节——OpenFlow概述
- 第四章云网络4.8.2.2节——OpenFlow协议详解
- 第四章云网络4.8.2.3节——OpenFlow运行机制
- 第四章云网络4.8.3.1节——Open vSwitch简介
- 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
- 第四章云网络4.8.4节——OpenStack与SDN的集成
- 第四章云网络4.8.5节——OpenDayLight
- 第四章云网络4.8.6节——Dragonflow
4 CGroups使用
4.1 挂载cgroup树
开始使用cgroup前需要先挂载cgroup树,下面先看看如何挂载一颗cgroup树,然后再查看其根目录下生成的文件。
#准备需要的目录
- #准备需要的目录
- dev@ubuntu:~$ mkdir cgroup && cd cgroup
- dev@ubuntu:~/cgroup$ mkdir demo
-
- #由于name=demo的cgroup树不存在,所以系统会创建一颗新的cgroup树,然后挂载到demo目录
- dev@ubuntu:~/cgroup$ sudo mount -t cgroup -o none,name=demo demo ./demo
-
- #挂载点所在目录就是这颗cgroup树的root cgroup,在root cgroup下面,系统生成了一些默认文件
- dev@ubuntu:~/cgroup$ ls ./demo/
- cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
-
- #cgroup.procs里包含系统中的所有进程
- dev@ubuntu:~/cgroup$ wc -l ./demo/cgroup.procs
- 131 ./demo/cgroup.procs
下面是每个文件的含义:
- cgroup.clone_children
这个文件只对cpuset(subsystem)有影响,当该文件的内容为1时,新创建的cgroup将会继承父cgroup的配置,即从父cgroup里面拷贝配置文件来初始化新cgroup,可以参考这里
- cgroup.procs
当前cgroup中的所有进程ID,系统不保证ID是顺序排列的,且ID有可能重复
- cgroup.sane_behavior
- notify_on_release
该文件的内容为1时,当cgroup退出时(不再包含任何进程和子cgroup),将调用release_agent里面配置的命令。新cgroup被创建时将默认继承父cgroup的这项配置。
- release_agent
里面包含了cgroup退出时将会执行的命令,系统调用该命令时会将相应cgroup的相对路径当作参数传进去。 注意:这个文件只会存在于root cgroup下面,其他cgroup里面不会有这个文件。
- tasks
当前cgroup中的所有线程ID,系统不保证ID是顺序排列的,后面在介绍如何往cgroup中添加进程时会介绍cgroup.procs和tasks的差别。
4.2 创建和删除cgroup
挂载好上面的cgroup树之后,就可以在里面建子cgroup了。
- #创建子cgroup很简单,新建一个目录就可以了
- dev@ubuntu:~/cgroup$ cd demo
- dev@ubuntu:~/cgroup/demo$ sudo mkdir cgroup1
-
- #在新创建的cgroup里面,系统默认也生成了一些文件,这些文件的意义和root cgroup里面的一样
- dev@ubuntu:~/cgroup/demo$ ls cgroup1/
- cgroup.clone_children cgroup.procs notify_on_release tasks
-
- #新创建的cgroup里没有任何进程和线程
- dev@ubuntu:~/cgroup/demo$ wc -l cgroup1/cgroup.procs
- 0 cgroup1/cgroup.procs
- dev@ubuntu:~/cgroup/demo$ wc -l cgroup1/tasks
- 0 cgroup1/tasks
-
- #每个cgroup都可以创建自己的子cgroup,所以我们也可以在cgroup1里面创建子cgroup
- dev@ubuntu:~/cgroup/demo$ sudo mkdir cgroup1/cgroup11
- dev@ubuntu:~/cgroup/demo$ ls cgroup1/cgroup11
- cgroup.clone_children cgroup.procs notify_on_release tasks
-
- #删除cgroup也很简单,删除掉相应的目录就可以了
- dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/
- rmdir: failed to remove 'cgroup1/': Device or resource busy
- #这里删除cgroup1失败,是因为它里面包含了子cgroup,所以不能删除,
- #如果cgroup1包含有进程或者线程,也会删除失败
-
- #先删除cgroup11,再删除cgroup1就可以了
- dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/cgroup11/
- dev@ubuntu:~/cgroup/demo$ sudo rmdir cgroup1/
4.3 添加进程
创建新的cgroup后,就可以往里面添加进程了。注意下面几点:
- 在一颗cgroup树里面,一个进程必须要属于一个cgroup。
- 新创建的子进程将会自动加入父进程所在的cgroup。
- 从一个cgroup移动一个进程到另一个cgroup时,只要有目的cgroup的写入权限就可以了,系统不会检查源cgroup里的权限。
- 用户只能操作属于自己的进程,不能操作其他用户的进程,root账号除外。
- #--------------------------第一个shell窗口----------------------
- #创建一个新的cgroup
- dev@ubuntu:~/cgroup/demo$ sudo mkdir test
- dev@ubuntu:~/cgroup/demo$ cd test
-
- #将当前bash加入到上面新创建的cgroup中
- dev@ubuntu:~/cgroup/demo/test$ echo $$
- 1421
- dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > cgroup.procs'
- #注意:一次只能往这个文件中写一个进程ID,如果需要写多个的话,需要多次调用这个命令
-
- #--------------------------第二个shell窗口----------------------
- #重新打开一个shell窗口,避免第一个shell里面运行的命令影响输出结果
- #这时可以看到cgroup.procs里面包含了上面的第一个shell进程
- dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
- 1421
-
- #--------------------------第一个shell窗口----------------------
- #回到第一个窗口,运行top命令
- dev@ubuntu:~/cgroup/demo/test$ top
- #这里省略输出内容
-
- #--------------------------第二个shell窗口----------------------
- #这时再在第二个窗口查看,发现top进程自动和它的父进程(1421)属于同一个cgroup
- dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
- 1421
- 16515
- dev@ubuntu:~/cgroup/demo/test$ ps -ef|grep top
- dev 16515 1421 0 04:02 pts/0 00:00:00 top
- dev@ubuntu:~/cgroup/demo/test$
-
- #在一颗cgroup树里面,一个进程必须要属于一个cgroup,
- #所以我们不能凭空从一个cgroup里面删除一个进程,只能将一个进程从一个cgroup移到另一个cgroup,
- #这里我们将1421移动到root cgroup
- dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > ../cgroup.procs'
- dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
- 16515
- #移动1421到另一个cgroup之后,它的子进程不会随着移动
-
- #--------------------------第一个shell窗口----------------------
- ##回到第一个shell窗口,进行清理工作
- #先用ctrl+c退出top命令
- dev@ubuntu:~/cgroup/demo/test$ cd ..
- #然后删除创建的cgroup
- dev@ubuntu:~/cgroup/demo$ sudo rmdir test
4.4 权限
上面我们都是用sudo(root账号)来操作的,但实际上普通账号也可以操作cgroup
- #创建一个新的cgroup,并修改他的owner
- dev@ubuntu:~/cgroup/demo$ sudo mkdir permission
- dev@ubuntu:~/cgroup/demo$ sudo chown -R dev:dev ./permission/
-
- #1421原来属于root cgroup,虽然dev没有root cgroup的权限,但还是可以将1421移动到新的cgroup下,
- #说明在移动进程的时候,系统不会检查源cgroup里的权限。
- dev@ubuntu:~/cgroup/demo$ echo 1421 > ./permission/cgroup.procs
-
- #由于dev没有root cgroup的权限,再把1421移回root cgroup失败
- dev@ubuntu:~/cgroup/demo$ echo 1421 > ./cgroup.procs
- -bash: ./cgroup.procs: Permission denied
-
- #找一个root账号的进程
- dev@ubuntu:~/cgroup/demo$ ps -ef|grep /lib/systemd/systemd-logind
- root 839 1 0 01:52 ? 00:00:00 /lib/systemd/systemd-logind
- #因为该进程属于root,dev没有操作它的权限,所以将该进程加入到permission中失败
- dev@ubuntu:~/cgroup/demo$ echo 839 >./permission/cgroup.procs
- -bash: echo: write error: Permission denied
- #只能由root账号添加
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 839 >./permission/cgroup.procs'
-
- #dev还可以在permission下创建子cgroup
- dev@ubuntu:~/cgroup/demo$ mkdir permission/c1
- dev@ubuntu:~/cgroup/demo$ ls permission/c1
- cgroup.clone_children cgroup.procs notify_on_release tasks
-
- #清理
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 839 >./cgroup.procs'
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1421 >./cgroup.procs'
- dev@ubuntu:~/cgroup/demo$ rmdir permission/c1
- dev@ubuntu:~/cgroup/demo$ sudo rmdir permission
4.5 cgroup.procs vs tasks
上面提到cgroup.procs包含的是进程ID, 而tasks里面包含的是线程ID,那么他们有什么区别呢?
- #创建两个新的cgroup用于演示
- dev@ubuntu:~/cgroup/demo$ sudo mkdir c1 c2
-
- #为了便于操作,先给root账号设置一个密码,然后切换到root账号
- dev@ubuntu:~/cgroup/demo$ sudo passwd root
- dev@ubuntu:~/cgroup/demo$ su root
- root@ubuntu:/home/dev/cgroup/demo#
-
- #系统中找一个有多个线程的进程
- root@ubuntu:/home/dev/cgroup/demo# ps -efL|grep /lib/systemd/systemd-timesyncd
- systemd+ 610 1 610 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
- systemd+ 610 1 616 0 2 01:52 ? 00:00:00 /lib/systemd/systemd-timesyncd
- #进程610有两个线程,分别是610和616
-
- #将616加入c1/cgroup.procs
- root@ubuntu:/home/dev/cgroup/demo# echo 616 > c1/cgroup.procs
- #由于cgroup.procs存放的是进程ID,所以这里看到的是616所属的进程ID(610)
- root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
- 610
- #从tasks中的内容可以看出,虽然只往cgroup.procs中加了线程616,
- #但系统已经将这个线程所属的进程的所有线程都加入到了tasks中,
- #说明现在整个进程的所有线程已经处于c1中了
- root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
- 610
- 616
-
- #将616加入c2/tasks中
- root@ubuntu:/home/dev/cgroup/demo# echo 616 > c2/tasks
-
- #这时我们看到虽然在c1/cgroup.procs和c2/cgroup.procs里面都有610,
- #但c1/tasks和c2/tasks中包含了不同的线程,说明这个进程的两个线程分别属于不同的cgroup
- root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
- 610
- root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
- 610
- root@ubuntu:/home/dev/cgroup/demo# cat c2/cgroup.procs
- 610
- root@ubuntu:/home/dev/cgroup/demo# cat c2/tasks
- 616
- #通过tasks,我们可以实现线程级别的管理,但通常情况下不会这么用,
- #并且在cgroup V2以后,将不再支持该功能,只能以进程为单位来配置cgroup
-
- #清理
- root@ubuntu:/home/dev/cgroup/demo# echo 610 > ./cgroup.procs
- root@ubuntu:/home/dev/cgroup/demo# rmdir c1
- root@ubuntu:/home/dev/cgroup/demo# rmdir c2
- root@ubuntu:/home/dev/cgroup/demo# exit
- exit
4.6 release_agent
当一个cgroup里没有进程也没有子cgroup时,release_agent将被调用来执行cgroup的清理工作。
- #创建新的cgroup用于演示
- dev@ubuntu:~/cgroup/demo$ sudo mkdir test
- #先enable release_agent
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1 > ./test/notify_on_release'
-
- #然后创建一个脚本/home/dev/cgroup/release_demo.sh,
- #一般情况下都会利用这个脚本执行一些cgroup的清理工作,但我们这里为了演示简单,仅仅只写了一条日志到指定文件
- dev@ubuntu:~/cgroup/demo$ cat > /home/dev/cgroup/release_demo.sh << EOF
- #!/bin/bash
- echo \$0:\$1 >> /home/dev/release_demo.log
- EOF
-
- #添加可执行权限
- dev@ubuntu:~/cgroup/demo$ chmod +x ../release_demo.sh
-
- #将该脚本设置进文件release_agent
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo /home/dev/cgroup/release_demo.sh > ./release_agent'
- dev@ubuntu:~/cgroup/demo$ cat release_agent
- /home/dev/cgroup/release_demo.sh
-
- #往test里面添加一个进程,然后再移除,这样就会触发release_demo.sh
- dev@ubuntu:~/cgroup/demo$ echo $$
- 27597
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./test/cgroup.procs'
- dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./cgroup.procs'
-
- #从日志可以看出,release_agent被触发了,/test是cgroup的相对路径
- dev@ubuntu:~/cgroup/demo$ cat /home/dev/release_demo.log
- /home/dev/cgroup/release_demo.sh:/test
4.7 如何查看当前进程属于哪些cgroup
可以通过查看/proc/[pid]/cgroup(since Linux 2.6.24)知道指定进程属于哪些cgroup。
- dev@ubuntu:~$ cat /proc/777/cgroup
- 11:cpuset:/
- 10:freezer:/
- 9:memory:/system.slice/cron.service
- 8:blkio:/system.slice/cron.service
- 7:perf_event:/
- 6:net_cls,net_prio:/
- 5:devices:/system.slice/cron.service
- 4:hugetlb:/
- 3:cpu,cpuacct:/system.slice/cron.service
- 2:pids:/system.slice/cron.service
- 1:name=systemd:/system.slice/cron.service
每一行包含用冒号隔开的三列,他们的意思分别是
- cgroup树的ID, 和/proc/cgroups文件中的ID一一对应。
- 和cgroup树绑定的所有subsystem,多个subsystem之间用逗号隔开。这里name=systemd表示没有和任何subsystem绑定,只是给他起了个名字叫systemd。
- 进程在cgroup树中的路径,即进程所属的cgroup,这个路径是相对于挂载点的相对路径。
4.8 完整使用示例
查看cgroup挂载点:

创建隔离组
- cd /sys/fs/cgroup/memory/
- mkdir test
目录创建完成会自动生成以下文件
ls test

先写测试程序
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-
- #define MB (1024 * 1024)
-
- int main(int argc, char *argv[])
- {
- char *p;
- int i = 0;
- while(1) {
- p = (char *)malloc(MB);
- memset(p, 0, MB);
- printf("%dM memory allocated\n", ++i);
- sleep(1);
- }
-
- return 0;
- }
程序很简单,无限循环,来向系统申请内存,它会每秒消耗1M的内存。
下面开始写入一些具体的参数:

- 设置内存限额为5M
sudo sh -c "echo 5M > memory.limit_in_bytes"
- 把当前bash加入到test中,即所有在此bash下创建的进程都会加入到test中
sudo sh -c "echo $$ >> cgroup.procs"
- 查看一下oom开关,一般cgroup下oom是不能关闭的。默认为0,即开启。
cat memory.oom_control
- oom会受到swap空间的影响,设置他为0,把它禁用。
sudo sh -c "echo 0 > memory.swappiness"
接下来运行测试程序,当分配到5M时,会关闭bash。因为被kill。我们关掉oom,这样程序就会暂停,这时来观察一下。

- sudo sh -c "echo 1 >> memory.oom_control"
-
- cat memory.oom_control

运行程序发现停在这里了

再开终端查看oom,发现under_oom为1,表示当前已经oom了。

在当前终端设置内存限额为7M。

回到第一个终端可以看到程序又开始继续分配了,最终停在了5M处
参考链接
linux 容器(LXC) 第4章 cgroups_caoshuming_500的博客-CSDN博客
Linux 基础:cgroup 原理与实现_CGroup_层级_控制
【docker 底层知识】cgroup 原理分析_张忠琳的博客-CSDN博客_cgroup
CGroup的原理和使用_书笑生的博客-CSDN博客_cgroup原理
Linux Cgroups详解(二) - lisperl - 博客园
Linux Cgroup系列(04):限制cgroup的内存使用(subsystem之memory) - SegmentFault 思否
Linux Cgroup系列(01):Cgroup概述 - SegmentFault 思否
Linux Cgroup系列(02):创建并管理cgroup - SegmentFault 思否
深入理解 Linux Cgroup 系列(一):基本概念 - SegmentFault 思否
本文由mdnice多平台发布


浙公网安备 33010602011771号