Чтобы Kubernetes
смог максимально эффективно использовать доступную инфраструктуру и корректно выделить ресурсы, необходимые для работы вашего приложения, вам следует указать требования в ресурсам каждого контейнера. В данный момент есть возможность задавать два типа требований (requests
и limits
) для двух типов ресурсов — памяти (memory
) и процессора (CPU
). В данной статье рассмотрим requests
и limits
применительно к памяти — давайте разберемся!
При описании пода (Pods
) для каждого из его контейнеров могут быть заданы требования к ресурсам в следующем формате:
...
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "2"
memory: 2Gi
...
Тип требований requests
используется в Kubernetes
планировщиком для корректного размещения и запуска подов в существующей инфраструктуре, как бы говоря “запусти контейнеры данного пода там, где есть достаточное количество запрощенных ресурсов”. Limits
— жесткое ограничение ресурсов, доступных контейнеру и среде его выполнения (тут и далее имеется в виду Docker container runtime). Превышение указанных лимитов (limits
) ресурсов зачастую приводит к троттлингу или остановке (termination) контейнера.
Если значение requests
для контейнера не указано, то по умолчанию будет использоваться значение установленное в limits
. Если же не указано значение limits
, то по умолчанию это значение будет равно 0 (если верить документации — неограниченно. На самом деле ограничивается ресурсами узла, на котором запускается под).
Возникает вопрос, стоит ли указывать значения limits
больше, чем requests
? Если ваше приложение стабильно использует предсказуемый объем оперативной памяти, то устанавливать разные значения параметров requests
и limits
для памяти нет смысла. В случае с CPU, разница между заданными значениями requests
и limits
может не устанавливаться (при условии, что эти же ресурсы не используются другими контейнерами и их не нужно “делить”).
Если вы новичок в Kubernetes
, для начала лучше всего использовать значения limits
точно такие же как и requests
— это обеспечит так называемый “гарантированный класс качества сервиса” (Guaranteed QoS
class, об этих классах чуть ниже). С другой стороны, класс Burstable QoS
потенциально позволяет более эффективно использовать ресурсы инфраструктуры, правда, за счет большей непредсказуемости — например, рост CPU-latency может повлиять на остальные поды/контейнеры, запущенные на том же рабочем узле (ноде).
В Kubernetes
QoS классы используются в соответствии с наличием и конфигурацией requests
и limits
(детальное описание):
- если для всех контейнеров пода установлены отличные от 0
requests
иlimits
для всех типов ресурсов, и эти значения равны, то под будет принадлежать к классуGuaranteed
; - если для одного или нескольких контейнера пода установлены отличные от 0
requests
иlimits
для одного или всех типов ресурсов и эти значения не равны, то под будет принадлежать к классуBurstable
; - если для всех контейнеров пода не установлены значения
requests
иlimits
для всех типов ресурсов, то поду будет присвоен классBest-Effort
.
Поды класса Best-Effort
обладают наименьшим приоритетом. Они могут использовать любое количество свободной памяти, доступное на рабочем узле, но будут остановлены в первую очередь, если система испытывает недостаток памяти (under memory pressure). Поды класса Burstable
обычно имеют некоторое гарантированное количество ресурсов (благодаря requests
), но могут использовать больше ресурсов (если такие доступны). Если система испытывает недостаток памяти (и остановка подов с классом Best-Effort
не помогла), то поды данного класса, которые превысили значение заданное в requests
будут остановлены. Класс Guaranteed
обладает максимальным приоритетом, и поды данного класса будут остановлены только если они используют больше ресурсов, чем установлено в limits
.
Итак, что же означает память (memory
) в данном контексте? В нашем случае, это общее значение размера страниц памяти (Resident set size, RSS) и использования кэша страниц (page cache) контейнерами.
Примечание. В “чистом” docker’е в это значение также входит своп (swap), который предусмотрительно отключен в Kubernetes
.
RSS — размер страниц памяти, выделенных процессу операционной системой и в настоящее время находящихся в ОЗУ. Например, для Java процесса это heap (куча), non-heap (стек) память, оff-heap (она же native memory) и т. д.
Кэш страниц — иногда также называемый дисковый кэшем, используется для кеширования блоков с HDD/SSD. Все операции ввода/вывода обычно происходят через этот кэш (из соображений производительности). Чем больше данных читает/записывает ваше приложение на диск, тем больший объем памяти необходим для кэша страниц. Ядро будет использовать доступную память для кэша страниц, но будет освобождать ее, если память понадобится в другом месте/процессе — таким образом производительность вашего приложения может снижаться при недостаточном объеме оперативной памяти.
Исходя из документации docker, можно сказать, что размер кэша страниц, используемых контейнером, может сильно отличаться в зависимости от того, могут ли некоторые файлы “поделены” между несколькими контейнерами, запущенными на одном рабочем узле (достигается благодаря overlayfs storage driver).
Значения параметров requests
и limits
измеряются в байтах, однако можно использовать и суффиксы. К примеру, настройка памяти JVM Xmx1g
(1024³ bytes) будет соответствовать 1Gi
в спецификации контейнера.
Ограничения по памяти (limits
) с точки зрения Kubernetes
считаются “несжимаемыми” (non-compressible), следовательно при превышении этих ограничений троттлинг невозможен — ядро будет агрессивно очищать кэш страниц (для освобождения ресурсов / достижения желаемого состояния рабочего узла) и контейнеры к конце концов могут быть остановлены (прерваны) хорошо известным Linux Out of Memory (OOM) Killer.
Для хорошей настройки приложения часто приходится эмпирическим путем подбирать необходимые значения requests
и limits
и менять их на протяжении всего жизненного цикла приложения, поэтому не стоит пренебрегать сбором метрик, мониторингом и оповещением о использовании ресурсов.
На этом все, в следующей статье рассмотрим requests
и limits
применительно к использованию процессора (CPU
).