一次搞明白 Docker 容器资源限制

前言

在使用容器时(未被Kubernetes进行管理的情况下),我们单台主机上可能会跑几十个容器,容器虽然都相互隔离,但是用的却是与宿主机相同的内核,CPU、内存、磁盘等硬件资源。注:容器没有内核。默认情况下,容器没有资源限制,可以使用主机内核调度程序允许的尽可能多的给定资源;如果不对容器资源进行限制,容器之间就会相互影响,一些占用硬件资源较高的容器会吞噬掉所有的硬件资源,从而导致其它容器无硬件资源可用,发生停服状态。Docker提供了限制内存,CPU或磁盘IO的方法, 可以对容器所占用的硬件资源大小以及多少进行限制,我们在使用docker create创建一个容器或者docker run运行一个容器的时候就可以来对此容器的硬件资源做限制。

Docker核心

Docker分别使用Namespace和CGroup实现了对容器的资源隔离和资源限制,本文将会讲到怎么使用内核来调用CGroup对容器资源做限制。

OOM介绍

out of memorty
OOM:out of memorty的简称,称之为内存溢出

1.如果内存耗尽,内存将无法给予内存空间,内核检测到没有足够的内存来执行重要的系统功能,它会抛出OOM或Out of Memory异常,内存将会溢出,随之会有选择性的杀死相应的进程。
2.内存属于不可压缩性资源,如果执行这个进程的内存不够使用,这个进程它就会一直申请内存资源,直到内存溢出。
3.CPU属于可压缩性资源,所以CPU并不会出现这种情况,例如一个进程占用了一个核心100%的CPU,那么原本这个进程是需要占用200%的CPU资源,如果其它进程所占用的CPU核心并不需要多高的CPU频率,那么此进程就会占用掉空闲的CPU,如果其它进程也需要他自己核心的CPU频率,那么此进程就只能使用对它自己所使用CPU核心100%,因此叫做可压缩。
4.内存的有选择性:为什么不是杀死占用内存最高的进程呢?举个例子:例如我们运行了一个MySQL和一个Tomcat;这个MySQL原本是需要占用2G的内存资源,但他占用了1.9G;而Tomcat原本是需要占用500M的内存空间,可他占用了1G内存空间,这个时候当内存报异常OOM的时候,就会选择Tomcat进程进行发送kill -9的信号,进行杀死以释放内存。
5.当我们的一个重要的进程占用的内存超标而导致内存OOM的情况下,我们不希望内核来Kill掉我们这个进程,怎么办?我们可以调整内存OOM后kill进程的优先级,优先级越高越优先杀死,则反之 为此,Docker特地调整了docker daemon的OOM优先级,以免它被内核的杀死,但容器的优先级并未被调整
导致内存OOM
1.加载对象过大;
2.相应资源过多,来不及加载;
3.应用运行时间较长未重启,从而一点一点占用内存资源,长期积累导致占用内存空间较多;
4.代码存在内存泄漏bug。
解决OOM办法
1.内存引用上做一些处理,常用的有软引用;
2.内存中加载图片直接在内存中做处理,(如边界压缩);
3.动态回收内存;
4.优化Delivk虚拟机的堆内存分配;
5.自定义堆内存大小;
6.定期重启应用以释放内存。
压测工具stress

下载stress

docker pull lorel/docker-stress-ng:latest
latest:Pullingfrom lorel/docker-stress-ng
c52e3ed763ff:Pull complete
a3ed95caeb02:Pull complete
7f831269c70e:Pull complete
Digest: sha256:c8776b750869e274b340f8e8eb9a7d8fb2472edd5b25ff5b7d55728bca681322
Status:Downloaded newer image for lorel/docker-stress-ng:latest

使用方法

docker run --name stress -it --rm lorel/docker-stress-ng:latest stress --help
–name 指定lorel/docker-stress-ng:latest所启动的测试得容器名称为stress
–it:打开一个伪终端,并提供交互式
–rm:容器停止即删除
lorel/docker-stress-ng:latest:压测stress工具镜像名称
stress:lorel/docker-stress-ng:latest镜像内所内置的命令,必须使用此命令来指定–help支持的选项

