docker cgroup详解

目录

原理介绍

组织结构和基本规则介绍

规则一:同一个Hierarchy(层级)可以附加一个或者多个subsystem(子系统)

规则二:一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。

规则三:一个tasks不能存在于同一层级的不同cgroup中,但一个tasks可以存在于不同层级中的多个cgroup中。

规则四:tasks任务在fork/clone自身创建的子任务默认与原任务在同一个cgroup中,但是子任务允许被移动到不同的cgroup中。

测试用例

CPU限制

内存限制


原理介绍

cgroup不仅可以限制被namespace隔离起来的资源,还可以为我们的资源设置权重,操控进程,停止进程等操作。

我们这里重点学习cgroup怎么实现限制的,以配置为准,它整个配置是通过伪文件系统配置文件的方式来修改配置的。所以我们最终理解一下它的作用即可。

docker cgroup详解_第1张图片

所以cgroup和我们之前的通俗的理解上用户和组的关系是类似的,把用户放到组里面,然后对组限制权限,然后组的目录又分为父目录,子目录,有继承关系,不同目录的继承关系有一个规定,就是入乡随俗,跨目录又不行。。。

我们namespace划分的是空间资源的东西,划分出来一个隔离的空间,这个空间跟我们完全解耦的虚拟化有一个最大的区别在于就是没有hypervisor那一层,这一层可以隔离我们的物理资源,那么这个时候cgroup就起到我们隔离物理资源的管控的工具。那么cgroup起到的作用就是物理资源的管控。

虚拟机和docker容器的区别

docker cgroup详解_第2张图片

我们常见的物理资源有:CPU,memory,IO.......

我们物理硬件资源是为了给虚拟化提供一种叫做基础的运行保障,它是构成docker一系列虚拟化管理当中的基础 。

从开发的角度来看,cgroup有以下几个特点

docker cgroup详解_第3张图片

cgroup的作用

docker cgroup详解_第4张图片

cgroup的术语

docker cgroup详解_第5张图片

分析:

1. 其实每一个程序里面表示进程都由task来表示的,我们随便找个进程验证一下

[root@master1 ~]# ls /proc/577/task/
577
[root@master1 ~]#

我们在进到577这个进程里面,可以看到进程里面的内容

docker cgroup详解_第6张图片

2. 关于subsystem子系统,我们可以先安装一个工具 yum install libcgroup-tools -y

可以看到系统当中所有的subsystem内容

docker cgroup详解_第7张图片

通过名字我们就可以发现,是对哪一项进行控制的。

3. 现在我们进入到sys这个伪文件系统的目录下(注意我们之前学习proc也是一个伪文件系统,这个是内存的)

docker cgroup详解_第8张图片

对上面的目录分析:

docker cgroup详解_第9张图片

这里我们只要关注一下内存和CPU即可,这个是我们经常用到的。。。

我们手动在/sys/fs/cgroup/cpu目录下创建一个目录,会看见系统会为我们默认生成一堆文件。

docker cgroup详解_第10张图片

通过控制这些文件里面的数值,就可以对资源进行限制。比如cpu.cfs_quota_us文件,如果我们往里写入100000(十万),那么就证明使用了xjjdog的cgroup,最多能够使用1核的CPU。写入20000,证明最多使用使用1/5核的CPU,这是因为,cpu.cfs_period_us这个配置文件,默认把1核cpu分成了10万份。。。

组织结构和基本规则介绍

规则一:同一个Hierarchy(层级)可以附加一个或者多个subsystem(子系统)

docker cgroup详解_第11张图片

分析:假如我们这里有一个Cgroup Hierarchy A的程序,它有自己的cgroup,我们这里给它取个名字就叫做cpu_mem_cg,同时这个cgroup它又有自己的两个cgroup,一个叫cg1,一个叫做cg2,这些组成了一个Hierarchy(层级关系),然后我们这里有一个CPU,Memory作为subsystem,假如说我们现在想要对Cgroup Hierarchy A程序里面的cgroup进行控制的话,那么CPU,Memory就可以附加到我们Cgroup Hierarchy A程序中来。这个就是规则一的简单介绍,同一个Hierarchy(层级)可以附加一个或者多个subsystem(子系统)。

扩展:其实我们cgroup里面都是有tasks的,我们cg1里面都是有tasks的。这里我们以cpu这个目录为例,然后看到里面的tasks里面全是进程编号,它的意思就是,我们tasks里面的进程都要遵循cpu这个目录里面的控制,里面的每一个选项(cpu目录里面的文件,例如:cpu.shares,cpu.stat等等)都代表一个规则。

docker cgroup详解_第12张图片

例如:最常见的cpu.shares,我们cat一下它的值,1024,表示tasks里面的所有进程使用cpu share的有限就为1024。

[root@master1 cpu]# cat cpu.shares
1024

规则二:一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。

现在假设说我们现在还有有一 个Cgroup Hierarchy B的程序,我们的Memory没有附加在Cgroup Hierarchy A上,而是附加在Cgroup Hierarchy B上了。Cgroup Hierarchy A对CPU的subsystem进行了附加,Cgroup Hierarchy B对Memory的CPU进行了附加。如果我们要想把CPU附加到Cgroup Hierarchy B这个节点上来,这种跨平台的方式是不被允许的。这个就叫做一个已经附加到某个Hierarchy 的subsystem不能附加到别的Hierarchy 的subsystem上。

docker cgroup详解_第13张图片

如果真的想用,那么只能附加到一个没有subsystem的Hierarchy节点上。

