docker-内存-限制

在使用 Docker 运行容器时,一台主机上可能会运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制,那么容器之间会互相影响,小的来说会导致容器资源使用不公平;大的来说,可能会导致主机和集群资源耗尽,服务完全不可用。
这篇文章主要是讲解docker对于内存的限制。

参数概述

docker run –help 能够看到相关参数:

选项 说明
-m,–memory 内存限制,格式是数字加单位,单位可以为 b,k,m,g。最小为 4M
–memory-swap 内存+交换分区大小总限制。格式同上。必须比 -m 设置的值大
–memory-reservation 内存的软性限制。格式同上
–oom-kill-disable 是否阻止 OOM killer 杀死容器,默认没设置
–oom-score-adj 容器被 OOM killer 杀死的优先级,范围是[-1000, 1000],默认为 0
-memory-swappiness 用于设置容器的虚拟内存控制行为。值为 0~100 之间的整数
–kernel-memory 核心内存限制。格式同上,最小为 4M

用户内存设置

用户内存限制就是对容器能使用的内存和交换分区的大小作出限制。使用时要遵循两条直观的规则:-m,–memory选项的参数最小为 4 M。–memory-swap不是交换分区,而是内存加交换分区的总大小,所以–memory-swap必须比-m,–memory大。在这两条规则下,一般有四种设置方式。

不设置

如果不设置-m,–memory和–memory-swap,容器默认可以用完宿舍机的所有内存和 swap 分区。不过注意,如果容器占用宿主机的所有内存和 swap 分区超过一段时间后,会被宿主机系统杀死(如果没有设置–oom-kill-disable=true的话)。

设置-m,–memory,不设置–memory-swap

给-m或–memory设置一个不小于 4M 的值,假设为 x,不设置–memory-swap,或将–memory-swap设置为 0。这种情况下,容器能使用的内存大小为 x,能使用的交换分区大小也为 x。因为 Docker 默认容器交换分区的大小和内存相同。

如果在容器中运行一个一直不停申请内存的程序,你会观察到该程序最终能占用的内存大小为 2x。

比如$ docker run -m 1G ubuntu:16.04,该容器能使用的内存大小为 1G,能使用的 swap 分区大小也为 1G。容器内的进程能申请到的总内存大小为 2G。

设置-m,–memory=x,–memory-swap=y,且y > x

给-m设置一个参数 x,给–memory-swap设置一个参数 y。x 是容器能使用的内存大小,y是容器能使用的 内存大小 + swap 分区大小。所以 y 必须大于 x。y - x 即为容器能使用的 swap 分区大小。

比如$ docker run -m 1G –memory-swap 3G ubuntu:16.04,该容器能使用的内存大小为 1G,能使用的 swap 分区大小为 2G。容器内的进程能申请到的总内存大小为 3G。

设置-m,–memory=x,–memory-swap=-1

给-m参数设置一个正常值 x,而给–memory-swap设置成 -1。这种情况表示限制容器能使用的内存大小为 x,而不限制容器能使用的 swap 分区大小。

这时候,容器内进程能申请到的内存大小为 x + 宿主机的 swap 大小。

Memory reservation

Memory reservation 是一种软性限制,用于节制容器内存使用。给–memory-reservation设置一个比-m小的值后,虽然容器最多可以使用-m 设置的内存大小,但在宿主机内存资源紧张时,在系统的下次内存回收时,系统会回收容器的部分内存页,强迫容器的内存占用回到–memory-reservation设置的值大小。

没有设置时(默认情况下)–memory-reservation的值和-m的限定的值相同。将它设置为 0 和设置的比-m的参数大 都等同于没有设置。

Memory reservation 是一种软性机制,它不保证任何时刻容器使用的内存不会超过–memory-reservation限定的值,它只是确保容器不会长时间占用超过–memory-reservation限制的内存大小。
例如:

1
$ docker run -it -m 500M --memory-reservation 200M ubuntu:16.04 /bin/bash