stress常用选项

–cpu N:启动几个子进程来做压测,默认一个进程使用一个CPU核心,选项可简写为-c N
docker run --name stress -it --rm lorel/docker-stress-ng:latest stress --help | grep “cpu N”
-c N,–cpu N start N workers spinning on sqrt(rand())

–vm N:启动几个进程来做匿名页压测,选项可简写为-m N
docker run --name stress -it --rm lorel/docker-stress-ng:latest stress --help | grep “vm N”
-m N,–vm N start N workers spinning on anonymous mmap

–vm-bytes N:为–vm N指定的进程提供内存分配,每个进程可以分配到的内存数量,默认为256M
docker run --name stress -it --rm lorel/docker-stress-ng:latest stress --help | grep “vm-bytes N”
–vm-bytes N allocate N bytes per vm worker (default256MB)

Docker内存限制

限制内存注意事项
1.为需要限制容器内的应用提前做好压测,例如Nginx容器自身所占内存空间,以及预算业务量大小所需占用的内存空间,进行压测后,才能进入生产环境使用;
2.保证宿主机内存资源充足,监控及时上报容器内的内存使用情况,一旦容器所占用的内存不足,立刻对容器内存限制做调整或者打包此容器为镜像到其它内存充足的机器上进行启动;
3.如果物理内存充足,尽量不要使用swap交换内存,swap会导致内存计算复杂。
设置内存选项
注意:可限制的内存单位:b、k、m、g;分别对应bytes、KB、MB、GB

-m or --memory=:容器能使用的最大内存大小,最小值为4M
–memory-swap=:容器能够使用swap内存的大小,使用—memory-swap选项必须要使用—memory选项,否则—memory-swap不生效
–memory-swappiness:默认情况下,主机可以把容器使用的匿名页swap出来,你可以设置一个0-100之间的值,代表swap出来的比例,如果此值设置为0,容器就会先使用物理内存,能不用就不用swap空间,如果设置为100,则反之,能用swap就会用,哪怕有一丝可以用到swap空间的可能性就会使用swap空间
–memory-reservation:预留的一个内存空间,设置一个内存使用soft limit,如果docker发现主机内存不足,会执行OOM操作,这个值必须小于—memory设置的值
–kernel-memory:容器能够使用kernel memory大小,最小值为4M
–oom-kill-disable:是否运行OOM的时候杀死容器,只有设置了-m或者-memory,才可以设置此值,值为flase或者true,设置为false之后当此容器的内存溢出的时候就会对此容器执行kill操作,否则容器会耗尽主机内存,而且导致主机应用被杀死,如果这个容器运行的应用非常重要,就把—oom-kill-disable设置为true,就是禁止被oom杀掉
–memory-swap详解: swap:交换内存 ram:物理内存

图片

查看内存大小:

图片

限制容器内存
使用docker的–memory选项来限制容器能够使用物理内存的大小,使用stress命令的选项–vm指定启动几个占用内存的进程和每个占用内存进程所占用的内存空间大小 我们指定了容器最多使用物理内存512M,启动两个占用内存的进程,每个进程分别占用512M的空间,那么两个进程理论上需要占用1024的空间,我们只给了1024的空间,显然是不够的:

docker run --name stress-memory -it --rm -m 512M lorel/docker-stress-ng:latest stress --vm 2–vm-bytes 512M
stress-ng: info:[1] defaulting to a 86400 second run per stressor
stress-ng: info:[1] dispatching hogs:2 vm

使用docker stats命令来查看容器硬件资源的使用情况 可以看到我们的stress-memory容器的总内存为512M,使用了500多点,但为超过521M,内存占用的百分比为99.3%:

图片

使用htop命令来查看资源情况:

图片

限制容器swap内存
设置oom时是否杀掉进程
Docker CPU限制

