Docker容器基础3:Cgroup - cpu, cpuacct, cpuset子系统
测试环境
Ubuntu 18.04,内核版本4.15,机器拥有4核。
1 | [~]$ cat /proc/version |
CPU相关子系统简介
有关CPU的cgroup subsystem有3个:
- cpu : 用来限制cgroup的CPU使用率
- cpuacct : 用来统计cgroup的CPU的使用率
- cpuset : 用来绑定cgroup到指定CPU的哪个核上和NUMA节点
每个子系统都有多个配置项和指标文件,主要介绍下图常用的配置项:
cpu
cpu子系统用来限制cgroup如何使用CPU的时间,也就是调度,它提供了3种调度办法,并且这3种调度办法都可以在启动容器时进行配置,分别是:
- share :相对权重的CPU调度
- cfs :完全公平调度
- rt :实时调度
share调度的配置项和原理如下:
cfs 是Completely Fair Scheduler的缩写,代表完全公平调度,它利用 cpu.cfs_quota_us
和 cpu.cfs_period_us
实现公平调度,这两个文件内容组合使用可以限制进程在长度为 cfs_period_us
的时间内,只能被分配到总量为 cfs_quota_us
的 CPU 时间。CFS的指标如下:
注意:
cfs_period_us
取值范围1000~1000000:1ms ~ 1s,cfs_quota_us
的最小值为1000,当设置的值不在取值范围时,会报write xxx: invalid argument
的错误。- 只有这2个参数都有意义时,才能把任务写入到 tasks 文件。
rt 是RealTime的缩写,它是实时调度,它与cfs调度的区别是cfs不会保证进程的CPU使用率一定要达到设置的比率,而rt会严格保证,让进程的占用率达到这个比率,适合实时性较强的任务,它包含 cpu.rt_period_us
和 cpu.rt_runtime_us
2个配置项。
cpuacct
cpuacct包含非常多的统计指标,常用的有以下4个文件:
cpuset
为啥需要cpuset?
比如:
- 多核可以提高并发、并行,但是核太多了,会影响进程执行的局部性,降低效率。
- 一个服务器上部署多种应用,不同的应用不同的核。
cpuset也包含居多的配置项,主要是分为cpu和mem 2类,mem与NUMA有关,其常用的配置项如下图:
利用Docker演示Cgroup CPU限制
cpu
不限制cpu的情况
stress为基于ubuntu:16.04安装stress做出来的镜像,利用stress来测试cpu限制。
Dockerfile如下:
1 | From ubuntu:16.04 |
启动容器不做任何cpu限制,利用 stress -c 2
开启另外2个stress线程,共3个:
1 | [/sys/fs/cgroup/cpu]$ docker run --rm -it stress:16.04 |
在cgroup/cpu,cpuacct
下,找到该容器对应的目录,查看 cfs_period_us
和 cfs_quota_us
的默认值:
1 | [/sys/fs/cgroup/cpu,cpuacct/system.slice/docker-5fad38726740b90b93c06972fe4a9f11391a38aaeb3e922f10c3269fa32e1873.scope]$ cat cpu.cfs_period_us |
查看主机CPU利用率,为3个stress进程,每1个都100%,它们属于同一个cgroup:
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
限制cpu的情况
--cpu-quota
设置5000,开stress分配到另外2个核。
[/sys/fs/cgroup/cpu]$ docker run –rm -it –cpu-quota=5000 stress:16.04
root@7e79005d7ca1:/#
root@7e79005d7ca1:/# stress -c 2
stress: info: [13] dispatching hogs: 2 cpu, 0 io, 1 vm, 0 hdd
查看 cfs_period_us
和 cfs_quota_us
的设置,5000/100000 = 5%
, 即限制该容器的CPU使用率不得超过5%。
1 | [/sys/fs/cgroup/cpu,cpuacct/system.slice/docker-7e79005d7ca1b338d870d3dc79af3f1cd38ace195ebd685a09575f6acee36a07.scope]$ cat cpu.cfs_quota_us |
利用top可以看到3个进程总cpu使用率5.1%。
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
cpuacct
查看cpuacct.stat, cpuacct.usage, cpuacct.usage_percpu
,一定要同时输出这几个文件,不然可能有时间差,利用python可以计算每个核上的时间之和为usage
,即该容器占用的cpu总时间。
1 | [/sys/fs/cgroup/cpu,cpuacct]$ cat cpuacct.* |
cpuset
启动容器,然后使用stress占用1个核:
1 | [/sys/fs/cgroup/cpu]$ docker run --rm -it stress:16.04 |
top显示占用100%CPU。
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
cpuset 能看到可使用的核为: 0~3。
1 | [/sys/fs/cgroup/cpuset/docker/a907df624697a19631929c1e9e971d2893afddbf6befb0dd44be3cf0024a3e0d]$ cat cpuset.cpus |
使用cpuacct查看CPU情况使用统计,可以看到用了4个核上的使用时间。
1 | [/sys/fs/cgroup/cpu/docker/a907df624697a19631929c1e9e971d2893afddbf6befb0dd44be3cf0024a3e0d]$ cat cpuacct.usage cpuacct.usage_all |
现在创建一个新容器,限制只能用1,3这2个核:
1 | [/sys/fs/cgroup/cpu]$ docker run --rm -it --cpuset-cpus 1,3 stress:16.04 |
查看可以使用的核:
1 | [/sys/fs/cgroup/cpuset/docker/0ce61a38e7c9621334871ab40d5b7d287d89a1e994148833ddf3ca4941a39c89]$ cat cpuset.cpus |
cpuacct.usage_all
显示只有1、3两个核的数据在使用:
1 | [/sys/fs/cgroup/cpu/docker/0ce61a38e7c9621334871ab40d5b7d287d89a1e994148833ddf3ca4941a39c89]$ cat cpuacct.usage_all |
现在切换到root账号,把 sched_load_balance
标记设置为0,不进行核间的负载均衡,然后利用 cpuacct.usage_all
查看每个核上的时间,隔几秒前后查询2次,可以发现3号核的cpu使用时间停留在21332956940
,而核1的cpu使用时间从185084024837
增加到 221479683602
, 说明设置之后stress线程一致在核1上运行,不再进行负载均衡。
1 | [/sys/fs/cgroup/cpuset/docker/0ce61a38e7c9621334871ab40d5b7d287d89a1e994148833ddf3ca4941a39c89]$ echo 0 > cpuset.sched_load_balance |
利用Go演示Cgroup CPU限制
测试程序:02.2.cgroup_cpu.go 。
该程序接受1个入参,代表测试类型:
- 空或
nolimit
: 无限制 cpu
: 执行cpu限制,利用cfs把cpu使用率控制在5%cpuset
: 限制只使用核1和核3
测试程序的执行动作如下:
- 程序首先在cpu和cpuset中创建2个cgroup,
- 按传入的参数设置限制或不设置限制
- 利用
/proc/self/exe
启动进程 - 把进程加入到2个cgroup的tasks,即加入cgroup
- 进程会创建3个goroutine不断的去消耗cpu,它们会占用3个线程
当CPU使用率不限制时,3个线程会分配到3个核上执行,所以进程的CPU使用率应当达到300%。
利用测试程序分3组实验,然后利用 top
、cpuacct.usage_all
、cpuset.cpu
3个角度查看CPU限制和使用情况。
不限制CPU
- 启动测试程序,进程id为4805,进入Namespace后进程id变为1,可以看到启动了3个worker协程。
1 | [/home/ubuntu/workspace/notes/docker/codes]$ go run 02.2.cgroup_cpu.go |
- top查看进程的CPU占用率为300%,符合预期。
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
- 利用cpuacct查看每个核上的使用时间:
1 | [/sys/fs/cgroup/cpuacct]$ cat test_cpu_limit/cpuacct.usage_all |
- 利用cpuset.cpus查看使用的cpu核:
1 | [/sys/fs/cgroup/cpuset]$ cat test_cpuset_limit/cpuset.cpus |
使用cpu限制CPU使用率
- 启动测试程序:
1 | [/home/ubuntu/workspace/notes/docker/codes]$ go run 02.2.cgroup_cpu.go cpu |
- top查看进程的CPU占用率为5.0%,符合预期。
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
- 利用cpuacct查看每个核上的使用时间,由于没有限制使用的cpu核,所以每个核上都还有运行时间
1 | [/sys/fs/cgroup/cpuacct]$ cat test_cpu_limit/cpuacct.usage_all |
- 利用cpuset.cpus查看使用的cpu核
1 | [/sys/fs/cgroup/cpuset]$ cat test_cpuset_limit/cpuset.cpus |
使用cpuset限制CPU占用的核
- 启动测试程序,这次与前面的不同,看到只起来了2个worker协程在运行,因为机器上的Go版本是go1.10,还不支持抢占,当协程为for循环时,2个协程都持续运行,不让出cpu,只有2个核时,第3个协程无法运行。
1 | [/home/ubuntu/workspace/notes/docker/codes]$ go run 02.2.cgroup_cpu.go cpuset |
- top查看进程的CPU占用率为200%,符合只使用2个核的预期。
1 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND |
- 利用cpuacct查看每个核上的使用时间,只有核1和3上有时间统计,说明只使用了核1和3
1 | [/sys/fs/cgroup/cpuacct]$ cat test_cpu_limit/cpuacct.usage_all |
- 利用cpuset.cpus查看使用的cpu核
1 | [/sys/fs/cgroup/cpuset]$ cat test_cpuset_limit/cpuset.cpus |