资源限制
默认情况下,容器没有资源限制,可以使用主机内核调度器允许的任意数量的给定资源。Docker 提供了控制容器可以使用多少内存或 CPU 的方法,通过设置 docker run 命令的运行时配置标志来实现。本节详细介绍了何时应该设置此类限制以及设置它们的可能影响。
这些功能中的许多需要您的内核支持 Linux 功能。要检查支持情况,您可以使用
docker info 命令。如果某个功能在您的内核中被禁用,您可能会在输出末尾看到类似如下的警告:
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 主机的交换空间时请谨慎。交换空间比内存慢,但可以提供一个缓冲区,防止系统内存耗尽。
- 考虑将您的容器转换为 服务,并使用服务级约束 和节点标签以确保应用程序仅在具有足够内存的主机上运行
限制容器对内存的访问
Docker 可以强制执行硬限制或软限制的内存限制。
- 硬限制使容器无法使用超过固定数量的内存。
- 软限制允许容器根据需要使用尽可能多的内存,除非满足特定条件,例如当内核检测到主机内存不足或主机上存在资源争用时。
其中一些选项在单独使用或设置多个选项时会产生不同的效果。
这些选项中的大多数接受一个正整数,后跟 b、k、m、g 后缀,以表示字节、千字节、兆字节或吉字节。
| 选项 | 描述 |
|---|---|
-m or --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 终止程序。如果未设置 -m 标志,主机可能会耗尽内存,内核可能需要终止主机系统的进程以释放内存。 |
有关 cgroups 和内存的更多信息,请参阅 内存资源控制器的文档。
--memory-swap 详情
--memory-swap 是一个修饰符标志,只有在设置了 --memory 时才有意义。使用 swap 允许容器在耗尽所有可用的 RAM 时,将多余的内存需求写入磁盘。对于频繁将内存交换到磁盘的应用程序,会有性能损失。
其设置可能会产生复杂的影响:
如果将
--memory-swap设置为正整数,则必须同时设置--memory和--memory-swap。--memory-swap表示可使用的内存和交换空间的总和,而--memory控制非交换内存的使用量。因此,如果--memory="300m"和--memory-swap="1g",则 容器可以使用 300m 的内存和 700m (1g - 300m) 的交换空间。如果将
--memory-swap设置为0,则忽略该设置,并且该值被视为未设置。如果
--memory-swap设置为与--memory相同的值,并且--memory设置为正整数,容器将无法访问 swap。 请参阅 防止容器使用 swap。如果
--memory-swap未设置,且--memory已设置,则容器可以使用--memory设置的交换空间大小,前提是主机容器已配置交换内存。例如,如果--memory="300m"和--memory-swap未设置,则容器总共可以使用 600m 的内存和交换空间。如果将
--memory-swap显式设置为-1,则容器可以使用无限量的交换空间,最多可达主机系统上可用的总量。在容器内部,像
free这样的工具报告的是主机上可用的交换空间,而不是容器内部可用的。不要依赖free或类似工具的输出来判断是否存在交换空间。
防止容器使用 swap
如果 --memory 和 --memory-swap 被设置为相同的值,这将阻止
容器使用任何交换空间。这是因为 --memory-swap 是可以使用的
内存和交换空间的总和,而 --memory 仅仅是
可以使用的物理内存量。
--memory-swappiness 详情
- 值为 0 表示关闭匿名页面交换。
- A value of 100 sets all anonymous pages as swappable.
- 默认情况下,如果您不设置
--memory-swappiness,该值将 从主机继承。
--kernel-memory 详情
内核内存限制是以分配给容器的总内存来表示的。考虑以下场景:
- 无限内存,无限内核内存:这是默认行为。
- 无限内存,受限内核内存:当所有 cgroups 需要的内存总量大于主机实际存在的内存量时,这是合适的。您可以配置内核内存使其永远不超过主机上可用的内存量,需要更多内存的容器需要等待。
- 有限的内存,无限的内核内存:总体内存是有限的,但内核内存不是。
- 有限的内存,有限的内核内存:限制用户内存和内核内存对于调试内存相关问题非常有用。如果一个容器使用了意外数量的任何一种内存,它会在不影响其他容器或主机的情况下耗尽内存。在此设置下,如果内核内存限制低于用户内存限制,耗尽内核内存会导致容器遇到OOM错误。如果内核内存限制高于用户内存限制,内核限制不会导致容器遇到OOM。
当您启用内核内存限制时,主机会按进程跟踪“高水位标记”统计信息,以便您可以跟踪哪些进程(在此情况下为容器)正在使用过多的内存。可以通过在主机上查看 /proc/<PID>/status 来查看每个进程的情况。
CPU
默认情况下,每个容器对主机 CPU 周期的访问是无限的。 您可以设置各种约束来限制给定容器对主机 CPU 周期的访问。大多数用户使用和配置 默认 CFS 调度器。您还可以 配置 实时调度器。
配置默认 CFS 调度器
CFS 是 Linux 内核中用于普通 Linux 进程的 CPU 调度器。多个运行时标志允许您配置容器对 CPU 资源的访问量。当您使用这些设置时,Docker 会修改主机上容器 cgroup 的设置。
| 选项 | 描述 |
|---|---|
--cpus=<value> | 指定容器可以使用多少可用的 CPU 资源。例如,如果主机有两颗 CPU,并且您设置了 --cpus="1.5",那么容器最多可以使用一颗半 CPU。这相当于设置 --cpu-period="100000" 和 --cpu-quota="150000"。 |
--cpu-period=<value> | 指定 CPU CFS 调度器周期,它与 --cpu-quota 一起使用。默认为 100000 微秒(100 毫秒)。大多数用户无需更改此默认值。对于大多数用例,--cpus 是更方便的替代方案。 |
--cpu-quota=<value> | 对容器实施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调度和优先级是高级内核级功能。大多数用户无需更改这些默认值。错误设置这些值可能导致主机系统变得不稳定或无法使用。
配置主机的内核
通过运行 CONFIG_RT_GROUP_SCHED 或检查文件 /sys/fs/cgroup/cpu.rt_runtime_us 是否存在,验证 Linux 内核中是否启用了 zcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHED。有关配置内核实时调度器的指导,请参阅您操作系统的文档。
配置 Docker 守护进程
要使用实时调度器运行容器,请运行 Docker 守护进程,并将 --cpu-rt-runtime 标志设置为每个运行周期为实时任务保留的最大微秒数。例如,在默认周期为 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 目录页面。