原文地址:https://docs.docker.com/engine/admin/resource_constraints/#configure-the-realtime-scheduler

摘要

    在Docker中默认情况下容器的资源只受到host端内核资源调度的限制。但是可以在使用docker run命令启动容器时添加一些资源控制标志来控制容器的memroy,cpu以及block IO资源分配。

    本文将介绍这些控制标志使用细节及其意义。

一、Memory内存

1.1 OOM的危害

    有时候我们有必要限制一个容器的内存用量以免它肆无忌惮的消耗host主机内存,导致系统内存紧张。在host端linux系统中,若内存资源消耗到极度紧缺的状态时一些关键任务将无法再继续正常运行,这将会引发Out of Memory Exception(OOME)。此时内核会选择一个进程(一般是内存消耗极大的进程)来杀掉以释放出内存。任何进程都有可能被杀到,包括Docker或者其他重要的应用进程;如果被杀到的是系统中重要的进程就很可能会影响系统稳定。

    Docker可以通过调整Docker daemon的OOM priority来尽量降低系统发生OOM时被kill掉的风险。默认情况下容器的的OOM priority是没有被经过调整的,在发生OOME时内核就会倾向于"干掉"某个容器,而非Docker daemon或者系统中重要的进程。如果有必要,我们也可以通过参数--oom-score-adj(后面带一个很小的负值)或者--oom-disable-kill参数
来保护Docker daemon或者某个重要的容器在发生OOM时尽量不被内核给杀掉。

   更多linux 内核OOM管理的详细信息,参考 Out of Memory Management

    我们还可以通过如下途径来减少OOME带来的风险:
    (1) 在应用正式发布前进行充分的性能测试,以对它的内存消耗量有所了解;
    (2) 确保我们的应用消耗合理的资源量;
    (3) 限制容器的内存使用量,下面会详细介绍;
    (4) 要注意Docker host端配置了swap的情况。内存交换会影响性能和速度,但在系统内存紧张时会起到一定的缓冲作用。
    (5) 如果可以,将容器改造成服务进程,然后使用service-level限制或node labels来确保应用在主机上有足够内存运行。

1.2 容器的内存限制

    容器的内存资源限制有hard limit或者soft limit限制。
    对于hard limit,容器内存不能超过设置的内存上限;对于soft limit,除非系统内存紧张或者内存资源争夺激励等等情况发生,否则一般在系统内存资源宽裕的情况下容器可以不受限制的自由申请内存。
    容器内存资源限制的选项标志一旦设置,会对容器的运行产生影响。

    这些选项标志的值格式为正整数加单位,如b(bytes), k(kilobytes), m(megabytes), g(gigabytes)。

选项描述
-m,--memory内存限制,格式是数字加单位,单位可以为 b,k,m,g。最小为 4M。
--memory-swap可交换分区内存的大小,详细见下面--memory-swap(一节)
--memory-swappiness默认情况下host端内核可将容器使用的anonymous pages按一定的比例交换出去。可为此标志设置一个 0~100 之间的整数值来调整这个比例。
--memory-reservation内存资源软限制,其值一般不超过--memory的值(实际可以设置超过--memory,但是那样一来这个软限制实质没有起到作用)。软限制一般会在系统内存紧张时才起作用,它不保证容器的内存使用不超过这个限制。
--kernel-memory容器的核心内存限制,最小为 4M。由于核心内存无法交换出去,对核心内存大量需求的容器可能会造成host端资源的紧张,从而影响到host端或在其他容器参考"-"-kernel-memory 一节。
--oom-kill-disable使用此标志可阻止 OOM发生时被内核kill掉。要用此标志保证容器不被OOM killer杀掉还需要设置-m/"-"-memroy选项;如果不设置-m选项,host端可能会有内存耗尽的风险,此时内核会杀掉系统中的进程以释放内存。
--oom-score-adj 容器被 OOM killer 杀死的优先级,范围是[-1000, 1000],默认为 0


    关于cgroups和memory更多的详细信息参考文档 Memroy Resource Controller

1.3 memory-swap

    --memory-swap标志只有在 --memory也设置时才有意义。使用此标志容器可以在耗尽它的可用内存资源后将超额的内存交换到磁盘上。当然,频繁的内存交换会导致性能的损耗。
    这个标志的设置会产生如下一些影响:
     (1) 如果--memory-swap设置为一个正整数,则 --memory标志也必须设置。 --memory-swap代表"内存"+"交换分区"的总量;--memory表示非交换内存的总量。举个例子:--memory="300M"  而  --memory-swap="1g",则容器可用的内存为300M,而交换分区为700M;
    (2) 如果--memory-swap 设置为0,则忽略此标志;
    (3) 如果 --memory-swap的值与 --memory相等 ( --memory设置为正值),则其效果相当于 --memory-swap设置为0;
    (4) 如果没有设置 --memory-swap标志,但设置了 --memory,则容器可获得2倍于 --memory的交换分区。例如,如果 --memory="300M", 而 --memory-swap 没有设置,实际上容器可使用300M内存+600M交换分区;
    (5) 如果 --memory-swap 设置为 -1,则容器的的交换分区不受Docker限制,只受到host系统可用资源限制。

1.4 --memory-swappiness

    (1) 0值表示关掉 anonymous 页的交换;
    (2) 设置为100表示所有的匿名页都可交换;
    (3) 如果没有设置此标志,其默认值与host的值保持一致。

