03-Docker资源控制

    cgroup是control group的简写,是Linux内核提供的一种限制所使用物理资源的机制,这些资源主要包括CPU、内存、blkio。Docker就是采用cgroup来控制容器对操作系统资源的使用。

一、内存限制

1.概述

Docker 提供的内存限制功能有以下几点:

  a.容器能使用的内存和交换分区大小。

  b.容器的核心内存大小。

  c.容器虚拟内存的交换行为。

  d.容器内存的软性限制。

  e.是否杀死占用过多内存的容器。

  f.容器被杀死的优先级

一般情况下,达到内存限制的容器过段时间后就会被系统杀死。

2. 内存限制相关的参数

    执行docker run命令时能使用的和内存限制相关的所有选项如下。

03-Docker资源控制_第1张图片

3. 用户内存限制

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

03-Docker资源控制_第2张图片

1)不设置

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

2)设置-m,--memory,不设置--memory-swap

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

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

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

3)设置-m,--memory=a,--memory-swap=b,且b > a

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

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

4)设置-m,--memory=a,--memory-swap=-1

    给-m参数设置一个正常值,而给--memory-swap设置成 -1。这种情况表示限制容器能使用的内存大小为 a,而不限制容器能使用的 swap 分区大小。这时候,容器内进程能申请到的内存大小为 a + 宿主机的 swap 大小。

4. Memoryreservation

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

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

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

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

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

5. 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,是正确的使用方法:

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

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

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

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

6. 核心内存

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

    a.stack pages(栈页面)

    b.slab pages

    c.socket memory pressure

    d.tcp memory pressure

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

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

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

    U != 0,不限制核心内存。这是默认的标准设置方式

    K < U,核心内存是用户内存的子集。这种设置在部署时,每个cgroup 的内存总量被过度使用。过度使用核心内存限制是绝不推荐的,因为系统还是会用完不能回收的内存。在这种情况下,你可以设置K,这样 groups 的总数就不会超过总内存了。然后,根据系统服务的质量自由地设置 U。

    K > U,因为核心内存的变化也会导致用户计数器的变化,容器核心内存和用户内存都会触发回收行为。这种配置可以让管理员以一种统一的视图看待内存。对想跟踪核心内存使用情况的用户也是有用的。

    例如:

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

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

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

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

7. Swappiness

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

    例如:

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

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

二、CPU 限制

1. 概述

    Docker 的资源限制和隔离完全基于Linux cgroups。对CPU资源的限制方式也和cgroups相同。Docker提供的CPU资源限制选项可以在多核系统上限制容器能利用哪些vCPU。而对容器最多能使用的CPU时间有两种限制方式:

    一是有多个CPU密集型的容器竞争CPU时,设置各个容器能使用的CPU时间相对比例。

    二是以绝对的方式设置容器在每个调度周期内最多能使用的CPU时间。

2. CPU 限制相关参数

    docker run命令和 CPU 限制相关的所有选项如下:


03-Docker资源控制_第3张图片

    其中--cpuset-cpus用于设置容器可以使用的vCPU核。-c,--cpu-shares用于设置多个容器竞争CPU时,各个容器相对能分配到的CPU时间比例。--cpu-period和--cpu-quata用于设置容器能使用的CPU绝对时间。

3. 用户CPU限制

    我们可以设置容器可以在哪些 CPU 核上运行。

    例如:$ docker run -it --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash

    表示容器中的进程可以在cpu1和cpu3上执行。

    $ docker run -it --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash

    表示容器中的进程可以在 cpu 0、cpu 1 及 cpu 2 上执行。

