Docker资源限制与Cgroups
一、Linux control groups
简介
Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中。
主要提供了如下功能:
- Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
- Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
- Accounting: 一些审计或一些统计,主要目的是为了计费。
- Control: 挂起进程,恢复执行进程。
CGroup的术语
任务(Tasks):就是系统的一个进程。
控制组(Control Group):一组按照某种标准划分的进程,其表示了某进程组,Cgroups中的资源控制都是以控制组为单位实现,一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。以下为内核3.10+支持的子系统(可以通过 ls /sys/fs/cgroup 查看到):
blkio — 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
cpu — 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
cpuacct — 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
cpuset — 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
devices — 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
freezer — 这个子系统挂起或者恢复 cgroup 中的任务。
memory — 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成内存资源使用报告。
net_cls — 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
net_prio — 这个子系统用来设计网络流量的优先级
hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。
操作接口
在linux系统中一皆文件,当然对CGroup的接口操作也是通过文件来实现的,我们可以通过mount命令查看其挂载目录:
以上目录都可以是限制的对象,也就是对应的术语中的各个子系统。例如查看这里的CPU限制目录:
如果你熟悉CPU管理的话,cfs_period 和 cfs_quota两个可能不会陌生,这两个参数需要组合使用,可以用来限制进程在长度为cfs_period 的一段时间内,只能被分配到总量为cfs_quota 的 CPU 时间。以下示例将会演示如何限制。
限制CPU
限制CPU的使用需要在子系统目录(/sys/fs/cgroup/cpu)下创建目录,这里创建一个cpu_limit_demo目录:
可以看到当我们在CPU子系统下创建目录时候,其对应的限制配置文件会自动进行创建,现在在后台写个死循环跑满CPU:
此时使用top命令查看pid为10069的进程CPU使用状况:
如图可见,此时该进程使用的CPU1已经是100%,即%Cpu :100.0 us,我们先来查看我们刚才创建的cpu_limit_demo目录下这两个参数默认值是多少:
cpu.cfs_quota_us值为-1代表没有任何限制,cpu.cfs_period_us 则是默认的 100 ms,即100000us,下面将向cpu_limit_demo控制组的cpu.cfs_quota_us文件写入50ms即50000us,这表示在100ms周期内,cpu最多使用%50,同时将该进程的pid号为10069写入对应的tasks文件,表示对那个进程限制:
于是pid为10069的进程cpu就被限制成了%Cpu :50.0 us,此时利用top在此查看cpu使用情况(top后按1可看到每个逻辑cpu具体使用):
以上可以看到此时pid10069的进程使用率已经变成了50%了,说明限制生效了。同样的道理,在/sys/fs/cgroup下的子系统都可以限制不通的资源使用如Block IO、Memory等。
限制内存
首先在 /sys/fs/cgroup/memory 下新建一个名为 limit_memory_demo 的 cgroup:
mkdir /sys/fs/cgroup/memory/limit_memory_demo
限制该 cgroup 的内存使用量为 500 MB:
# 物理内存500M(下面单位Byte) MB并且不使用swap
echo 524288000 > /sys/fs/cgroup/memory/limit_memory/memory.limit_in_bytes
echo 0 > /sys/fs/cgroup/memory/limit_memory/memory.swappiness
最后将需要限制的进程号的pid写入task文件就可以了:
echo [pid] > /sys/fs/cgroup/memory/limit_memory_demo/tasks
限制磁盘IO
使用dd写到null设备:
[root@app51 blkio]# dd if=/dev/sda of=/dev/null bs=1M
使用iotop(安装使用yum install -y iotop)查看io速率,此时读的速率为2.17G/s
创建一个blkio(块设备IO)的cgroup,并查看设备号,然后将pid和限制的设备以及速度写入文件:
然后在观察dd命令的进程:10178,如图显示该进程读的的速率已经变成了3.8M/s了
注:8:0 是设备号,你可以通过ls -l /dev/sda1查看。
可以简写为以下步骤:
mkdir /sys/fs/cgroup/blkio/ echo '8:0 1048576' > /sys/fs/cgroup/blkio/limit_io/blkio.throttle.read_bps_device
echo [pid] > /sys/fs/cgroup/blkio/limit_io/tasks
二、 Docker中资源限制原理
在了解了Cgroup对资源的限制方法,再来理解Docker中的资源限制其实就变的容易了。默认情况下,Docker会在需要限制的子系统下创建一个目录为docker的控制组如下:
当容器运行后,会在这些目录生成以容器ID为目录的子目录用于限制的容器资源。例如,以cpu为例,docker run启动参数中--cpu-quota 参数可以指定默认时间内使用的cpu:
[root@app51 docker]# docker run -d --name nginx-c1 --cpu-quota 50000 nginx:latest
e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29
[root@app51 docker]#
此时我们查看cpu对应的容器资源限制:
[root@app51 docker]# cd /sys/fs/cgroup/cpu/docker/
[root@app51 docker]# cat e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29/cpu.cfs_quota_us
50000 [root@app51 docker]# cat e9432a513e4bed0a744a29a8eaba2b27d9e40efabfe479d19d32f9558888ed29/tasks
10561
10598
从上面结果我们能看出,docker run的启动限制参数值,被注入到了Cgroups的cpu控制组的对应配置文件中了。同时还有看到有两个进程同时被限制了,这是因为我们启动的nginx,nginx的主进程会启动多个子进程,使用ps -ef可以查看:
[root@app51 docker]# ps -ef |grep 10561
root 10561 10544 0 16:32 ? 00:00:00 nginx: master process nginx -g daemon off; 101 10598 10561 0 16:32 ? 00:00:00 nginx: worker process
root 10614 10179 0 16:38 pts/2 00:00:00 grep --color=auto 10561
[root@app51 docker]#
不难看到,其中主进程为10561(nginx master),子进程为10598(nginx worker)。以上则是docker的资源限制原理。Docker对资源限制主要是CPU和内存,其中还有很多参数可以使用,以下将会介绍docker中CPU和MEMERY限制参数。
三、CPU限制参数
默认情况下,容器的资源是不受限制的,宿主机提供多少资源,容器就可以使用多少资源,如果不对容器做资源限制,很有可能一个或几个容器把宿主机的资源耗尽,而导致应用或者服务不可用。所以对容器资源的限制显得非常重要,而docker主要提供两种类别的资源限制:CPU和内存,通过docker run 时指定参数实现。cpu限制资源限制有多种维度,以下将详细介绍。
限制cpu配额
参数通过--cpu-period=<value>和--cpu-quota=<value>共同起作用,即介绍上述Cgroups使用的例子。表示在cpu-period时间(默认100ms)内,可用的cpu配额。
示例:
docker run -d --cpu-period=100000 --cpu-quota=250000 --name test-c1 nginx:latest
限制cpu可用数量
参数通过--cpus=<value>指定,意思限制可用cpu个数,列如--cpus=2.5表示该容器可使用的cpu个数最多是2.5个,这相当与设置了--cpu-period=100000和 --cpu-quota=250000该指令在docker版本1.13以及以上使用。
示例:
[root@app51 ~]# docker run -d --cpus=2 --name test-c2 nginx:latest
5347269d0974e37af843b303124d8799c6f4336a14f61334d21ce9356b1535bc
使用固定的cpu
通过--cpuset-cpus参数指定,表示指定容器运行在某个或某些个固定的cpu上,多个cpu使用逗号隔开。例如四个cpu,0代表第一个cpu,--cpuset-cpus=1,3代表该容器只能运行在第二个或第四个cpu上。查看cpu可以通过cat /proc/cpuinfo查看。
示例:
[root@app51 ~]# docker run -d --cpuset-cpus=1,3 --name test-c3 nginx:latest
276056fce04982c2de7969ca309560ce60b0ebf960cf7197808616d65aa112d4
设置CPU比例(权重)
通过参数--cpu-shares指定,值为大于或小于1024的整数(默认1024),代表当主机cpu资源使用紧张时,每个容器所使用的cpu资源的比例(权重)。当有足够的CPU资源时,所有容器都会根据需要使用尽可能多的CPU。当第一个容器设置该参数的值为2048,第二个容器设置该参数的值为4096,那么当cpu资源不够用时候,两个容器cpu资源使用比例为1:2,
示例:
[root@app51 ~]# docker run -d --cpu-shares=2048 --name test-c4 nginx:latest
578506d61324b38d7a01bf1d2ec87cb5d1ab50276ef6f7b28858f2d2e78b2860
[root@app51 ~]# docker run -d --cpu-shares=4096 --name test-c5 nginx:latest
d56a90bf080b70d11d112468348874e48fe4a78d09d98813a0377b34fa382924
四、 MEMORY限制参数
内存是一种不可压缩资源,一旦某个进程或者容器中应用内存不足就会引起OOM异常(Out Of Memory Exception),这将导致应用不可用,并且在Linux主机上,如果内核检测到没有足够的内存来执行重要的系统功能,系统会按照一定优先级杀死进程来释放系统内存。docker对内存的限制分为swap限制和物理内存限制,单位可以使用b,k, m,g。
限制最大物理内存使用
通过-m或者—memory指定,是硬限制,如果设置此选项,则允许设置的最小值为4m,该参数是最常用参数。
示例:
[root@app51 ~]# docker run -d --memory=512m --name mem-c1 nginx:latest
67b0cb645c401bc6df3235d27d629185870716351396c71dfa3877abbbd377c8
限制swap使用
通过--memory-swap参数指定,不过此参数设置时候,情况比较较多。当容器中的物理内存使用不足时候,swap才会被允许使用,所以当--memory参数设置时候,此参数才会有意义:
- --memory-swap 设置为正整数,那么这两个--memory和 --memory-swap 必须设置。--memory-swap 表示可以使用的memory 和 swap,并且--memory控制非交换内存(物理内存)使用的量。列如--memory=300m 和--memory-swap=1g,容器可以使用300m的内存和700m(1g - 300m)swap。
- --memory-swap 设置为0,则忽略该设置,并将该值视为未设置。
- --memory-swap 设置为与值相同的值--memory,并且--memory设置为正整数,则容器不能使用swap,可以通过此方法限制容器不使用swap。
- --memory-swap 未设置并--memory 设置,则容器可以使用两倍于--memory设置的swap,主机容器需要配置有swap。例如,如果设置--memory="300m" 和--memory-swap 未设置,容器可以使用300m的内存和600m的swap。
- --memory-swap 设置为-1,则允许容器使用无限制swap,最多可达宿主机系统上可用的数量。
示例:
[root@app51 ~]# docker run -d --memory=512m --memory-swap=512m --name mem-c2 nginx:latest
6b52c015a53be2c3e0e509eea918125a760c1c14df4cc977f05b5b31b83161d5
其他
- --oom-kill-disable :默认情况下,如果发生内存不足(OOM)错误,内核会终止容器中的进程,如要更改此行为,使用该--oom-kill-disable选项,但是该选项有个前提就是-m选项必须设置。
- --memory-swappiness:默认情况下,容器的内核可以交换出一定比例的匿名页。--memory-swappiness就是用来设置这个比例的。--memory-swappiness可以设置为从 0 到 100。0 表示关闭匿名页面交换。100 表示所有的匿名页都可以交换。默认情况下,如果不适用--memory-swappiness,则该值从父进程继承而来。
- --memory-reservation:Memory reservation 是一种软性限制,用于限制物理内存的最大用值,它只是确保容器不会长时间占用超过--memory-reservation限制的内存大小。给--memory-reservation设置一个比-m小的值后,虽然容器最多可以使用-m使用的内存大小,但在宿主机内存资源紧张时,在系统的下次内存回收时,系统会回收容器的部分内存页,强迫容器的内存占用回到--memory-reservation设置的值大小。
五、总结
本文简单介绍了Linux Cgroups以及它对资源限制的使用方法,并提供了简单的示例。此外还介绍了Docker如何使用该方式对容器进行资源限制,本质上是docker run时将限制参数注入到Cgroups的各个资源限制的配置文件中,从而达到对容器的资源限制。希望大家对容器的资源限制有一定的理解。
参考:
《Limit a container's resources》