docker cgroup详解_第14张图片

具体的业务场景就是,我们的一个tasks既想被我们的CPU控制,也想被我们的Memory控制,

那么tasks里面的值也要放到对应的cgroup层级中去。。。

规则三:一个tasks不能存在于同一层级的不同cgroup中,但一个tasks可以存在于不同层级中的多个cgroup中。

假如我们现在有两个Hierarchy A和B,现在突然冒出一个进程,例如:httpd,进程号为1234,这个进程可以放到A程序中的cg1或者B程序中的cg1。但是如果放到了A程序的cg1中,那么就不能放到A程序中的cg2中。原因其实很简单,如果我们的cg1是限制CPU的30%,cg2是限制50%,那么最后听谁的呢。。。

docker cgroup详解_第15张图片

规则四:tasks任务在fork/clone自身创建的子任务默认与原任务在同一个cgroup中,但是子任务允许被移动到不同的cgroup中。

例如我们还是以一个Apache的进程为例,我们httpd刚开始属于cg1,我们知道Apache每建立一个链接,就要fork一个子进程,这个子进程在初始状态的时候一定跟它的父进程在同一个cgroup里面(这里的cg1),我们对子进程进行一个特殊的设置,子进程运行一段时间之后,子进程可以移动到不同的cgroup中去。

docker cgroup详解_第16张图片

上面的都是理论部分,下面我进行一些小实验来验证一下

测试用例

这里我们先下载一个镜像progrium/stress,通过这个镜像来验证我们的试验是否生效。

下载完成之后,按理说我们需要将宿主机调整为单核CPU,这样效果更明显,但是我的VMware开启单核就起不来虚拟机了,这里我改成2核进行试验。顺便清理一下防火墙

[root@master1 ~]# iptables -F
[root@master1 ~]# iptables-save
[root@master1 ~]# systemctl restart docker

CPU限制

前面的准备工作完成之后,就可以开始了

根据上面的理论说明,我们默认的cpu-shares是1024,这里我们启动一个容器,修改为512看看

[root@master1 ~]# docker run -itd --name AA --cpu-shares 512 centos:7 bash

此时我们进去/sys/fs/cgroup/cpu/下面去看看

[root@master1 cpu]# pwd
/sys/fs/cgroup/cpu
[root@master1 cpu]# cat cpu.shares 
1024
[root@master1 cpu]#

然而并没有改变,还是默认的1024,其实是我们找错目录了,

docker cgroup详解_第17张图片

我们可以看到进程编号2619其实就是我们开启的容器里的进程编号。

上面的目录关系我们理解一下

docker cgroup详解_第18张图片

当然我们也可以手动的修改这个值

[root@master1 811baf188595e976c6cbee3017299f7b6e191dcfc471529b3feb6eb07d24d76a]# echo 2048 > cpu.shares 
[root@master1 811baf188595e976c6cbee3017299f7b6e191dcfc471529b3feb6eb07d24d76a]# cat cpu.shares 
2048
[root@master1 811baf188595e976c6cbee3017299f7b6e191dcfc471529b3feb6eb07d24d76a]# 

接下来我们使用stress这个容器具体看一下限制之后的效果

[root@master1 docker]# docker run -itd --name aa -c 512 progrium/stress --cpu 2 
9310cb28f60dd569abfe20674d3e4be7f47d55317814b0e0f41ce19718f8491a
[root@master1 docker]# docker run -itd --name bb -c 1024 progrium/stress --cpu 2 
c8c33ef0616b5ea42705a36057cfe8575e32a17a7e01a5e0daae7c3e46796b84
[root@master1 docker]# 

-c 指定优先级(cpu.shares)

--cpu 把CPU核数压满,即要消耗多少CPU

启动之后,使用docker stats可以看到容器占用的CPU

或者使用top命令

docker cgroup详解_第19张图片

此时使用docker top xxxx命令可以看到

docker cgroup详解_第20张图片

此时我们杀掉其中一个进程kill -9 4798,另外一个进程的CPU就会立刻压满。

docker cgroup详解_第21张图片

上面就是对CPU的限制,下面试验对内存的限制,在Linux系统中。内存分为物理内存和swap分区,限制上也有这个。

内存限制

先检查swap分区是否开启,使用命令swapon,或free -h看看

若未开启则使用如下命令创建swap分区

[root@master1 ~]# dd if=/dev/zero of=/swap bs=1M count=1024
[root@master1 ~]# mkswap -f /swap
[root@master1 ~]# swapon /swap

然后可以试验了

[root@master1 ~]# docker run --memory 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M

--memory-swap表示swap+物理内存总共限额多少,这里是限制内存+swap300M

--vm:使用多少个进程

--vm-bytes:进程占用的内存大小

这里我们限制300M,占用280M,则可以运行。若改成300M,则不会运行

[root@master1 ~]# docker run --memory 200M --memory-swap=300M  progrium/stress --vm 1 --vm-bytes 300M
stress: FAIL: [1] (416) <-- worker 8 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 1s
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [8] forked
[root@master1 ~]# 

查看dmesg打印信息,有OOM错误

docker cgroup详解_第22张图片

若只使用-m参数运行的话,表示容器最多使用物理内存多少,swap多少,如下:最多使用物理内存200M,最多使用swap分区200M

[root@master1 ~]# docker run -itd -m 200M centos:7 bash

参考资料:《docker 容器与容器云(第2版)》

你可能感兴趣的:(docker学习,docker)