Docker 核心原理 cgroup

1. Cgroup

Docker 通过 cgroup 来控制容器使用的 资源配额,包括 CPU、内存、 磁盘等三个大的方面,基本覆盖了常见的资源配额和使用量的控制。

Cgroup( controller group),是Linux内核中提供的一种可以限制、记录、隔离进程组所使用的物理资源,例如CPU、Memory、磁盘IO等等)的机制,被LXC、Docker 等多种项目用于实现进程资源控制。

Cgroup 简单的来理解,就是可以将cpu、内存、磁盘I/O进行资源分配,限制使用的 大小、以及频率


2. Cgroup 功能实现

1) 资源限制: 对进程组使用的资源总额进行限制

  • Memory 子系统可以为进程组设定一个 Memory 使用上限,一旦进程组使用的内存达到限定的额度再次申请内存。

2) 优先级分配: 通过使用权重值分配CPU 的时间和硬盘的I/O大小

  • 可以使用CPU子系统为某个进程组分配特定 CPU share(优先级)

3) 资源统计: 记录容器对硬件的使用,时间用量等等

  • 可以使用cpuacct 子系统记录某个进程组使用的 cpu 时间等等

4) 进程控制: 对进程挂起、恢复等操作

  • 使用freezer 子系统可以将进程组挂起和恢复

5) 进程组隔离: 使不同的进程组使用不同的Namespace ,打到隔离的目的

  • 不同的进程组有各自的进程、网络、文件系统挂载空间。
  • 查看cgroup 挂载的子系统
[root@localhost ~]# ll /sys/fs/cgroup/
total 0
drwxr-xr-x. 5 root root  0 Mar 25 04:59 blkio
lrwxrwxrwx. 1 root root 11 Mar 25 04:59 cpu -> cpu,cpuacct
lrwxrwxrwx. 1 root root 11 Mar 25 04:59 cpuacct -> cpu,cpuacct
drwxr-xr-x. 5 root root  0 Mar 25 04:59 cpu,cpuacct
drwxr-xr-x. 3 root root  0 Mar 25 04:59 cpuset
drwxr-xr-x. 5 root root  0 Mar 25 04:59 devices
drwxr-xr-x. 3 root root  0 Mar 25 04:59 freezer
drwxr-xr-x. 3 root root  0 Mar 25 04:59 hugetlb
drwxr-xr-x. 5 root root  0 Mar 25 04:59 memory
lrwxrwxrwx. 1 root root 16 Mar 25 04:59 net_cls -> net_cls,net_prio
drwxr-xr-x. 3 root root  0 Mar 25 04:59 net_cls,net_prio
lrwxrwxrwx. 1 root root 16 Mar 25 04:59 net_prio -> net_cls,net_prio
drwxr-xr-x. 3 root root  0 Mar 25 04:59 perf_event
drwxr-xr-x. 5 root root  0 Mar 25 04:59 pids
drwxr-xr-x. 5 root root  0 Mar 25 04:59 systemd

3. Cgroup 子系统

Cgroup 将任意进程进行分组化管理的linux内核功能,cgroup 本身就是提供将进程进行分组化管理的功能和接口的基础结构,I/O或内存的分配控制等具体的资源管理功能是通过这个功能来实现的,这些具体的资源管理功能被称为一个Cgroup子系统。

blkio – 设置限制每个块设备(磁盘、U盘)的输入输出的控制。

CPU – 使用调度程序为Cgroup任务提供CPU 的访问

Cpuacct – 产生Cgroup 任务的CPU资源报告

cpuset – 如果,是多核心的CPU,这个子系统会为cgroup 任务分配单独的CPU和内存

devices – 允许 或拒绝 cgroup 任务对设备的访问

freezer – 暂停和恢复cgroup 的内存限制以及产生内存资源报告

memory – 设置每个Cgroup的内存限制以及生产内存资源的报告

net_cls – 标记了每个网络包,供cgroup 方便使用

ns – 命名空间子系统

perf_event – 增加了对每个 group 的检测跟踪能力,可以检测属于某个特定的group的所有线程以及运行在特定CPU上的 线程


