一文读懂 Docker 资源管理的底层原理与实践
一、对资源管理及 Cgroup 的认识
在Linux系统使用中有一个需求经常被提到“希望能限制某个或者某一些进程的资源分配”。这时候就出现了一个新的名词Cgroups。
Cgroups是Control Groups的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:cpu,memory,IO等等)的机制,对进程分层分组,并通过特殊的文件系统公开。
最初由Google的工程师提出,后来被整合进Linux内核中。
二、Docker-Cgroup原理与机制
2.1 原理与作用
Docker使用Namespace 完成了环境的隔离。但是对于容器来说,所有容器可以共享宿主机的全部 CPU 和内存及 IO 资源,要完成这些资源的隔离,Docker需要借助于Cgroup。Cgroups中的重要概念是”子系统”,也就是资源控制器,每种子系统就是一个资源的分配器,比如CPU子系统是控制CPU时间分配的。
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子系统名称与用途包括:
子系统的文档:
使用命令 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
其中user.slice下的2个scope为用户登陆的tty,中间的数字代表用户的uid。
service.slice下代表系统的所有服务。
整个层级结构也可以在/sys/fs/cgroup/systemd下观察到。
2.5 图解层级与子系统的关系
- 系统中第一个被创建的cgroup被称为root Cgroup,该Cgroup的成员包含系统中所有的进程
- 一个子系统可以位于多个层级中。但是有限制条件,当且仅当这些层级没有其他的子系统,比如2个Hierarchies可以同时只具有一个CPU子系统
- 每个层级中可以关联多个子系统
- 一个进程可以位于不同层级的Cgroup中
- 一个进程创建了子进程后,该子进程默认为父进程所在Cgroup的成员,此子进程可以手动更换Cgroup组
三、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负载
发现2.sh 这个服务已经跑满100%了
使用systemctl设置cpu的quota值
[root@192 ~]# systemctl set-property 2.sh.service CPUQuota=50%
然后在查看top值
会发现该进程已经降低为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负载:
2个服务负载差不多,下载调整1.sh的shares值为512
[root@192 ~]# systemctl set-property 1.sh.service CPUShares=512
查看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.
然后再查看负载
会发现其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
会发现内存已经被限制在10M。
更多docker的实践学习,关注我就告诉你~