详解Docker 容器使用 cgroups 限制资源使用
上一篇文章将到Docker容器使用linuxnamespace来隔离其运行环境,使得容器中的进程看起来就像爱一个独立环境中运行一样。但是,光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存等。为了让容器中的进程更加可控,Docker使用Linuxcgroups来限制容器中的进程允许使用的系统资源。
1.基础知识:Linuxcontrolgroups
1.1概念
LinuxCgroup可让您为系统中所运行任务(进程)的用户定义组群分配资源—比如CPU时间、系统内存、网络带宽或者这些资源的组合。您可以监控您配置的cgroup,拒绝cgroup访问某些资源,甚至在运行的系统中动态配置您的cgroup。所以,可以将controllgroups理解为controller(systemresource)(for)(process)groups,也就是是说它以一组进程为目标进行系统资源分配和控制。
它主要提供了如下功能:
- Resourcelimitation:限制资源使用,比如内存使用上限以及文件系统的缓存限制。
- Prioritization:优先级控制,比如:CPU利用和磁盘IO吞吐。
- Accounting:一些审计或一些统计,主要目的是为了计费。
- Control:挂起进程,恢复执行进程。
使用cgroup,系统管理员可更具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和用户分配硬件资源,提高总体效率。
在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):
- 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
- 为这组进程分配其足够使用的内存
- 为这组进程分配相应的网络带宽和磁盘存储限制
- 限制访问某些设备(通过设置设备的白名单)
Linux系统中,一切皆文件。Linux也将cgroups实现成了文件系统,方便用户使用。在我的Ubuntu14.04测试环境中:
root@devstack:/home/sammy#mount-tcgroup cgroupon/sys/fs/cgroup/cpusettypecgroup(rw,relatime,cpuset) cgroupon/sys/fs/cgroup/cputypecgroup(rw,relatime,cpu) systemdon/sys/fs/cgroup/systemdtypecgroup(rw,noexec,nosuid,nodev,none,name=systemd) root@devstack:/home/sammy#lssubsys-m cpuset/sys/fs/cgroup/cpuset cpu/sys/fs/cgroup/cpu cpuacct/sys/fs/cgroup/cpuacct memory/sys/fs/cgroup/memory devices/sys/fs/cgroup/devices freezer/sys/fs/cgroup/freezer blkio/sys/fs/cgroup/blkio perf_event/sys/fs/cgroup/perf_event hugetlb/sys/fs/cgroup/hugetlb root@devstack:/home/sammy#ls/sys/fs/cgroup/-l total0 drwxr-xr-x3rootroot0Sep1821:46blkio drwxr-xr-x3rootroot0Sep1821:46cpu drwxr-xr-x3rootroot0Sep1821:46cpuacct drwxr-xr-x3rootroot0Sep1821:46cpuset drwxr-xr-x3rootroot0Sep1821:46devices drwxr-xr-x3rootroot0Sep1821:46freezer drwxr-xr-x3rootroot0Sep1821:46hugetlb drwxr-xr-x3rootroot0Sep1821:46memory drwxr-xr-x3rootroot0Sep1821:46perf_event drwxr-xr-x3rootroot0Sep1821:46systemd
我们看到/sys/fs/cgroup目录中有若干个子目录,我们可以认为这些都是受cgroups控制的资源以及这些资源的信息。
- 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系统进行限制,这是一个大页文件系统。
默认的话,在Ubuntu系统中,你可能看不到net_cls和net_prio目录,它们需要你手工做mount:
root@devstack:/sys/fs/cgroup#modprobecls_cgroup root@devstack:/sys/fs/cgroup#mkdirnet_cls root@devstack:/sys/fs/cgroup#mount-tcgroup-onet_clsnonenet_cls root@devstack:/sys/fs/cgroup#modprobenetprio_cgroup root@devstack:/sys/fs/cgroup#mkdirnet_prio root@devstack:/sys/fs/cgroup#mount-tcgroup-onet_priononenet_prio root@devstack:/sys/fs/cgroup#lsnet_prio/cgroup.clone_childrencgroup.procsnet_prio.ifpriomapnotify_on_releasetasks cgroup.event_controlcgroup.sane_behaviornet_prio.prioidxrelease_agent root@devstack:/sys/fs/cgroup#lsnet_cls/ cgroup.clone_childrencgroup.event_controlcgroup.procscgroup.sane_behaviornet_cls.classidnotify_on_releaserelease_agenttasks
1.2实验
1.2.1通过cgroups限制进程的CPU
写一段最简单的C程序:
intmain(void) { inti=0; for(;;)i++; return0; }
编译,运行,发现它占用的CPU几乎到了100%:
top-22:43:02up 1:14, 3users, loadaverage:0.24,0.06,0.06 PIDUSER PR NI VIRT RES SHRS%CPU%MEM TIME+COMMAND
2304root 20 0 4188 356 276R99.6 0.0 0:11.77hello
接下来我们做如下操作:
root@devstack:/home/sammy/c#mkdir/sys/fs/cgroup/cpu/hello root@devstack:/home/sammy/c#cd/sys/fs/cgroup/cpu/hello root@devstack:/sys/fs/cgroup/cpu/hello#ls cgroup.clone_childrencgroup.procscpu.cfs_quota_uscpu.stattasks cgroup.event_controlcpu.cfs_period_uscpu.sharesnotify_on_release root@devstack:/sys/fs/cgroup/cpu/hello#catcpu.cfs_quota_us -1 root@devstack:/sys/fs/cgroup/cpu/hello#echo20000>cpu.cfs_quota_us root@devstack:/sys/fs/cgroup/cpu/hello#catcpu.cfs_quota_us 20000 root@devstack:/sys/fs/cgroup/cpu/hello#echo2428>tasks
然后再来看看这个进程的CPU占用情况:
PIDUSER PR NI VIRT RES SHRS%CPU%MEM TIME+COMMAND
2428root 20 0 4188 356 276R19.9 0.0 0:46.03hello
它占用的CPU几乎就是20%,也就是我们预设的阈值。这说明我们通过上面的步骤,成功地将这个进程运行所占用的CPU资源限制在某个阈值之内了。
如果此时再启动另一个hello进程并将其id加入tasks文件,则两个进程会共享设定的CPU限制:
PIDUSER PR NI VIRT RES SHRS%CPU%MEM TIME+COMMAND
2428root 20 0 4188 356 276R10.0 0.0285:39.54hello
12526root 20 0 4188 356 276R10.0 0.0 0:25.09hello
1.2.2通过cgroups限制进程的Memory
同样地,我们针对它占用的内存做如下操作:
root@devstack:/sys/fs/cgroup/memory#mkdirhello root@devstack:/sys/fs/cgroup/memory#cdhello/ root@devstack:/sys/fs/cgroup/memory/hello#catmemory.limit_in_bytes 18446744073709551615 root@devstack:/sys/fs/cgroup/memory/hello#echo64k>memory.limit_in_bytes root@devstack:/sys/fs/cgroup/memory/hello#echo2428>tasks root@devstack:/sys/fs/cgroup/memory/hello#
上面的步骤会把进程2428说占用的内存阈值设置为64K。超过的话,它会被杀掉。
1.2.3限制进程的I/O
运行命令:
sudoddif=/dev/sda1of=/dev/null
通过iotop命令看IO(此时磁盘在快速转动),此时其写速度为242M/s:
TID PRIO USER DISKREAD DISKWRITE SWAPIN IO> COMMAND
2555be/4root 242.60M/s 0.00B/s 0.00%61.66%ddif=/dev/sda1of=/dev/null
接着做下面的操作:
root@devstack:/home/sammy#mkdir/sys/fs/cgroup/blkio/io root@devstack:/home/sammy#cd/sys/fs/cgroup/blkio/io root@devstack:/sys/fs/cgroup/blkio/io#ls-l/dev/sda1 brw-rw----1rootdisk8,1Sep1821:46/dev/sda1 root@devstack:/sys/fs/cgroup/blkio/io#echo'8:01048576'>/sys/fs/cgroup/blkio/io/blkio.throttle.read_bps_device root@devstack:/sys/fs/cgroup/blkio/io#echo2725>/sys/fs/cgroup/blkio/io/tasks
结果,这个进程的IO速度就被限制在1Mb/s之内了:
TID PRIO USER DISKREAD DISKWRITE SWAPIN IO> COMMAND
2555be/4root 990.44K/s 0.00B/s 0.00%96.29%ddif=/dev/sda1of=/dev/null
1.3术语
cgroups的术语包括:
- 任务(Tasks):就是系统的一个进程。
- 控制组(ControlGroup):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的hello一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
- 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
- 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。
2.Docker对cgroups的使用
2.1默认情况
默认情况下,Docker启动一个容器后,会在/sys/fs/cgroup目录下的各个资源目录下生成以容器ID为名字的目录(group),比如:
/sys/fs/cgroup/cpu/docker/03dd196f415276375f754d51ce29b418b170bd92d88c5e420d6901c32f93dc14
此时cpu.cfs_quota_us的内容为-1,表示默认情况下并没有限制容器的CPU使用。在容器被stopped后,该目录被删除。
运行命令dockerrun-d--nameweb41--cpu-quota25000--cpu-period100--cpu-shares30training/webapppythonapp.py启动一个新的容器,结果:
root@devstack:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24#catcpu.cfs_quota_us 25000 root@devstack:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24#cattasks 3704 root@devstack:/sys/fs/cgroup/cpu/docker/06bd180cd340f8288c18e8f0e01ade66d066058dd053ef46161eb682ab69ec24#catcpu.cfs_period_us 2000
Docker会将容器中的进程的ID加入到各个资源对应的tasks文件中。表示Docker也是以上面的机制来使用cgroups对容器的CPU使用进行限制。
相似地,可以通过dockerrun中mem相关的参数对容器的内存使用进行限制:
--cpuset-memsstringMEMsinwhichtoallowexecution(0-3,0,1) --kernel-memorystringKernelmemorylimit -m,--memorystringMemorylimit --memory-reservationstringMemorysoftlimit --memory-swapstringSwaplimitequaltomemoryplusswap:'-1'toenableunlimitedswap --memory-swappinessintTunecontainermemoryswappiness(0to100)(default-1)
比如 dockerrun-d--nameweb42--blkio-weight100--memory10M--cpu-quota25000--cpu-period2000--cpu-shares30training/webapppythonapp.py:
root@devstack:/sys/fs/cgroup/memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410#catmemory.limit_in_bytes 10485760 root@devstack:/sys/fs/cgroup/blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410#catblkio.weight 100
目前docker已经几乎支持了所有的cgroups资源,可以限制容器对包括network,device,cpu和memory在内的资源的使用,比如:
root@devstack:/sys/fs/cgroup#find-inameec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./net_prio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./net_cls/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./systemd/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./hugetlb/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./perf_event/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./blkio/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./freezer/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./devices/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./memory/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpuacct/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpu/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410 ./cpuset/docker/ec8d850ebbabaf24df572cb5acd89a6e7a953fe5aa5d3c6a69c4532f92b57410
2.2net_cls
net_cls和tc一起使用可用于限制进程发出的网络包所使用的网络带宽。当使用cgroupsnetworkcontrollnet_cls后,指定进程发出的所有网络包都会被加一个tag,然后就可以使用其他工具比如iptables或者trafficcontroller(TC)来根据网络包上的tag进行流量控制。关于TC的文档,网上很多,这里不再赘述,只是用一个简单的例子来加以说明。
关于classid,它的格式是0xAAAABBBB,其中,AAAA是十六进制的主ID(majornumber),BBBB是十六进制的次ID(minornumber)。因此,0X10001表示10:1,而0x00010001表示1:!。
(1)首先在host的网卡eth0上做如下设置:
tcqdiscdeldeveth0root#删除已有的规则 tcqdiscadddeveth0roothandle10:htbdefault12 tcclassadddeveth0parent10:classid10:1htbrate1500kbitceil1500kbitburst10k#限速 tcfilteradddeveth0protocolipparent10:0prio1u32matchipprotocol10xffflowid10:1#只处理ping参数的网络包
其结果是:
- 在网卡eth0上创建了一个HTBroot队列,hangle10:表示队列句柄也就是majornumber为10
- 创建一个分类10:1,限制它的出发网络带宽为80kbit(千比特每秒)
- 创建一个分类器,将eth0上IPIMCP协议的majorID为10的prio为1的网络流量都分类到10:1类别
(2)启动容器
容器启动后,其init进程在host上的PID就被加入到tasks文件中了:
root@devstack:/sys/fs/cgroup/net_cls/docker/ff8d9715b7e11a5a69446ff1e3fde3770078e32a7d8f7c1cb35d51c75768fe33#ps-ef|grep10047 2310721004710013107:08?00:00:00pythonapp.py
设置net_clsclassid:
echo0x100001>net_cls.classid
再在容器启动一个ping进程,其ID也被加入到tasks文件中了。
(3)查看tc情况:tc-s-dclassshowdeveth0
Every2.0s:tc-sclasslsdeveth0WedSep2104:07:562016 classhtb10:1rootprio0rate1500Kbitceil1500Kbitburst10Kbcburst1599b Sent17836bytes182pkt(dropped0,overlimits0requeues0) rate0bit0ppsbacklog0b0prequeues0 lended:182borrowed:0giants:0 tokens:845161ctokens:125161
我们可以看到tc已经在处理ping进程产生的数据包了。再来看一下net_cls和ts合作的限速效果:
10488bytesfrom192.168.1.1:icmp_seq=35ttl=63time=12.7ms
10488bytesfrom192.168.1.1:icmp_seq=36ttl=63time=15.2ms
10488bytesfrom192.168.1.1:icmp_seq=37ttl=63time=4805ms
10488bytesfrom192.168.1.1:icmp_seq=38ttl=63time=9543ms
其中:
后两条说使用的tcclass规则是tcclassadddeveth0parent10:classid10:1htbrate1500kbitceil15kbitburst10k
前两条所使用的tcclass规则是tcclassadddeveth0parent10:classid10:1htbrate1500kbitceil10Mbitburst10k
3.Dockerrun命令中cgroups相关命令
blockIO: --blkio-weightvalueBlockIO(relativeweight),between10and1000 --blkio-weight-devicevalueBlockIOweight(relativedeviceweight)(default[]) --cgroup-parentstringOptionalparentcgroupforthecontainer CPU: --cpu-percentintCPUpercent(Windowsonly) --cpu-periodintLimitCPUCFS(CompletelyFairScheduler)period --cpu-quotaintLimitCPUCFS(CompletelyFairScheduler)quota -c,--cpu-sharesintCPUshares(relativeweight) --cpuset-cpusstringCPUsinwhichtoallowexecution(0-3,0,1) --cpuset-memsstringMEMsinwhichtoallowexecution(0-3,0,1) Device: --devicevalueAddahostdevicetothecontainer(default[]) --device-read-bpsvalueLimitreadrate(bytespersecond)fromadevice(default[]) --device-read-iopsvalueLimitreadrate(IOpersecond)fromadevice(default[]) --device-write-bpsvalueLimitwriterate(bytespersecond)toadevice(default[]) --device-write-iopsvalueLimitwriterate(IOpersecond)toadevice(default[]) Memory: --kernel-memorystringKernelmemorylimit -m,--memorystringMemorylimit --memory-reservationstringMemorysoftlimit --memory-swapstringSwaplimitequaltomemoryplusswap:'-1'toenableunlimitedswap --memory-swappinessintTunecontainermemoryswappiness(0to100)(default-1)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。