一文读懂 Docker 资源管理的底层原理与实践

一、对资源管理及 Cgroup 的认识

在Linux系统使用中有一个需求经常被提到“希望能限制某个或者某一些进程的资源分配”。这时候就出现了一个新的名词Cgroups。

Cgroups是Control Groups的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO等等)的机制,对进程分层分组,并通过特殊的文件系统公开。

最初由Google的工程师提出,后来被整合进Linux内核中。

一文读懂 Docker 资源管理的底层原理与实践

二、Docker-Cgroup原理与机制

2.1 原理与作用

Docker使用Namespace 完成了环境的隔离。但是对于容器来说,所有容器可以共享宿主机的全部 CPU 和内存及 IO 资源,要完成这些资源的隔离,Docker需要借助于Cgroup。Cgroups中的重要概念是”子系统”,也就是资源控制器,每种子系统就是一个资源的分配器,比如CPU子系统是控制CPU时间分配的。

一文读懂 Docker 资源管理的底层原理与实践

2.2 Cgroup的关键概念

1) Subsystems: 称之为子系统,一个子系统就是一个资源控制器,比如 CPU子系统就是控制cpu时间分配的一个控制器。

2) Hierarchies: 可以称之为层次体系也可以称之为继承体系,指的是Control Groups是按照层次体系的关系进行组织的。

3) Control Groups: 一组按照某种标准划分的进程。进程可以从一个Control Groups迁移到另外一个Control Groups中,同时Control Groups中的进程也会受到这个组的资源限制。

4) Tasks: 在Cgroups中,Tasks就是系统的一个进程

2.3 Cgroup 子系统

Cgroup子系统名称与用途包括:

一文读懂 Docker 资源管理的底层原理与实践

子系统的文档:

使用命令 yum install kernel-doc 安装rpm包。

文档全部在/usr/share/doc/kernel-doc-<kernel_version>/Documentation/cgroups/ 目录中,全部为txt格式。

关于 cpu 子系统的更多信息如下:

实时调度 ——

/usr/share/doc/kernel-doc-<kernel_version>/Documentation/scheduler/sched-rt-group.txt

CFS 调度 ——

/usr/share/doc/kernel-doc-<kernel_version>/Documentation/scheduler/sched-bwc.txt

2.4 Cgroup的默认层级

在centos/red_hat 7的系统,默认情况下,systemd 会自动创建 slice、scope 和 service 单位的层级,来为 Cgroup 树提供统一结构。

systemd 的单位类型,系统中运行的所有进程,都是 systemd init 进程的子进程。在资源管控方面,systemd 提供了三种单位类型。

  • service - 一个或一组进程,由 systemd 依据单位配置文件启动。service 对指定进程进行封装,这样进程可以作为一个整体被启动或终止.(系统中的服务xxxx.service)
  • scope - 一组外部创建的进程。由强制进程通过 fork() 函数启动和终止、之后被 systemd 在运行时注册的进程,scope 会将其封装。例如:用户会话、 容器和虚拟机被认为是 scope。
  • slice - 一组按层级排列的单位。slice 并不包含进程,但会组建一个层级,并将 scope 和 service 都放置其中。真正的进程包含在 scope 或 service 中。

可以使用systemd-cgls 命令查看:

[root@192 systemd]# systemd-cgls

一文读懂 Docker 资源管理的底层原理与实践

一文读懂 Docker 资源管理的底层原理与实践

其中user.slice下的2个scope为用户登陆的tty,中间的数字代表用户的uid。

service.slice下代表系统的所有服务。

整个层级结构也可以在/sys/fs/cgroup/systemd下观察到。

2.5 图解层级与子系统的关系

  • 系统中第一个被创建的cgroup被称为root Cgroup,该Cgroup的成员包含系统中所有的进程
  • 一个子系统可以位于多个层级中。但是有限制条件,当且仅当这些层级没有其他的子系统,比如2个Hierarchies可以同时只具有一个CPU子系统

一文读懂 Docker 资源管理的底层原理与实践

  • 每个层级中可以关联多个子系统

一文读懂 Docker 资源管理的底层原理与实践

  • 一个进程可以位于不同层级的Cgroup中

一文读懂 Docker 资源管理的底层原理与实践

  • 一个进程创建了子进程后,该子进程默认为父进程所在Cgroup的成员,此子进程可以手动更换Cgroup组

一文读懂 Docker 资源管理的底层原理与实践

三、Docker-Cgroup 实践

3.1 创建1个控制组

1.使用systemd-run 创建临时 Cgroup

以root用户执行

systemd-run --unit=name --scope --slice=slice_name command

备注:

  • name 代表您想要此单位被识别的名称。如果 --unit 没有被指定,单位名称会自动生成。
  • 使用可选的 --scope 参数创建临时 scope 单位来替代默认创建的 service 单位。
  • --slice 选项,让您新近创建的 service 或 scope 单位可以成为指定 slice 的一部分。
  • command 用您希望在 service 单位中运行的指令替代 command。将此指令放置于 systemd-run 句法的最末端。

示例:

这里写了一个打满Cpu的sh脚本,本身是一个死循环

[root@192 1.sh.service]# cat /root/1.sh 
while true
do
x=+1
done

执行下面的命令来完成创建控制组

systemd-run --unit=1.sh --slice=test sh /root/1.sh

使用下面的命令查询服务运行状态

systemctl status 1.sh

使用show命令查看服务运行参数

systemctl show 1.sh

查看目录结构:

tree /sys/fs/cgroup/systemd/

会发现创建了1个test.slice的目录,这个目录下放了1个1.sh.service的目录。 都是在创建时指定的。

2.创建永久Cgroup

