默认情况下,一个容器并没有资源限制,并且该容器可以使用内核调度的所有资源。Docke提供了在启动容器时设置一些参数来控制该容器使用的内存、CPU和block IO。
真正可以控制的只有内存和CPU。。
内存
内存是不可压缩资源
OOME
在Linxu系统中,如果内核探测到宿主机没有足够的内存来调用执行系统的某些重要功能的时候,那么会抛出一个OOME(Out Of Memory Exception:内存异常)杀死某些进程,以此来释放内存。
一旦发生OOME,任何进程都有可能被杀死,包括docker daemon在内。为此,Docker特地调整了docker daemon的OOM优先级,防止其被杀死,但是容器的优先级没有被调整。在内存不足时,内核会根据自己的调度算法,为每个进程得出一个评分,然后杀死分数最高的进程来释放内存。
可以在docker run的时候指定--oom-score-adj参数(默认为0)。这个是容器被杀死的优先级,会影响评分,值越大越容易被杀死。这个参数只是影响最终的评分,优先杀死进程是看评分,参数小的可能计算后分数仍然最高,依然会被优先杀掉。
可以指定–oom-kill-disable=true参数,指定一些特定的重要的容器禁止被OOM杀掉。
--oom-kill-disable Disable OOM Killer
--oom-score-adj int Tune host's OOM preferences (-1000 to 1000)
内存限制
选项 |
描述 |
-m,--memory |
内存限制,格式是数字加单位,单位可以为 b,k,m,g。最小为4M |
--memory-swap |
内存+交换分区大小总限制。格式同上。必须比-m设置的大 |
--memory-swappiness |
默认情况下,主机可以把容器使用的匿名页(anonymous page)swap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例 |
--memory-reservation |
设置一个内存使用的 soft limit,如果 docker 发现主机内存不足,会执行 OOM 操作。这个值必须小于 --memory 设置的值 |
--kernel-memory |
容器能够使用的 kernel memory 大小,最小值为 4m |
--oom-kill-disable |
是否运行 OOM 的时候杀死容器。只有设置了 -m,才可以把这个选项设置为 false,否则容器会耗尽主机内存,而且导致主机应用被杀死 |
--memory-swap参数
这个参数要结合-m一起生效,表格中的描述比较简单,是一般的用法。
一般用法:比-m大,内存+交换分区大小总限制
禁用swap:和-m一样大,这样--memory和--memory-swap的限制是一样大,可用的swap资源为0,相当于禁用。
默认设置:设置为0或者不设置,如果主机(Docker Host)启用了swap,则容器可用的swap为内存限制的2倍。
无限制:设置为-1,如果主机(Docker Host)启用了swap,则容器可使用宿主机所有的swap资源。
在容器内使用free命令,看到的swap空间没有体现出上面这些限制,没有参考价值。
CPU
CPU是可压缩资源。
默认情况下,每一个容器可以使用宿主机上的所有cpu资源。大多数系统使用的资源调度算法是CFS(完全公平调度器),它公平调度每一个工作进程。进程可以分2类:CPU密集型(低优先级)和IO密集型(高优先级)。系统内核实时监测系统进程,当某个进程占用cpu资源时间过长时,内核会调整该进程的优先级。
docker 1.13 之后还支持realtime调度。
有如下的3种CPU资源分配策略:
- 按压缩方式比例分配
- 限定最多只能几个核
- 限定只能使用哪个或哪几个核
选项 |
描述 |
-c, --cpu-shares int |
cpu资源提供给一组容器使用,组内的容器按比例使用cpu资源,当容器处于空闲状态时,cpu资源被负载大的容器占用,(按压缩方式比例分配),当空闲进行运行起来时,cpu资源会被分配到其他容器 |
--cpus decimal |
指定 cpu的核心数量,这种方式直接限定了容器可用的cpu资源 |
|
--cpuset-cpus string |
指定容器只能运行在哪个cpu核心上(绑定cpu);核心使用0,1,2,3编号 |
CPU Share
docker 为容器设置 CPU share 的参数是 -c, --cpu-shares,它的值是一个整数。
docker 允许用户为每个容器设置一个数字,代表容器的 CPU share,默认情况下每个容器的 share 是 1024。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。举个例子,如果主机上有两个一直使用 CPU 的容器(为了简化理解,不考虑主机上其他进程),其 CPU share 都是 1024,那么两个容器 CPU 使用率都是 50%;如果把其中一个容器的 share 设置为 512,那么两者 CPU 的使用率分别为 67% 和 33%;如果删除 share 为 1024 的容器,剩下来容器的 CPU 使用率将会是 100%。
总结下来,这种情况下,docker 会根据主机上运行的容器和进程动态调整每个容器使用 CPU 的时间比例。这样的好处是能保证 CPU 尽可能处于运行状态,充分利用 CPU 资源,而且保证所有容器的相对公平;缺点是无法指定容器使用 CPU 的确定值。
CPU 核数
从 1.13 版本之后,docker 提供了 --cpus 参数可以限定容器能使用的 CPU 核数。这个功能可以让我们更精确地设置容器 CPU 使用量,是一种更容易理解也因此更常用的手段。
--cpus 后面跟着一个浮点数,代表容器最多使用的核数,可以精确到小数点二位,也就是说容器最小可以使用 0.01 核 CPU。比如,我们可以限制容器只能使用 1.5 核数 CPU。
如果设置的 --cpus 值大于主机的 CPU 核数,docker 会直接报错。
如果多个容器都设置了 --cpus,并且它们之和超过主机的 CPU 核数,并不会导致容器失败或者退出,这些容器之间会竞争使用 CPU,具体分配的 CPU 数量取决于主机运行情况和容器的 CPU share 值。也就是说 --cpus 只能保证在 CPU 资源充足的情况下容器最多能使用的 CPU 数,docker 并不能保证在任何情况下容器都能使用这么多的 CPU(因为这根本是不可能的)。
CPU 指定核心
Docker 允许调度的时候限定容器运行在哪个 CPU 上。可以通过 --cpuset-cpus 参数让容器只运行在某个或某几个核上。
--cpuset-cpus、-cpus 参数可以和 -c, --cpu-shares 一起使用,限制容器只能运行在某些 CPU 核上,并且配置了使用率。
限制容器运行在哪些核上并不是一个很好的做法,因为它需要事先知道主机上有多少 CPU 核,而且非常不灵活。除非有特别的需求,一般并不推荐在生产中这样使用。
其他CPU参数
选项 |
描述 |
--cpu-period int |
指定CFS调度的周期,一般与--cpu-quota一起使用。默认情况下周期为1 秒,以微秒为单位表示,一般使用默认值。1.13或者更高版本推荐使用 --cpus 标志代替。 |
--cpu-quota int |
在CFS调度中容器一个周期的cpu时间配额,即每个--cpu-period周期容器可获得的cpu时间(微秒),cpu-quota/cpu-period。1.13或者更高版本推荐使用 --cpus 标志代替。 |
压力测试
资源限制的演示
查询宿主机上的资源
这里用了lscpu和free命令:
[root@Docker ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 60
Model name: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
Stepping: 3
CPU MHz: 3999.996
BogoMIPS: 7999.99
Hypervisor vendor: Microsoft
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm ssbd ibrs ibpb stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt spec_ctrl intel_stibp flush_l1d
[root@Docker ~]# free -h
total used free shared buff/cache available
Mem: 936M 260M 340M 6.7M 334M 592M
Swap: 1.6G 0B 1.6G
[root@Docker ~]#
下载镜像
可以在docker hub上搜索stress(压测)。
下载镜像,运行查看帮助:
[root@Docker ~]# docker pull lorel/docker-stress-ng
[root@Docker ~]# docker run -it --rm lorel/docker-stress-ng
stress-ng, version 0.03.11
Usage: stress-ng [OPTION [ARG]]
--h, --help show help
......省略......
Example: stress-ng --cpu 8 --io 4 --vm 2 --vm-bytes 128M --fork 4 --timeout 10s
Note: Sizes can be suffixed with B,K,M,G and times with s,m,h,d,y
[root@Docker ~]#
主要命令参数:
- --h, --help: 默认启动容器就是这个命令参数
- -c N, --cpu N: 启动N个子进程对CPU进行压测
- -m N, --vm N: 启动N个进程对内存进行压测
- --vm-bytes N: 每个子进程使用多少内存(默认256MB)
测试内存限制
查看lorel/docker-stress-ng里内存相关的参数说明:
-m N, --vm N start N workers spinning on anonymous mmap
--vm-bytes N allocate N bytes per vm worker (default 256MB)
默认每个worker是256MB的内存,这个保持默认。然后指定--vm,开启2个worker,并且限制容器的内存只能使用256MB,启动容器:
[root@Docker ~]# docker run --name stress1 -it --rm -m 256m lorel/docker-stress-ng --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
这个终端已经被占用了,另起一个终端使用docker top命令查看容器内部正在运行的进程:
[root@Docker ~]# docker top stress1
UID PID PPID C STIME TTY TIME CMD
root 5922 5907 0 21:06 pts/0 00:00:00 /usr/bin/stress-ng --vm 2
root 6044 5922 0 21:06 pts/0 00:00:00 /usr/bin/stress-ng --vm 2
root 6045 5922 0 21:06 pts/0 00:00:00 /usr/bin/stress-ng --vm 2
root 6086 6044 13 21:06 pts/0 00:00:00 /usr/bin/stress-ng --vm 2
root 6097 6045 47 21:06 pts/0 00:00:00 /usr/bin/stress-ng --vm 2
[root@Docker ~]#
这里可以看一下PID和PPID,这里一共5个进程,一个父进程创建了2个子进程,这2个子进程又分别各创建了一个进程。
另外还可以使用命令docker stats查看容器的资源实时使用的情况:
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
626f38c4a4ad stress1 18.23% 256MiB / 256MiB 100.00% 656B / 0B 17.7MB / 9.42GB 5
这个是实时刷新的。
测试CPU限制
限制容器最大只能使用2核,然后同时开启8个CPU进行压测,使用下面的命令:
docker run -it --rm --cpus 2 lorel/docker-stress-ng --cpu 8
限制只用0.5核,开启4个CPU进行压测:
[root@Docker ~]# docker run --name stress2 -it --rm --cpus 0.5 lorel/docker-stress-ng --cpu 4
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 4 cpu
另起一个终端使用docker top命令查看容器内部正在运行的进程:
[root@Docker ~]# docker top stress2
UID PID PPID C STIME TTY TIME CMD
root 7198 7184 0 22:35 pts/0 00:00:00 /usr/bin/stress-ng --cpu 4
root 7230 7198 12 22:35 pts/0 00:00:02 /usr/bin/stress-ng --cpu 4
root 7231 7198 12 22:35 pts/0 00:00:02 /usr/bin/stress-ng --cpu 4
root 7232 7198 12 22:35 pts/0 00:00:02 /usr/bin/stress-ng --cpu 4
root 7233 7198 12 22:35 pts/0 00:00:02 /usr/bin/stress-ng --cpu 4
[root@Docker ~]#
一个父进程,创建了4个子进程。
然后再用docker stats命令查看资源占用:
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
14a341dd23d1 stress2 50.02% 13.75MiB / 908.2MiB 1.51% 656B / 0B 0B / 0B 5
因为限制了0.5核,所以基本不会超过50%。
测试 CPU Share
开启3个容器,分别指定不同的--cpu-shares参数,不指定的话默认是1024:
[root@Docker ~]# docker run --name stress3.1 -itd --rm --cpu-shares 512 lorel/docker-stress-ng --cpu 4
800d756f76ca4cf20af9fa726349f25e29bc57028e3a1cb738906a68a87dcec4
[root@Docker ~]# docker run --name stress3.2 -itd --rm lorel/docker-stress-ng --cpu 4
4b88007191812b239592373f7de837c25f795877d314ae57943b5410074c6049
[root@Docker ~]# docker run --name stress3.3 -itd --rm --cpu-shares 2048 lorel/docker-stress-ng --cpu 4
8f103395b6ac93d337594fdd1db289b6462e01c3a208dcd3788332458ec03b98
[root@Docker ~]#
查看3个容器的CPU占用率:
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
800d756f76ca stress3.1 14.18% 14.53MiB / 908.2MiB 1.60% 656B / 0B 0B / 0B 5
4b8800719181 stress3.2 28.60% 15.78MiB / 908.2MiB 1.74% 656B / 0B 0B / 0B 5
8f103395b6ac stress3.3 56.84% 15.38MiB / 908.2MiB 1.69% 656B / 0B 0B / 0B 5
占用率基本就是1/2/4,符合期望。