Docker如何限制容器可⽤的内存
默认情况下容器使⽤的资源是不受限制的。也就是可以使⽤主机内核调度器所允许的最⼤资源。但是在容器的使⽤过程中,经常需要对容器可以使⽤的主机资源进⾏限制,本⽂介绍如何限制容器可以使⽤的主机内存。
为什么要限制容器对内存的使⽤?
限制容器不能过多的使⽤主机的内存是⾮常重要的。对于 linux 主机来说,⼀旦内核检测到没有⾜够的内存可以分配,就会扔出 OOME(Out Of Memmory Exception),并开始杀死⼀些进程⽤于释放内存空间。糟糕的是任何进程都可能成为内核猎杀的对象,包括 docker daemon 和其它⼀些重要的程序。更危险的是如果某个⽀持系统运⾏的重要进程被⼲掉了,整个系统也就宕掉了!这⾥我们考虑⼀个⽐较常见的场景,⼤量的容器把主机的内存消耗殆尽,OOME 被触发后系统内核⽴即开始杀进程释放内存。如果内核杀死的第⼀个进程就是 docker daemon 会怎么样?结果是所有的容器都不⼯作了,这是不能接受的!
针对这个问题,docker 尝试通过调整 docker daemon 的 OOM 优先级来进⾏缓解。内核在选择要杀死的进程时会对所有的进程打分,直接杀死得分最⾼的进程,接着是下⼀个。当 docker daemon 的 OOM 优先级被降低后(注意容器进程的 OOM 优先级并没有被调整),docker daemon 进程的得分不仅会低于容器进程的得分,还会低于其它⼀些进程的得分。这样 docker daemon 进程就安全多了。
我们可以通过下⾯的脚本直观的看⼀下当前系统中所有进程的得分情况:
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
printf "%2d %5d %s\n" \
"$(cat $proc/oom_score)" \
"$(basename $proc)" \
"$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 40
此脚本输出得分最⾼的 40 个进程,并进⾏了排序:
第⼀列显⽰进程的得分,mysqld 排到的第⼀名。显⽰为 node server.js 的都是容器进程,排名普遍⽐较靠前。红框中的是docker daemon 进程,⾮常的靠后,都排到了 sshd 的后⾯。
有了上⾯的机制后是否就可以⾼枕⽆忧了呢!不是的,docker 的官⽅⽂档中⼀直强调这只是⼀种缓解的⽅案,并且为我们提供了⼀些降低风险的建议:
通过测试掌握应⽤对内存的需求
保证运⾏容器的主机有重组的内存
限制容器可以使⽤的内存
为主机配置 swap
好了,啰嗦了这么多,其实就是说:通过限制容器使⽤的内存上限,可以降低主机内存耗尽时带来的各种风险。
压⼒测试⼯具 stress
为了测试容器的内存使⽤情况,笔者在 ubuntu 的镜像中安装了压⼒测试⼯作 stress,并新创建了镜像
u-stress。本⽂演⽰⽤的所有容器都会通过 u-stress 镜像创建(本⽂运⾏容器的宿主机为 CentOS7)。下⾯是创建 u-stress 镜像的 Dockerfile:
FROM ubuntu:latest
RUN apt-get update && \
apt-get install stress
创建镜像的命令为:
$ docker build -t u-stress:latest .
限制内存使⽤上限
在进⼊繁琐的设置细节之前我们先完成⼀个简单的⽤例:限制容器可以使⽤的最⼤内存为 300M。
-m(--memory=) 选项可以完成这样的配置:
$ docker run -it -m 300M --memory-swap -1 --name con1 u-stress /bin/bash
下⾯的 stress 命令会创建⼀个进程并通过 malloc 函数分配内存:
# stress --vm 1 --vm-bytes 500M
通过 docker stats 命令查看实际情况:
上⾯的 docker run 命令中通过 -m 选项限制容器使⽤的内存上限为 300M。同时设置 memory-swap 值为 -1,它表⽰容器程序使⽤内存的受限,⽽可以使⽤的 swap 空间使⽤不受限制(宿主机有多少 swap 容器就可以使⽤多少)。
下⾯我们通过 top 命令来查看 stress 进程内存的实际情况:
上⾯的截图中先通过 pgrep 命令查询 stress 命令相关的进程,进程号⽐较⼤的那个是⽤来消耗内存的进程,我们就查看它的内存信息。VIRT 是进程虚拟内存的⼤⼩,所以它应该是 500M。RES 为实际分配的物理内存数量,我们看到这个值就在300M 上下浮动。看样⼦我们已经成功的限制了容器能够使⽤的物理内存数量。
限制可⽤的 swap ⼤⼩
强调⼀下 --memory-swap 是必须要与 --memory ⼀起使⽤的。
docker重启容器命令正常情况下, --memory-swap 的值包含容器可⽤内存和可⽤ swap。所以 --memory="300m" --memory-swap="1g" 的含义为:
容器可以使⽤ 300M 的物理内存,并且可以使⽤ 700M(1G -330M) 的 swap。--memory-swap 居然是容器可以使⽤的物理内存和可以使⽤的 swap 之和!
把 --memory-swap 设置为 0 和不设置是⼀样的,此时如果设置了 --memory,容器可以使⽤的 swap ⼤⼩为 --memory 值的两倍。
如果 --memory-swap 的值和 --memory 相同,则容器不能使⽤ swap。下⾯的 demo 演⽰了在没有 swap 可⽤的情况下向系统申请⼤量内存的场景:
$ docker run -it --rm -m 300M --memory-swap=300M u-stress /bin/bash
# stress --vm 1 --vm-bytes 500M
demo 中容器的物理内存被限制在 300M,但是进程却希望申请到 500M 的物理内存。在没有 swap 可⽤的情况下,进程直接被 OOM kill 了。如果有⾜够的 swap,程序⾄少还可以正常的运⾏。
我们可以通过 --oom-kill-disable 选项强⾏阻⽌ OOM kill 的发⽣,但是笔者认为 OOM kill 是⼀种健康的⾏为,为什么要阻⽌它呢?
除了限制可⽤ swap 的⼤⼩,还可以设置容器使⽤ swap 的紧迫程度,这⼀点和主机的 swappiness 是⼀样的。容器默认会继承主机的 swappiness,如果要显式的为容器设置 swappiness 值,可以使⽤ --memory-swappiness 选项。
总结
通过限制容器可⽤的物理内存,可以避免容器内服务异常导致⼤量消耗主机内存的情况(此时让容器重启是较好的策略),因此可以降低主机内存被耗尽带来的风险。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。