Источник: Ограничение ресурсов контейнеров
По умолчанию ресурсы контейнера ничем не ограничены, и он может использовать столько ресурсов, сколько ему позволяет планировщик ядра хоста. Docker позволяет управлять объёмом памяти и процессорного времени, доступным контейнеру, с помощью параметров команды docker run. Ниже рассказано, когда следует задавать такие ограничения и к каким последствиям это может привести.
Для работы многих из этих возможностей ядро должно поддерживать соответствующие функции Linux. Проверить их наличие можно командой docker info. Если какая-либо возможность отключена в ядре, в конце вывода может появиться такое предупреждение:
WARNING: No swap limit support
Чтобы включить необходимые возможности, обратитесь к документации своей операционной системы. Подробнее.
Важно не допускать, чтобы работающий контейнер потреблял слишком много памяти хоста. Если ядро Linux обнаруживает, что памяти недостаточно для выполнения важных системных функций, возникает исключение из-за нехватки памяти (OOME), после чего ядро начинает завершать процессы, чтобы освободить память. Завершён может быть любой процесс, в том числе Docker и другие важные приложения. Если ядро выберет не тот процесс, вся система может фактически перестать работать.
Docker старается снизить этот риск, изменяя OOM-приоритет демона Docker так, чтобы вероятность его завершения была ниже, чем у других системных процессов. OOM-приоритет контейнеров при этом не изменяется. Поэтому ядро с большей вероятностью завершит отдельный контейнер, а не демон Docker или другой системный процесс. Не пытайтесь обходить эти меры защиты: не задавайте демону или контейнеру чрезмерно большое отрицательное значение --oom-score-adj и не используйте для контейнера параметр --oom-kill-disable.
Подробнее об управлении OOM в ядре Linux см. в документации «Управление при нехватке памяти».
Снизить риск нестабильности системы из-за OOME можно следующим образом:
- Перед развёртыванием приложения в рабочей среде проведите тесты и определите, сколько памяти ему требуется.
- Запускайте приложение только на хостах с достаточным количеством ресурсов.
- Ограничьте объём памяти, доступный контейнеру, как описано ниже.
- Внимательно настраивайте подкачку на хостах Docker. Подкачка медленнее оперативной памяти и снижает производительность, но может дать системе запас при нехватке памяти.
- Рассмотрите возможность преобразовать контейнер в сервис и с помощью ограничений уровня сервиса и меток узлов обеспечить запуск приложения только на хостах с достаточным объёмом памяти.
Docker поддерживает жёсткие ограничения памяти, которые не позволяют контейнеру использовать больше заданного объёма пользовательской или системной памяти, и мягкие ограничения. При мягком ограничении контейнер может использовать столько памяти, сколько ему требуется, пока не возникнут определённые условия — например, ядро обнаружит нехватку памяти или конкуренцию за неё на хосте. Некоторые параметры действуют по-разному в зависимости от того, используются ли они отдельно или вместе.
Большинство этих параметров принимают положительное целое число с суффиксом b, k, m или g, обозначающим байты, килобайты, мегабайты или гигабайты соответственно.
| Параметр | Описание |
|---|---|
-m или --memory= |
Максимальный объём памяти, который может использовать контейнер. Минимально допустимое значение — 4m (4 мегабайта). |
--memory-swap* |
Объём памяти, который контейнер может выгрузить на диск. См. подробное описание --memory-swap. |
--memory-swappiness |
По умолчанию ядро хоста может выгружать в пространство подкачки некоторую долю анонимных страниц, используемых контейнером. Параметр --memory-swappiness принимает значение от 0 до 100 и позволяет регулировать эту долю. См. подробное описание --memory-swappiness. |
--memory-reservation |
Позволяет задать мягкое ограничение меньше значения --memory. Оно начинает действовать, когда Docker обнаруживает конкуренцию за память или её нехватку на хосте. Чтобы --memory-reservation имел приоритет, его значение должно быть меньше --memory. Поскольку это мягкое ограничение, оно не гарантирует, что контейнер его не превысит. |
--kernel-memory |
Максимальный объём памяти ядра, который может использовать контейнер. Минимально допустимое значение — 4m. Память ядра нельзя выгрузить в пространство подкачки, поэтому контейнер, которому её не хватает, может заблокировать ресурсы хоста. Это способно повлиять как на сам хост, так и на другие контейнеры. См. подробное описание --kernel-memory. |
--oom-kill-disable |
По умолчанию при нехватке памяти (OOM) ядро завершает процессы в контейнере. Изменить это поведение позволяет параметр --oom-kill-disable. Отключайте OOM killer только для контейнеров, которым также задан параметр -m/--memory. Если флаг -m не установлен, на хосте может закончиться память, и ядру придётся завершать системные процессы хоста, чтобы её освободить. |
Дополнительные сведения о cgroups и памяти см. в документации «Контроллер ресурсов памяти».
--memory-swap — это модифицирующий флаг, который имеет смысл только вместе с --memory. Подкачка позволяет контейнеру записывать избыточные данные из памяти на диск, когда вся доступная ему оперативная память исчерпана. Приложения, которые часто выгружают память на диск, работают медленнее.
Значение этого параметра может давать неоднозначные результаты:
- Если
--memory-swapзадан положительным целым числом, необходимо также задать--memory. Параметр--memory-swapопределяет суммарный объём оперативной памяти и пространства подкачки, а--memory— объём оперативной памяти без учёта подкачки. Например, при--memory="300m"и--memory-swap="1g"контейнер может использовать 300 МБ оперативной памяти и 700 МБ (1g - 300m) пространства подкачки. - Если
--memory-swapравен0, параметр игнорируется и считается незаданным. - Если
--memory-swapравен--memory, а--memoryзадан положительным целым числом, контейнер не может использовать пространство подкачки. См. раздел Запрет использования подкачки контейнером. - Если
--memory-swapне задан, а--memoryзадан, контейнер может использовать пространство подкачки в объёме, вдвое превышающем значение--memory, при условии что подкачка настроена на хосте. Например, если задано--memory="300m", а--memory-swapне задан, контейнер может использовать 300 МБ оперативной памяти и 600 МБ пространства подкачки. - Если явно задать
--memory-swap=-1, контейнер сможет использовать пространство подкачки без ограничений, в пределах доступного на хосте объёма. - Внутри контейнера такие инструменты, как
free, показывают доступное пространство подкачки хоста, а не контейнера. Не полагайтесь на выводfreeили подобных инструментов при определении наличия подкачки.
Если параметрам --memory и --memory-swap присвоено одинаковое значение, контейнер не сможет использовать пространство подкачки. Причина в том, что --memory-swap задаёт суммарный объём оперативной памяти и пространства подкачки, а --memory — только объём физической памяти.
- Значение 0 отключает выгрузку анонимных страниц в пространство подкачки.
- Значение 100 разрешает выгружать все анонимные страницы.
- Если
--memory-swappinessне задан, по умолчанию наследуется значение хоста.
Ограничения памяти ядра задаются с учётом общего объёма памяти, выделенного контейнеру. Рассмотрим следующие сценарии:
- Неограниченная общая память, неограниченная память ядра. Поведение по умолчанию.
- Неограниченная общая память, ограниченная память ядра. Такой вариант подходит, если всем cgroups суммарно требуется больше памяти, чем физически имеется на хосте. Можно настроить память ядра так, чтобы её потребление никогда не превышало доступный на хосте объём; контейнерам, которым требуется больше памяти, придётся ждать её освобождения.
- Ограниченная общая память, неограниченная память ядра. Общий объём памяти ограничен, но объём памяти ядра — нет.
- Ограниченная общая память, ограниченная память ядра. Ограничение как пользовательской памяти, так и памяти ядра может быть полезно при отладке проблем с памятью. Если контейнер неожиданно потребляет слишком много памяти любого из этих типов, память закончится только у него, не затрагивая другие контейнеры и хост. Если при такой настройке лимит памяти ядра ниже лимита пользовательской памяти, исчерпание памяти ядра приведёт к ошибке OOM в контейнере. Если лимит памяти ядра выше лимита пользовательской памяти, контейнер не столкнётся с OOM из-за ограничения памяти ядра.
При включении любого ограничения памяти ядра хост начинает собирать для каждого процесса статистику максимального потребления. Благодаря этому можно определить, какие процессы — в данном случае контейнеры — используют слишком много памяти. Статистику отдельного процесса можно посмотреть в файле /proc/<PID>/status на хосте.
По умолчанию доступ каждого контейнера к процессорному времени хоста не ограничен. Можно задать различные ограничения на использование процессора отдельным контейнером. Большинство пользователей применяют и настраивают планировщик CFS по умолчанию. В Docker 1.13 и новее также можно настроить планировщик реального времени.
CFS — планировщик процессорного времени ядра Linux для обычных процессов. Несколько параметров запуска позволяют настроить объём процессорных ресурсов, доступных контейнеру. При использовании этих параметров Docker изменяет настройки cgroup контейнера на хосте.
| Параметр | Описание |
|---|---|
--cpus=<value> |
Задаёт долю доступных процессорных ресурсов, которую может использовать контейнер. Например, если на хосте два процессора и указано --cpus="1.5", контейнер гарантированно сможет использовать не более полутора процессоров. Это эквивалентно сочетанию --cpu-period="100000" и --cpu-quota="150000". Доступен в Docker 1.13 и новее. |
--cpu-period=<value> |
Задаёт период планировщика CPU CFS и используется вместе с --cpu-quota. Значение по умолчанию — 100 микросекунд. Большинству пользователей менять его не требуется. В Docker 1.13 и новее вместо этого параметра используйте --cpus. |
--cpu-quota=<value> |
Задаёт квоту CPU CFS для контейнера: количество микросекунд в пределах каждого периода --cpu-period, по истечении которого использование процессора контейнером ограничивается. Таким образом, параметр устанавливает фактический верхний предел. В Docker 1.13 и новее вместо него используйте --cpus. |
--cpuset-cpus |
Ограничивает набор процессоров или ядер, которые может использовать контейнер. Если процессоров несколько, их задают списком через запятую или диапазоном через дефис. Нумерация начинается с 0. Например, значение 0-3 разрешает использовать первый, второй, третий и четвёртый процессоры, а 1,3 — второй и четвёртый. |
--cpu-shares |
Значение больше или меньше стандартного значения 1024 соответственно увеличивает или уменьшает вес контейнера и предоставляет ему большую или меньшую долю процессорного времени хоста. Ограничение применяется только при нехватке процессорного времени. Если его достаточно, каждый контейнер использует столько, сколько ему необходимо. Поэтому это мягкое ограничение. Параметр --cpu-shares не мешает планировать запуск контейнеров в режиме Swarm. Он задаёт приоритет контейнера при распределении доступного процессорного времени, но не гарантирует и не резервирует определённую долю ресурсов процессора. |
Если имеется один процессор, каждая из следующих команд гарантирует контейнеру не более 50 % его времени в секунду.
Docker 1.13 и новее:
docker run -it --cpus=".5" ubuntu /bin/bash
Docker 1.12 и старше:
$ docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash
В Docker 1.13 и новее контейнер можно настроить на использование планировщика реального времени для задач, которым не подходит CFS. Прежде чем настраивать демон Docker или отдельные контейнеры, убедитесь, что ядро хоста настроено правильно.
Предупреждение. Планирование и приоритизация процессорного времени — расширенные функции уровня ядра. Большинству пользователей не требуется изменять их стандартные значения. Неправильные настройки могут привести к нестабильности или полной неработоспособности хоста.
Убедитесь, что в ядре Linux включён параметр CONFIG_RT_GROUP_SCHED. Для этого выполните zcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHED или проверьте наличие файла /sys/fs/cgroup/cpu.rt_runtime_us. Инструкции по настройке планировщика реального времени ядра приведены в документации вашей операционной системы.
Чтобы запускать контейнеры с планировщиком реального времени, запустите демон Docker с флагом --cpu-rt-runtime, указав максимальное количество микросекунд, зарезервированных для задач реального времени в каждом периоде работы планировщика. Например, если период по умолчанию равен 1000000 микросекунд (1 секунде), значение --cpu-rt-runtime=950000 позволяет контейнерам с планировщиком реального времени выполняться 950000 микросекунд из каждого периода длительностью 1000000 микросекунд. При этом для остальных задач остаётся не менее 50000 микросекунд. Чтобы сделать такую конфигурацию постоянной в системах с systemd, см. раздел Управление и настройка Docker с помощью systemd.
При запуске контейнера командой docker run можно передать несколько флагов, управляющих его процессорным приоритетом. Подходящие значения описаны в документации вашей операционной системы и в справке по команде ulimit.
| Параметр | Описание |
|---|---|
--cap-add=sys_nice |
Предоставляет контейнеру системную возможность CAP_SYS_NICE, которая позволяет увеличивать значения nice процессов, задавать политики планирования реального времени, устанавливать привязку к процессорам и выполнять другие операции. |
--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 настроены неправильно, возникнет ошибка.