如果容器使用了大于 200M 但小于 500M 内存时,下次系统的内存回收会尝试将容器的内存锁紧到 200M 以下。

例如:

1
$ docker run -it --memory-reservation 1G ubuntu:16.04 /bin/bash

容器可以使用尽可能多的内存。–memory-reservation确保容器不会长时间占用太多内存。

OOM killer

默认情况下,在出现 out-of-memory(OOM) 错误时,系统会杀死容器内的进程来获取更多空闲内存。这个杀死进程来节省内存的进程,我们姑且叫它 OOM killer。我们可以通过设置–oom-kill-disable选项来禁止 OOM killer 杀死容器内进程。但请确保只有在使用了-m/–memory选项时才使用–oom-kill-disable禁用 OOM killer。如果没有设置-m选项,却禁用了 OOM-killer,可能会造成出现 out-of-memory 错误时,系统通过杀死宿主机进程来获取更大内存。
下面的例子限制了容器的内存为 100M 并禁止了 OOM killer:

1
$ docker run -it -m 100M --oom-kill-disable ubuntu:16.04 /bin/bash

是正确的使用方法。

而下面这个容器没设置内存限制,却禁用了 OOM killer 是非常危险的:

1
$ docker run -it --oom-kill-disable ubuntu:16.04 /bin/bash

容器没用内存限制,可能会导致系统无内存可用,并尝试杀死系统进程来获取更多可用内存。

一般一个容器只有一个进程,这个唯一进程被杀死,容器也就被杀死了。我们可以通过 –oom-score-adj 选项来设置在系统内存不够时,容器被杀死的优先级。负值更有不可能被杀死,而正值更有可能被杀死。

核心内存

核心内存和用户内存不同的地方在于核心内存不能被交换出。不能交换出去的特性使得容器可以通过消耗太多内存来阻塞一些系统服务。核心内存包括:

  • stack pages(栈页面)
  • slab pages
  • socket memory pressure
  • tcp memory pressure
    可以通过设置核心内存限制来约束这些内存。例如,每个进程都要消耗一些栈页面,通过限制核心内存,可以在核心内存使用过多时阻止新进程被创建。

核心内存和用户内存并不是独立的,必须在用户内存限制的上下文中限制核心内存。

假设用户内存的限制值为 U,核心内存的限制值为 K。有三种可能地限制核心内存的方式:

  • U != 0,不限制核心内存。这是默认的标准设置方式
  • K < U,核心内存是用户内存的子集。这种设置在部署时,每个 cgroup 的内存总量被过度使用。过度使用核心内存限制是绝不推荐的,因为系统还是会用完不能回收的内存。在这种情况下,你可以设置 K,这样 groups 的总数就不会超过总内存了。然后,根据系统服务的质量自由地设置 U。
  • K > U,因为核心内存的变化也会导致用户计数器的变化,容器核心内存和用户内存都会触发回收行为。这种配置可以让管理员以一种统一的视图看待内存。对想跟踪核心内存使用情况的用户也是有用的。

例如:

1
$ docker run -it -m 500M --kernel-memory 50M ubuntu:16.04 /bin/bash

容器中的进程最多能使用 500M 内存,在这 500M 中,最多只有 50M 核心内存。

1
$ docker run -it --kernel-memory 50M ubuntu:16.04 /bin/bash

没用设置用户内存限制,所以容器中的进程可以使用尽可能多的内存,但是最多能使用 50M 核心内存。

Swappiness

默认情况下,容器的内核可以交换出一定比例的匿名页。–memory-swappiness就是用来设置这个比例的。–memory-swappiness可以设置为从 0 到 100。0 表示关闭匿名页面交换。100 表示所有的匿名页都可以交换。默认情况下,如果不适用–memory-swappiness,则该值从父进程继承而来。

例如:

1
$ docker run -it --memory-swappiness=0 ubuntu:16.04 /bin/bash

将–memory-swappiness设置为 0 可以保持容器的工作集,避免交换代理的性能损失。

转自:Docker 运行时资源限制