Ресурсы в Kubernetes. Часть 1: Память (Memory)

Чтобы 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).



2023-01-03T00:07:01
DevOps