1.5 --kernel-memory

    核心内存不同于用户内存,它不能交换到磁盘,它包括: 
      *stack pages
      *slab pages
      *sockets memory pressure
      *tcp memory pressure
    --kernel-memory与 --memory有一定的联系,它是在 --memroy的上下文中限制核心内存,即核心内存的消耗会计入到 --memory的消耗中。
    请看如下场景:
      *Unlimited memory, unlimited kernel memory: 默认配置;
       *Unlimited memory, limited kernel memory:如果所有cgroup所需的内存总量超过host系统实际的内存总量时可选这种配置。这种情况下,可以限制核心内存数量不超过host主机可用内存。当容器需要更多内存时则需要等待。
      *Limited memory, umlimited kernel memory: 只对总的内存限制,但不单独限制kernel内存。
       *Limited memory, limited kernel memory: 同时限制用户内存和核心内存。这在进行内存相关的问题调式时有用。(1) 若核心内存限制低于用户内存限制,当容器核心内存超限会引发一个OOM;(2)若核心内存限制高于用户内存限制,则不会导致容器发生OOM。

     一旦打开任一内存限制,host端将会监控进程的"high 水线"值;我们也可以据此追踪哪个进程(这里实际是容器)的内存超限,这可以在主机端通过/proc/<PID>/status来查看。

二、CPU资源

     默认情况下没有限制容器的cpu(时间)资源,我们可以手动为容器设置这种限制。一般情况下是设置CFS调度相关的cpu时间(大部分的任务都是普通任务,即使用的是CFS调度)。在Docker1.13或更高版本中,可以对实时调度任务的cpu时间进行配置。

2.1 CFS调度中cpu分配

     在linux中普通进程都使用CFS算法来进行调度。可以通过相关的标志来配置容器可用的cpu资源,容器 使用这些标志,实际上是操作了容器在host端的cgroup相关的配置项。下表列出了一些标志:
Options Description
--cpus=<value>表示分配给容器可用的cpu资源。例如:host系统有2个cpu,我们为容器指定了 --cpu="1.5" , 这样就保证了容器最多可以使用"1.5"个cpus资源。这与 --cpu-period="1000000" 加上 --cpu-quota="1500000"的设置等价。此标志在Docker 1.3或更高版本才支持。
--cpu-period=<value>指定CFS调度的周期,一般与 --cpu-quota一起使用。默认情况下周期为1 秒,以微秒为单位表示,一般使用默认值。1.13或者更高版本推荐使用 --cpus 标志代替。
--cpu-quota=<value>在CFS调度中容器一个周期的cpu时间配额,即每个 --cpu-period周期容器可获得的cpu时间(微秒),cpu-quota/cpu-period。1.13或者更高版本推荐使用 --cpus 标志代替。
--cpuset-cpus指定容器运行在哪个cpu或cpu core上。其值可以是逗号“,”分隔的各个cpu号,也可以通过"-"指定一个cpu范围。在多cpu系统中,cpu从0号开始编号。举个例子:0-3表示容器可以使用cpu0,cpu1,cpu2,cpu3;而1,3则表示只能使用 cpu1和cpu3。
--cpu-shares用以设置容器的权重,CFS调度中进程的默认权重是1024。在cpu资源有限的情下权重值才会起作用;如果cpu资源充足,各个容器都可获得足够的cpu执行时间,这个时候 --cpu-shares相当于一个软限制。

    下面看一下系统只有1个cpu的情况下,下面的设置可以保证各个容器最多只能分配50%的cpu时间。
     (1) Docker 1.13或者更高版本
docker run -it --cpus=".5" ubuntu /bin/bash
     (2)Docker 1.12或者更低版本
$ docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash

2.2 实时调度的配置

    在Docker1.13或者更高的版本中,可以为容器的实时调度进行配置。在配置docker daemon或者容器前,首先要保证host内核使能了相应的配置。

 HSOT端的内核配置

     我们通过 zcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHED 来确认host内核是否使能CONFIG_RT_GROUP_SCHED配置项,也可以通过检查/sys/fs/cgroup/cpu.rt_runtime_us文件是否存在来进行确认。

DOCKER DAEMON的配置

    如果容器需要实时调度(有实 任务),启动Docker daemon时通过 --cpu-rt-runtime标志来指定容器中实时任务在一个周期内可占用的最大cpu时间(单位为微秒)。
    举个例子,在统的默认周期1000000 us(1秒)的情况下,设置 --cpu-rt-runtime=950000可以保证保证容器中的实时任务在一个周期内最多可以获得950毫秒的cpu时间,周期内剩余的50ms可分配给非实时任务。
   在具有systemd的系统上如果要让这些配置永久生效,可以参看Control and configure Docker with systemd(https://docs.docker.com/engine/admin/systemd/)。

单个容器的配置

    在用docker run启动容器时,可以通过一些标志来控制容器的CPU资源分配。

Option Description
-cap-add=sys_nice容器具有CAP_SYS_NICE权限,这可以允许容器修改自己的nice值,设置为实时调度策略,设置cpu亲和性等等操作
--cpu-rt-runtime=<value>一个实时周期内可运行的最大cpu时间,需要设置--cap-add=sys_nice标志
--ulimit rtprio=<value>容器的最高实时优先级,需要设置--cap-add=sys_nice标志

    看看下面这个例子:
$ docker run --it --cpu-rt-runtime=95000 \
                  --ulimit rtprio=99 \
                  --cap-add=sys_nice \
                  debian:jessie
    上例表示从debian:jessie运行一个拥有CAP_SYS_NICE特权,且实时优先级为99,周期内最大实时cpu时间为950ms的容器。
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