4. 相关概念

  • task(任务):在Cgroup是一个要控制的系统进程
  • cgroup(控制族群): 控制组群就是按照某一种标准划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现,可以有多个 cgroup 组,可以限制不同的 内容,组名不能相同
  • subsystem(子系统): 子系统限制具体的内容,cgroup 组中的具体事项, 子系统就是资源控制器
  • hierarchy(层级): 层级树,由一堆cgroup 构成,包含了多个 cgroup的叫层级树, 用于控制cgroup

查看cgroup 支持的所有的子系统

lssubsys -a cgroup
cpuset					// 给task 分配独立的CPU
cpu,cpuacct				// cpu(控制程序对CPU使用), cpuacct(生成CPU使用情况报告)
memory					// 内存使用量进行限制,并且生成报告
devices					// 开启关闭设备的访问,不仅仅是块设备
freezer					// 挂起和恢复 task
net_cls,net_prio		// 没有直接被使用一给网络数据打标签
blkio					// 可以为块设备设置输入和输出限制
perf_event				// 性能测试
hugetlb					// 没有被使用   自动义
pids					// 暂时没有被使用, 限制单一的 pid 运行

lssubsys -a 显示系统中支持的所有的子系统

lssybsys -m 显示限制的目录的层次结构

关于层级树的四大规则(相互关系)

1) 同一个 hierarchy 可以附加一个或者多个子系统

  • 相当于一个hierarchy 可以限制跟多的类型,例如 CPU、Memory、磁盘IO

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_优先级

2) 一个已经附加到 hierarchy 的 subsystem ,不可以附加到其他的含有子系统的 hierarchy 上,可以附加到没有定义的控制的 hierarchy。

  • 一个子系统最多只能附加到一个层级
  • 一个层级可以附加多个子系统
  • 如果 同一个子系统附加到两个 含有子系统的 hierarchy 中, 会发生冲突
  • 假如, hierarchy A 限制了 Memory 使用500M ,但是 hierarchy 确实是限制 1G,这个是后就会发生冲突

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_docker_02

3) 同一个 task 不能属于同一个 hierarchy 的不同 cgroup中,仅仅可以属于不同的 hierarchy 中的不同的cgroup中。

task 要在当前 hierarchy 当中处于唯一的一个位置。

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_docker_03

4) 父进程在哪个task当中,子进程就在 task,子进程属于父进程, 子进程会继承欺负进程的 group

  • 如果 fock 创建一个 pid 为 58950 的进程,这时有人访问,创建了 58951 的进程,58951 进程刚创建时时和58950 在一个 cgroup ,但是会很快与父进程互相独立,可以加入到其他的cgroup中

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_进程组_04

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_docker_05

  • 每次在系统中创建新的层级时,该系统中的所有任务都是那个层级的默认 cgroup (我们称为 root cgroup,在此cgroup 创建层级时自动创建,后面在该层级中创建的 cgroup都是此cgroup的后台)的初始成员
  • Cgroup 层级关系显示, CPU+Memory 两个子系统都有自己独立的层级系统,但是又通过 同一个task(进程)取得了联系。
  • Cgroup 相当于 跟进程挂上了钩子

Docker 是利用 Cgroup 实现资源限制 docker中cgroup的功能_进程组_06

  • 安装 Cgroup 管理工具
yum -y install libcgroup-tools
  • 列出有效的cgroup
lscgroup

5. CPU 资源配置控制

1) CPU 份额控制(优先级)

  • Docker 提供 -c 或者 --cpu-shares 参数,在创建容器时指定容器所使用的CPU 份额,如果不指定,默认为1024
  • 简单的说就是为 该容器的CPU指定使用宿主机 CPU的使用率
  • 默认情况下,每个docker容器的份额都是 1024,单独的一个容器的份额是没有任何意义的,只有在同时运行多个容器的情况下,容器的CPU 加权的效果才会显示出来。
  • 查看默认的 份额配置
  • 默认都是 1024
[root@localhost ~]# cd /sys/fs/cgroup/cpu
[root@localhost cpu]# cat cpu.shares 
1024
  • 有关参数

