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
2) 一个已经附加到 hierarchy 的 subsystem ,不可以附加到其他的含有子系统的 hierarchy 上,可以附加到没有定义的控制的 hierarchy。
- 一个子系统最多只能附加到一个层级
- 一个层级可以附加多个子系统
- 如果 同一个子系统附加到两个 含有子系统的 hierarchy 中, 会发生冲突
- 假如, hierarchy A 限制了 Memory 使用500M ,但是 hierarchy 确实是限制 1G,这个是后就会发生冲突
3) 同一个 task 不能属于同一个 hierarchy 的不同 cgroup中,仅仅可以属于不同的 hierarchy 中的不同的cgroup中。
task 要在当前 hierarchy 当中处于唯一的一个位置。
4) 父进程在哪个task当中,子进程就在 task,子进程属于父进程, 子进程会继承欺负进程的 group
- 如果 fock 创建一个 pid 为 58950 的进程,这时有人访问,创建了 58951 的进程,58951 进程刚创建时时和58950 在一个 cgroup ,但是会很快与父进程互相独立,可以加入到其他的cgroup中
- 每次在系统中创建新的层级时,该系统中的所有任务都是那个层级的默认 cgroup (我们称为 root cgroup,在此cgroup 创建层级时自动创建,后面在该层级中创建的 cgroup都是此cgroup的后台)的初始成员
- Cgroup 层级关系显示, CPU+Memory 两个子系统都有自己独立的层级系统,但是又通过 同一个task(进程)取得了联系。
- Cgroup 相当于 跟进程挂上了钩子
- 安装 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