若要在系统启动时,配置一个自动启动的单位,请执行 systemctl enable 前提是united文件已经写好.

3.2 删除控制组

1.临时控制组包含的进程一旦结束,临时Cgroup就会自动释放。

停止服务组中的所有进程:

systemctl stop 1.sh

查看systemd下的test.slice目录

ls /sys/fs/cgroup/systemd/test.slice

会发现1.sh那个服务已经没有了,被释放掉了

2.永久控制组,删除

执行

systemctl disable xxxxx

3.3 修改Cgroup参数(资源控制)

使用命令

systemctl set-property name parameter=value

其中name表示控制组名,parameter表示参数名

1.cpu资源的控制——CPUQUOTA(cpu单位周期使用时间)

使用上述1.sh创建一个控制组(1.sh写了个死循环,可以打满cpu)

[root@192 ~]# systemd-run --unit=2.sh --slice=test sh /root/1.sh
Running as unit 2.sh.service.

使用top查看cpu负载

一文读懂 Docker 资源管理的底层原理与实践

发现2.sh 这个服务已经跑满100%了

使用systemctl设置cpu的quota值

[root@192 ~]# systemctl set-property 2.sh.service CPUQuota=50%

然后在查看top值

一文读懂 Docker 资源管理的底层原理与实践

会发现该进程已经降低为50%

查看/sys/fs/cgroup/cpu/目录

[root@192 ~]# cd /sys/fs/cgroup/cpu/
[root@192 cpu]# ls
cgroup.clone_children cgroup.sane_behavior cpuacct.usage_percpu cpu.rt_period_us cpu.stat system.slice user.slice
cgroup.event_control cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release tasks
cgroup.procs cpuacct.usage cpu.cfs_quota_us cpu.shares release_agent test.slice
[root@192 cpu]# cd test.slice/
[root@192 test.slice]# ls
2.sh.service cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
[root@192 test.slice]# cd 2.sh.service/
[root@192 2.sh.service]# ls
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

会发现已经创建了test.slice 和2.sh.service的目录,其中2.sh.service的目录下记录了cpu的规则

查看该目录下的cfs配置

[root@192 2.sh.service]# cat cpu.cfs_period_us 
100000
[root@192 2.sh.service]# cat cpu.cfs_quota_us 
50000

其中period设置为100000(单位为微秒,及0.1秒),quota设置为50000(单位为微秒,及0.05秒),period代表周期,quota代表在该周期进程占用cpu的时间,这个设置代表在该控制组下所有的进程,在cpu周期0.1秒内,只是用了0.05秒的时间。如果2个cpu,quota可以设置为200000,这样在0.1秒的周期进程使用0.2秒时间,及占用2个cpu

备注:这个cfs的设置是绝对值

2.cpu资源的控制——CPUSHARES(cpu使用权重)

解释:

  • · 假设主机上没有执行其他进程,且加入的进程默认满负荷使用cpu。
  • 主机上有3个进程A,B,C 假设进程A权重为1024(默认),进程B权重512,进程C权重512。
  • 那么A将得到50%cpu时间片,B C分别得到25%的时间片。
  • 如果这个时候在加入一个进程D 权重为1024。
  • 那么A D将占用33%CPU时间片,B C占用16.5%时间片。
  • 针对于多核的系统,CPU时间片是所有cpu核总共时间,及1个进程被设定小于100%的CPU,其也有可能得到单核的100%。

演练(此次系统是2个cpu)

分别临时创建2个cgroup:

[root@192 ~]# systemd-run --unit=1.sh --slice=test sh /root/1.sh
Running as unit 1.sh.service.
[root@192 ~]# systemd-run --unit=2.sh --slice=test sh /root/1.sh
Running as unit 2.sh.service.

然后查看cpu负载:

一文读懂 Docker 资源管理的底层原理与实践

2个服务负载差不多,下载调整1.sh的shares值为512

[root@192 ~]# systemctl set-property 1.sh.service CPUShares=512

一文读懂 Docker 资源管理的底层原理与实践

查看1.sh 2.sh的cgroup的配置

[root@192 test.slice]# pwd
/sys/fs/cgroup/cpu/test.slice
[root@192 test.slice]# cat 1.sh.service/cpu.shares 
512
[root@192 test.slice]# cat 2.sh.service/cpu.shares 
1024

会发现cpu负载还是很平均,但是share值已经设置成512和1024了,着说明在多核中,即使设置的小于100%,也可能会出现负载100%的情况。

现在在加入1个3.sh的服务

[root@192 test.slice]# systemd-run --unit=3.sh --slice=test sh /root/1.sh
Running as unit 3.sh.service.

然后再查看负载

一文读懂 Docker 资源管理的底层原理与实践

会发现其cpu负载 的比例大致上和设置的差不多,这是因为cpushares值是弹性的

使用systemd-cgtop -c 查看各个cg的cpu使用情况。必须在cpu层级下存在systemd.slice 其各个cg才会显示出具体的数值,否则只会显示根的总值。

3.MEM资源控制

内存是无法共享的资源,所以使用最多的及分配多大的内存。

创建一个极度消耗内存的服务。

[root@192 ~]# cat 2.sh 
x=a
while true
do
x=$x$x
echo $x
done

创建一个

[root@192 ~]# systemd-run --unit=mem-test --slice=test sh /root/2.sh
Running as unit mem-test.service.

设置Memory值

[root@192 ~]# systemctl set-property mem-test MemoryLimit=10M

使用systemctl status mem-test查看这个服务所使用的内存

[root@192 ~]# systemctl status mem-test

一文读懂 Docker 资源管理的底层原理与实践

会发现内存已经被限制在10M。

更多docker的实践学习,关注我就告诉你~

相关推荐