--cpu-shares OR -c 限制 CPU 使用份额

--cpu-period 指定容器对CPU 的使用要在多久做一次重新分配,单位为 微秒

--cpu-quota 指定在这个周期内,最多可以有多少时间跑来这个容器,与–cpu-shares 相比,–cpu-quota 是一个绝对的值。 --cpu-quota 的值默认为 -1 ,表示不做控制, 单位为微秒

--cpuset-spus 指定CPU内核的节点

--cpuset-mems 指定 Memory 的节点

1.1) 为容器的CPU设置 CPU 权重

  • 将宿主机调成单核cpu,用于验证试验效果明显一点
  • 下载 progrium/stress 镜像进行测试
docker pull progrium/stress
  • 创建两个容器,开启两个docker产生冲突 top查看cpu占用率
  • aa 份额 512
  • bb 份额 1024
[root@localhost ~]# docker run -itd --name aa -c 512 progrium/stress --cpu 1
b5014c68cce454391aec3751eadda317bfd12ac97e2a87b5fb243660488381a9

[root@localhost ~]# docker run -itd --name bb -c 1024 progrium/stress --cpu 1
d47746159b3f778cb4572674f0d565dc65d188d7a140b91e9a14255af24cb54f
  • 查看 aa CPU 份额
[root@localhost docker]# pwd
/sys/fs/cgroup/cpu/docker
[root@localhost docker]# cd b5014c68cce454391aec3751eadda317bfd12ac97e2a87b5fb243660488381a9/
[root@localhost b5014c68cce454391aec3751eadda317bfd12ac97e2a87b5fb243660488381a9]# cat cpu.shares 
512
  • 查看 bb CPU 份额
[root@localhost docker]# pwd
/sys/fs/cgroup/cpu/docker
[root@localhost docker]# cd d47746159b3f778cb4572674f0d565dc65d188d7a140b91e9a14255af24cb54f/
[root@localhost d47746159b3f778cb4572674f0d565dc65d188d7a140b91e9a14255af24cb54f]# cat cpu.shares 
1024
  • 在宿主机,通过 top命令查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G8w35EQ5-1585191910253)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200325191522755.png)]

  • 通过 top 查看 发现 CPU 占用率发现基本第一个容器是第二个容器的二倍占用率,那是因为当我们制定了容器的优先级时,会根据优先级的大小分配CPU 资源( aa 512, bb 1024 , 占用率大概为 2:1)

2) CPU 周期控制

  • docker 提供了 --cpu-period --cpu-quota 两个参数控制容器可以分配到 CPU 时钟周期。
  • 如果 容器进程需要每一秒使用单个CPU 的0.2秒的时间,可以将 cpu-period 设置为 1000000(1,秒),cpu-quota 设置 200000(0.2秒)。
[root@localhost ~]# docker run -itd --name test-cpu --cpu-period 1000000 --cpu-quota 20000 centos /bin/bash
11aab2163f34ad88fa87cd6f336999b524e4daae50be9297d0b38865668198f2
  • 查看周期控制
  • ID 为容器长ID
[root@localhost ~]# cat /sys/fs/cgroup/cpu/docker/11aab2163f34ad88fa87cd6f336999b524e4daae50be9297d0b38865668198f2/cpu.cfs_period_us 
1000000
[root@localhost ~]# cat /sys/fs/cgroup/cpu/docker/11aab2163f34ad88fa87cd6f336999b524e4daae50be9297d0b38865668198f2/cpu.cfs_quota_us 
20000

3) CPU core 控制

  • 对于多核的CPU 服务器,Docker 可以控制容器运行时需要的 CPU 内核 的节点
  • 通过 --cpuset-cpus 参数,指定容器所要 使用的 CPU内核节点
  • 只对于有 多核CPU内核的主机生效
  • 调整主机的 CPU 内核数
  • 创建一个容器,只能使用 第一个、第二个内核
[root@localhost ~]# docker run -itd --name cpu --cpuset-cpus 0-1 centos /bin/bash
35fba50c4af0b1422d6db2824c97fe476302403ac3387a4456c708bc0a0f02a1

OR

