资源约束
默认情况下,容器没有资源约束,可以使用主机内核调度器允许的任何给定资源。Docker 提供了一些方法来控制容器可以使用多少内存或 CPU,通过设置 docker run
命令的运行时配置标志。本节详细介绍了何时应该设置这些限制以及设置这些限制可能带来的影响。
许多这些功能需要你的内核支持 Linux capabilities。要检查支持情况,可以使用 docker info
命令。如果你的内核中禁用了某个 capability,你可能会在输出末尾看到类似以下的警告:
WARNING: No swap limit support
请查阅操作系统的文档以启用它们。更多信息请参见 Docker Engine 故障排除指南。
内存
了解内存不足的风险
重要的是不要让运行中的容器消耗太多主机内存。在 Linux 主机上,如果内核检测到没有足够的内存来执行重要的系统功能,它会抛出 OOME
(即 Out Of Memory Exception
,内存不足异常),并开始终止进程以释放内存。任何进程都可能被终止,包括 Docker 和其他重要的应用程序。如果错误的进程被终止,这可能会有效地导致整个系统崩溃。
Docker 尝试通过调整 Docker 守护进程的 OOM 优先级来缓解这些风险,使其不太可能被系统上的其他进程终止。容器的 OOM 优先级未调整。这使得单个容器比 Docker 守护进程或其他系统进程更有可能被终止。你不应该试图通过手动将守护进程或容器上的 --oom-score-adj
设置为极小的负数,或者在容器上设置 --oom-kill-disable
来规避这些安全措施。
有关 Linux 内核 OOM 管理的更多信息,请参阅 内存不足管理。
你可以通过以下方式缓解由于 OOME 导致的系统不稳定风险:
- 在将应用程序投入生产之前,执行测试以了解其内存需求。
- 确保你的应用程序仅在资源充足的主机上运行。
- 限制你的容器可以使用的内存量,如下所述。
- 在 Docker 主机上配置 swap 时要小心。Swap 比内存慢,但可以作为防止系统内存耗尽的缓冲区。
- 考虑将你的容器转换为服务,并使用服务级约束和节点标签来确保应用程序仅在具有足够内存的主机上运行。
限制容器对内存的访问
Docker 可以强制执行硬性或软性内存限制。
- 硬性限制允许容器使用的内存不超过固定数量。
- 软性限制允许容器根据需要使用尽可能多的内存,除非满足某些条件,例如当内核检测到主机内存不足或出现争用时。
其中一些选项单独使用或同时设置多个选项时会产生不同的效果。
这些选项大多接受一个正整数,后跟后缀 b
、k
、m
、g
,分别表示字节、千字节、兆字节或千兆字节。
选项 | 描述 |
---|---|
-m 或 --memory= | 容器可以使用的最大内存量。如果设置此选项,允许的最小值为 6m (6 兆字节)。也就是说,必须将值设置为至少 6 兆字节。 |
--memory-swap * | 此容器允许交换到磁盘的内存量。参见 --memory-swap 详解。 |
--memory-swappiness | 默认情况下,主机内核可以交换出容器使用的匿名页的百分比。你可以将 --memory-swappiness 设置为 0 到 100 之间的值,以调整此百分比。参见 --memory-swappiness 详解。 |
--memory-reservation | 允许你指定一个小于 --memory 的软限制,当 Docker 检测到主机上的争用或内存不足时,该软限制会被激活。如果使用 --memory-reservation ,它必须设置为低于 --memory 才能优先。由于这是一个软限制,它不能保证容器不会超过该限制。 |
--kernel-memory | 容器可以使用的最大内核内存量。允许的最小值为 6m 。由于内核内存无法交换出去,内核内存不足的容器可能会阻塞主机资源,这对主机和其他容器都会产生副作用。参见 --kernel-memory 详解。 |
--oom-kill-disable | 默认情况下,如果发生内存不足 (OOM) 错误,内核会终止容器中的进程。要更改此行为,请使用 --oom-kill-disable 选项。仅在同时设置了 -m/--memory 选项的容器上禁用 OOM killer。如果未设置 -m 标志,主机可能会耗尽内存,内核可能需要终止主机系统的进程来释放内存。 |
有关 cgroups 和内存的更多信息,请参见 内存资源控制器 的文档。
--memory-swap
详解
--memory-swap
是一个修饰符标志,仅在同时设置了 --memory
时才有意义。使用 swap 允许容器在耗尽所有可用 RAM 后将额外的内存需求写入磁盘。对于经常将内存交换到磁盘的应用程序,会产生性能损失。
其设置可能产生复杂的影响:
如果将
--memory-swap
设置为正整数,则必须同时设置--memory
和--memory-swap
。--memory-swap
表示可以使用的内存和 swap 的总量,而--memory
控制非 swap 内存的使用量。因此,如果--memory="300m"
且--memory-swap="1g"
,容器可以使用 300m 内存和 700m (1g - 300m
) swap。如果将
--memory-swap
设置为0
,则该设置将被忽略,并将该值视为未设置。如果
--memory-swap
设置为与--memory
相同的值,且--memory
设置为正整数,则容器无权访问 swap。参见 阻止容器使用 swap。如果未设置
--memory-swap
且已设置--memory
,则如果主机容器配置了 swap 内存,容器可以使用与--memory
设置等量的 swap。例如,如果--memory="300m"
且未设置--memory-swap
,容器总共可以使用 600m 内存和 swap。如果
--memory-swap
明确设置为-1
,则容器允许使用无限量的 swap,最高可达主机系统上可用的数量。在容器内部,
free
等工具报告的是主机的可用 swap,而不是容器内部的可用 swap。不要依赖free
或类似工具的输出来确定是否存在 swap。
阻止容器使用 swap
如果 --memory
和 --memory-swap
设置为相同的值,这将阻止容器使用任何 swap。这是因为 --memory-swap
是内存和 swap 的总可用量,而 --memory
仅是物理内存的可用量。
--memory-swappiness
详解
- 值为 0 会关闭匿名页交换。
- 值为 100 会将所有匿名页设置为可交换。
- 默认情况下,如果不设置
--memory-swappiness
,该值会继承自主机。
--kernel-memory
详解
内核内存限制是根据分配给容器的总内存量表示的。考虑以下情况:
- 无限内存,无限内核内存:这是默认行为。
- 无限内存,有限内核内存:当所有 cgroups 所需的内存量大于主机上实际存在的内存量时,这种情况是合适的。你可以配置内核内存,使其永远不会超过主机上可用的量,而需要更多内存的容器需要等待。
- 有限内存,无限内核内存:总内存受限,但内核内存不受限。
- 有限内存,有限内核内存:限制用户内存和内核内存对于调试内存相关问题非常有用。如果容器使用了超出预期的任一类型内存,它会因内存不足而崩溃,而不会影响其他容器或主机。在此设置下,如果内核内存限制低于用户内存限制,内核内存耗尽会导致容器发生 OOM 错误。如果内核内存限制高于用户内存限制,则内核限制不会导致容器发生 OOM。
启用内核内存限制时,主机将按进程跟踪“高水位标记”统计信息,这样你就可以跟踪哪些进程(在此情况下为容器)正在使用过多内存。可以通过在主机上查看 /proc/<PID>/status
来查看每个进程的信息。
CPU
默认情况下,每个容器对主机 CPU 周期的访问是无限的。你可以设置各种约束来限制给定容器对主机 CPU 周期的访问。大多数用户使用并配置默认 CFS 调度器。你也可以配置实时调度器。
配置默认 CFS 调度器
CFS 是用于普通 Linux 进程的 Linux 内核 CPU 调度器。几个运行时标志允许你配置容器对 CPU 资源的访问量。当你使用这些设置时,Docker 会修改主机上容器 cgroup 的设置。
选项 | 描述 |
---|---|
--cpus=<值> | 指定容器可以使用多少可用的 CPU 资源。例如,如果主机有两个 CPU,并且你设置了 --cpus="1.5" ,则容器最多保证使用一核半 CPU。这等同于设置 --cpu-period="100000" 和 --cpu-quota="150000" 。 |
--cpu-period=<值> | 指定 CPU CFS 调度周期,与 --cpu-quota 一起使用。默认值为 100000 微秒(100 毫秒)。大多数用户不会更改此默认值。对于大多数用例,--cpus 是一个更方便的替代方法。 |
--cpu-quota=<值> | 对容器强制执行 CPU CFS 配额。容器在被限制之前,在每个 --cpu-period 内允许使用的微秒数。因此,它充当了有效的上限。对于大多数用例,--cpus 是一个更方便的替代方法。 |
--cpuset-cpus | 限制容器可以使用的特定 CPU 或核心。如果您有多个 CPU,则可以使用逗号分隔的列表或连字符分隔的范围来指定容器可以使用的 CPU。第一个 CPU 的编号是 0。有效值可能是 0-3 (表示使用第一个、第二个、第三个和第四个 CPU)或 1,3 (表示使用第二个和第四个 CPU)。 |
--cpu-shares | 将此标志设置为大于或小于默认值 1024 的值,以增加或减少容器的权重,从而使其可以访问主机机器 CPU 周期中更大或更小的比例。这仅在 CPU 周期受限时生效。当有充足的 CPU 周期可用时,所有容器都会按需使用尽可能多的 CPU。从这个意义上说,这是一个软限制。--cpu-shares 不会阻止容器在 Swarm 模式下被调度。它会根据可用的 CPU 周期优先处理容器的 CPU 资源。它不保证或预留任何特定的 CPU 访问。 |
如果您只有 1 个 CPU,以下每个命令都保证容器每秒最多使用 50% 的 CPU。
$ docker run -it --cpus=".5" ubuntu /bin/bash
这等同于手动指定 --cpu-period
和 --cpu-quota
;
$ docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash
配置实时调度器
您可以配置容器使用实时调度程序,用于无法使用 CFS 调度程序的任务。在配置 Docker 守护程序或配置单个容器之前,您需要确保主机机器的内核已正确配置。
警告
CPU 调度和优先级设置是高级的内核级功能。大多数用户无需更改这些默认值。错误地设置这些值可能会导致您的主机系统不稳定或无法使用。
配置主机机器的内核
通过运行命令 zcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHED
或检查文件 /sys/fs/cgroup/cpu.rt_runtime_us
是否存在,来验证 Linux 内核中是否启用了 CONFIG_RT_GROUP_SCHED
。有关配置内核实时调度程序的指南,请查阅您的操作系统的文档。
配置 Docker 守护程序
要使用实时调度程序运行容器,请使用 --cpu-rt-runtime
标志启动 Docker 守护程序,该标志设置为每个运行时周期为实时任务保留的最大微秒数。例如,在默认周期为 1000000 微秒(1 秒)的情况下,设置 --cpu-rt-runtime=950000
可确保使用实时调度程序的容器在每个 1000000 微秒周期内可以运行 950000 微秒,为非实时任务至少留下 50000 微秒。要在使用 systemd
的系统上使此配置永久生效,请为 docker
服务创建一个 systemd 单元文件。例如,请参阅关于如何使用systemd 单元文件为守护程序配置代理的说明。
配置单个容器
使用 docker run
启动容器时,您可以传递多个标志来控制容器的 CPU 优先级。有关适当值的信息,请查阅您的操作系统文档或 ulimit
命令。
选项 | 描述 |
---|---|
--cap-add=sys_nice | 授予容器 CAP_SYS_NICE 能力,这允许容器提高进程的 nice 值、设置实时调度策略、设置 CPU 亲和性以及执行其他操作。 |
--cpu-rt-runtime=<value> | 在 Docker 守护程序的实时调度周期内,容器可以以实时优先级运行的最大微秒数。您还需要 --cap-add=sys_nice 标志。 |
--ulimit rtprio=<value> | 允许容器使用的最大实时优先级。您还需要 --cap-add=sys_nice 标志。 |
以下示例命令在 debian:jessie
容器上设置了这三个标志。
$ docker run -it \
--cpu-rt-runtime=950000 \
--ulimit rtprio=99 \
--cap-add=sys_nice \
debian:jessie
如果内核或 Docker 守护程序配置不正确,将会发生错误。
GPU
访问 NVIDIA GPU
前提条件
访问官方 NVIDIA 驱动程序页面以下载并安装正确的驱动程序。完成后请重新启动您的系统。
验证您的 GPU 是否正在运行且可访问。
安装 nvidia-container-toolkit
遵循官方的 NVIDIA Container Toolkit 安装说明。
公开 GPU 以供使用
启动容器时包含 --gpus
标志即可访问 GPU 资源。指定要使用的 GPU 数量。例如:
$ docker run -it --rm --gpus all ubuntu nvidia-smi
公开所有可用的 GPU 并返回类似于以下内容的结果:
+-------------------------------------------------------------------------------+
| NVIDIA-SMI 384.130 Driver Version: 384.130 |
|-------------------------------+----------------------+------------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+========================|
| 0 GRID K520 Off | 00000000:00:03.0 Off | N/A |
| N/A 36C P0 39W / 125W | 0MiB / 4036MiB | 0% Default |
+-------------------------------+----------------------+------------------------+
+-------------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|===============================================================================|
| No running processes found |
+-------------------------------------------------------------------------------+
使用 device
选项指定 GPU。例如:
$ docker run -it --rm --gpus device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a ubuntu nvidia-smi
公开该特定 GPU。
$ docker run -it --rm --gpus '"device=0,2"' ubuntu nvidia-smi
公开第一个和第三个 GPU。
注意
NVIDIA GPU 只能由运行单个引擎的系统访问。
设置 NVIDIA 能力
您可以手动设置能力。例如,在 Ubuntu 上,您可以运行以下命令:
$ docker run --gpus 'all,capabilities=utility' --rm ubuntu nvidia-smi
这将启用 utility
驱动程序能力,该能力会将 nvidia-smi
工具添加到容器中。
能力以及其他配置可以通过环境变量在镜像中设置。有关有效变量的更多信息,请参阅 nvidia-container-toolkit 文档。这些变量可以在 Dockerfile 中设置。
您也可以使用 CUDA 镜像,它会自动设置这些变量。请参阅官方的 CUDA 镜像 NGC 目录页面。