查看CPU核心数以及编码:

图片

设置CPU选项
–cpu-shares:共享式CPU资源,是按比例切分CPU资源;比如当前系统上一共运行了两个容器,第一个容器上权重是1024,第二个容器权重是512, 第二个容器启动之后没有运行任何进程,自己身上的512都没有用完,而第一台容器的进程有很多,这个时候它完全可以占用容器二的CPU空闲资源,这就是共享式CPU资源;如果容器二也跑了进程,那么就会把自己的512给要回来,按照正常权重1024:512划分,为自己的进程提供CPU资源。如果容器二不用CPU资源,那容器一就能够给容器二的CPU资源所占用,如果容器二也需要CPU资源,那么就按照比例划分,这就是CPU共享式,也证明了CPU为可压缩性资源。
–cpus:限制容器运行的核数;从docker1.13版本之后,docker提供了–cpus参数可以限定容器能使用的CPU核数。这个功能可以让我们更精确地设置容器CPU使用量,是一种更容易理解也常用的手段。
-cpuset-cpus:限制容器运行在指定的CPU核心;运行容器运行在哪个CPU核心上,例如主机有4个CPU核心,CPU核心标识为0-3,我启动一台容器,只想让这台容器运行在标识0和3的两个CPU核心上,可以使用cpuset来指定。
限制CPU Share
启动stress压测工具,并使用stress命令加–cpu选项来启动四个进程,默认一个进程占用一颗CPU核心:

docker run --name stress-share -it --rm --cpu-shares 512 lorel/docker-stress-ng:latest stress -c 2
stress-ng: info:[1] defaulting to a 86400 second run per stressor
stress-ng: info:[1] dispatching hogs:2 cpu

压测工具会吃掉俩个核心的所有CPU资源 再次打开一个窗口,使用htop命令来查看硬件资源损耗情况 我们一共开了两个进程,默认会占用两个cpu核心,每个核心的资源为100%,两个也就是为200%,由于没有指定限制在某个CPU核心上,所以它是动态的跑在四核CPU核心数,但是stress占用CPU的资源不会超出200%:

图片

再次打开一个窗口,使用docker top container也可以查看到两个进程一共消耗了多少cpu资源:

图片

限制CPU核数
我们使用docker的–cpus选项来限制cpu运行的核心数,使用stress命令的选项–cpu来限制启动的进程数 显示cpu只运行两个核心数,也就是只能运行200%CPU资源,启动4个进程,也就是讲这4个进程只能跑在200%的cpu资源上:

docker run --name stress-cpus -it --rm --cpus 2 lorel/docker-stress-ng:latest stress -c 4
stress-ng: info:[1] defaulting to a 86400 second run per stressor
stress-ng: info:[1] dispatching hogs:4 cpu

使用htop命令查看cpu占用资源 可以看到四个进程,但是跑在了四颗cpu上面,但是每颗cpu只运行了50%左右的资源,4*50也就是200%左右的cpu资源:

图片

使用docker top container也可以查看到四个进程一共消耗了多少cpu资源:

图片

限制容器运行在指定核心
我们使用docker的–cpuset-cpus选项来指定cpu运行在哪个核心上,使用stress的选项–cpu来指定启动的进程数 我们指定4个进程运行在编号为0和2的cpu上:

docker run --name stress-cpuset -it --rm --cpuset-cpus=0,2 lorel/docker-stress-ng:latest stress -c 4
stress-ng: info:[1] defaulting to a 86400 second run per stressor
stress-ng: info:[1] dispatching hogs:4 cpu

使用htop查看系统硬件占用资源 可以看到和预期一样,占用了第一颗和第三颗cpu,每个进程占用cpu资源为50%,总资源为200%,两颗cpu的量:

图片

作者:Wayne
来源:https://zhuanlan.zhihu.com/p/162699218

上一篇:4 系统的 CPU 使用率很高,但为啥却找不到高 CPU的应用?


下一篇:英语-外刊《坐高铁上下班,“打工人”不容易》