[root@localhost ~]# docker run -itd --name cpu --cpuset-cpus="0,1" centos /bin/bash
7ed2fa82f40235ecc2973867a0f2115043837d35c1e9afccc400925668c65b91
  • 查看 使用的节点
[root@localhost ~]# docker exec -it cpu /bin/bash
[root@25f4353a555f /]# cat /sys/fs/cgroup/cpuset/cpuset.cpus  
0-1

6. 对内存的限额

  • 对容器使用的 内存进行限制
  • 物理内存与swap交换分区
  • 默认以下的参数为 -1 ,对内存和swap没有任何限制

-m OR --memory 设置内存的使用限额

--memory-swap 设置 内存+swap 的使用xinae

  • 允许容器最多使用 200M 内存和 100M swap
[root@localhost ~]#[root@localhost ~]# docker run -m 200M --memory-swap 300M -itd --name mem centos 
3e39464ede52337d5e95e0e6166e80da1262d55a924e6152d1d0032dce0937d1
  • 进入容器中查看
  • free -m 查看内存,单位 MB
  • free -g 查看内存,单位 GB
  • 我们会发现 查看 GB,都是1GB,与宿主机一样,这是由于 200MB 没办法显示,只能以 GB单位显示,所以 这不是问题
[root@localhost ~]# docker exec -it mem  /bin/bash
[root@3e39464ede52 /]# free -m
              total        used        free      shared  buff/cache   available
Mem:           1819         335        1022           9         461        1332
Swap:          2047           0        2047
[root@3e39464ede52 /]# free -g
              total        used        free      shared  buff/cache   available
Mem:              1           0           0           0           0           1
Swap:             1           0           1
  • 进行测试
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M

参数:

--vm 启动一个内存工作进程

--vm-bytes 280M 每个线程分配 280 内存

  • 超额进行测试
[root@localhost ~]# docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 350M
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 [6] forked
stress: dbug: [6] allocating 314572800 bytes ...
stress: dbug: [6] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (416) <-- worker 6 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 0s


  • 因为 280M 在可以分配的范围内(300M),所以工作线程能够正常工作
  • 其工作过程为:
分配 280MB内存
释放 280MB内存
再次分配 280MB内存
再次释放 280MB内存
一直循环下去

6. 对 Block I/O 限制

  • Block IO 是一种限制容器使用的资源,Block 指定的是磁盘的读写,docker 可以通过设置权重、限制 bps、iops 的方式控制容器读写磁盘的带宽。
  • 相关参数:

--blkio-weight 设置 block IO读写的优先级

--device-read-bps 限制读取某个设备的 bps

--device-write-bps 限制写入某个设备的 bps

--device-read-iops 限制读取某个设备的 iops

--device-write-iops 限制写入某个设备的 iops

  • bps 与 iops
  • bps 是 byte per second 每秒读写的数量
  • iops 是 io per second 每秒IO的次数

1) block IO 权重

  • 默认情况下, 所有的容器能够平等的读写磁盘,但是可以通过 设置 --blkio-weight 参数来改变容器blockIO的优先级
docker run -it --name io --blkio-weight 600 centos /bin/bash

2) 限制 bps 和 iops

例子: 限制硬盘每秒读写不超过 100MB

[root@localhost ~]# docker run -it --device-write-bps /dev/sda:95MB --device-read-bps /dev/sda:95MB centos
[root@f86e48c92612 /]# dd if=/dev/zero of=test bs=1M count=1024 oflag=direct
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 10.8136 s, 99.3 MB/s

1) block IO 权重

  • 默认情况下, 所有的容器能够平等的读写磁盘,但是可以通过 设置 --blkio-weight 参数来改变容器blockIO的优先级
docker run -it --name io --blkio-weight 600 centos /bin/bash

2) 限制 bps 和 iops

例子: 限制硬盘每秒读写不超过 100MB

[root@localhost ~]# docker run -it --device-write-bps /dev/sda:95MB --device-read-bps /dev/sda:95MB centos
[root@f86e48c92612 /]# dd if=/dev/zero of=test bs=1M count=1024 oflag=direct
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 10.8136 s, 99.3 MB/s