4. CPU 资源的相对限制

    默认情况下,所有的容器得到同等比例的CPU周期。在有多个容器竞争CPU时我们可以设置每个容器能使用的CPU时间比例。这个比例叫作共享权值,通过-c或--cpu-shares设置。Docker默认每个容器的权值为1024。不设置或将其设置为0,都将使用这个默认值。系统会根据每个容器的共享权值和所有容器共享权值和比例来给容器分配CPU时间。

    假设有三个正在运行的容器,这三个容器中的任务都是CPU密集型的。第一个容器的CPU共享权值是1024,其它两个容器的CPU共享权值是512。第一个容器将得到50%的CPU时间,而其它两个容器就只能各得到25%的CPU时间了。如果再添加第四个CPU共享值为1024 的容器,每个容器得到的CPU时间将重新计算。第一个容器的CPU时间变为33%,其它容器分得的CPU时间分别为16.5%、16.5%、33%。

    必须注意的是,这个比例只有在CPU密集型的任务执行时才有用。在四核的系统上,假设有四个单进程的容器,它们都能各自使用一个核的 100% CPU 时间,不管它们的CPU共享权值是多少。

    在多核系统上,CPU时间权值是在所有CPU核上计算的。即使某个容器的CPU时间限制少于 100%,它也能使用各个CPU核的100%时间。

    例如,假设有一个不止三核的系统。用-c=512的选项启动容器{C0},并且该容器只有一个进程,用-c=1024的启动选项为启动容器C2,并且该容器有两个进程。CPU 权值的分布可能是这样的:

PID    container   CPU CPU share

100    {C0}    0   100% of CPU0

101    {C1}    1   100% of CPU1

102    {C1}    2   100% of CPU2

5. CPU 资源的绝对限制

    Linux通过CFS(Completely Fair Scheduler,完全公平调度器)来调度各个进程对CPU的使用。CFS 默认的调度周期是100ms。

    我们可以设置每个容器进程的调度周期,以及在这个周期内各个容器最多能使用多少CPU时间。使用--CPU-period即可设置调度周期,使用--CPU-quota即可设置在每个周期内容器能使用的CPU时间。两者一般配合使用。

    例如:

    $ docker run -it --CPU-period=50000 --CPU-quota=25000 ubuntu:16.04 /bin/bash

    将 CFS 调度的周期设为50000,将容器在每个周期内的 CPU 配额设置为 25000,表示该容器每 50ms 可以得到 50% 的 CPU 运行时间。

    $ docker run -it --CPU-period=10000 --CPU-quota=20000 ubuntu:16.04 /bin/bash

    将容器的 CPU 配额设置为 CFS 周期的两倍,CPU 使用时间怎么会比周期大呢?其实很好解释,给容器分配两个 vCPU 就可以了。该配置表示容器可以在每个周期内使用两个 vCPU 的 100% 时间。

    CFS 周期的有效范围是 1ms~1s,对应的--CPU-period的数值范围是 1000~1000000。而容器的 CPU 配额必须不小于 1ms,即--CPU-quota的值必须 >= 1000。可以看出这两个选项的单位都是 us。

三、磁盘IO限制

1. 概述

    相对于CPU和内存的配额控制,docker对磁盘IO的控制相对不成熟,大多数都必须在有宿主机设备的情况下使用。

2. IO限制相关参数

    执行docker run命令时能使用的和IO限制相关的所有选项如下。

03-Docker资源控制_第4张图片

3. 磁盘IO配额控制

1)blkio-weight

    要使–blkio-weight生效,需要保证IO的调度算法为CFQ。可以使用下面的方式查看:

    root@ubuntu:~# cat /sys/block/sda/queue/scheduler

    noop [deadline] cfq

    使用下面的命令创建两个–blkio-weight值不同的容器:

    docker run -ti –rm –blkio-weight 100 ubuntu:stress

    docker run -ti –rm –blkio-weight 1000 ubuntu:stress

    在容器中同时执行下面的dd命令,进行测试:

    time dd if=/dev/zero of=test.out bs=1M count=1024 oflag=direct

2)device-write-bps

    使用下面的命令创建容器,并执行命令验证写速度的限制。

    docker run -tid –name disk1 –device-write-bps /dev/sda:1mb ubuntu:stress

    通过dd来验证写速度,输出如下图示:

    可以看到容器的写磁盘速度被成功地限制到了1MB/s。device-read-bps等其他磁盘IO限制参数可以使用类似的方式进行验证。

4. 容器空间大小限制

    在docker使用devicemapper作为存储驱动时,默认每个容器和镜像的最大大小为10G。如果需要调整,可以在daemon启动参数中,使用dm.basesize来指定,但需要注意的是,修改这个值,不仅仅需要重启docker daemon服务,还会导致宿主机上的所有本地镜像和容器都被清理掉。

    使用aufs或者overlay等其他存储驱动时,没有这个限制。

你可能感兴趣的:(03-Docker资源控制)