![](https://habrastorage.org/webt/un/jw/lq/unjwlq5olilzjz66vu5q3ypsq3a.png)
Эту статью я написал в противовес статье “For the love of god, stop using CPU limits on Kubernetes” (Ради всего святого, прекратите использовать в Kubernetes лимиты CPU).
Мне та статья понравилась, и я считаю её хорошим чтивом. Более того, я согласен с высказанными в ней рекомендациями относительно установки объёмов запрашиваемой памяти и её лимитов для контейнеров, а также с советом всегда устанавливать запросы на выделение CPU.
При этом моё несогласие, явно выраженное в противоположном по смыслу заголовке, связано с той категоричностью, с которой в итоге автор рекомендует не устанавливать лимиты потребления CPU.
Краткое обобщение по управлению в Kubernetes ресурсами для контейнеров: «запрос» CPU – это минимальный объём вычислительной мощности процессора, доступный контейнеру в процессе выполнения, а «лимит» CPU – это максимальный объём мощности процессора, который контейнер может использовать.
Для тех же, кто желает глубже разобраться в принципах работы Kubernetes, я рекомендую публикацию Стефани Лай “Layer-by-Layer Cgroup in Kubernetes” и подкреплённую данными серию из трёх статей (англ.), написанную Шоном Лев-Раном и Широм Монетером.
![](https://habrastorage.org/webt/0z/8j/wh/0z8jwhz4odujjzeu_97puaohamu.png)
Kubernetes позволяет разработчикам ПО и системным администраторам взаимодействовать в плане настройки минимальных резервов и максимальных пределов использования таких ресурсов, как CPU, память и временное хранилище.
Идея отказа от лимитов CPU стоит на том, что установка такого лимита имеет негативный эффект, не позволяя контейнеру задействовать неиспользуемое (и незарезервированное) время CPU в узле и подвергая его ненужному и потенциально вредному троттлингу процессора.
Однако после обширного исследования в ходе написания “Infinite scaling with containers and Kubernetes” я обнаружил явные причины для того, чтобы в большинстве случаев все же лимиты потребления CPU устанавливать, и ниже опишу основания этой своей еретической идеи.
▍ Причина #1: отказоустойчивость во время троттлинга
Обновлено 9/30: изначально этот раздел назывался «Пробы контейнеров не поддаются сжатию», что размывало его основную суть: разработка контейнеров с установкой лимитов CPU делает их более устойчивыми, в том числе при троттлинге. Создание проб для проверки активности контейнеров (liveness probes), которые более устойчивы к троттлингу, и проб готовности (readiness probes), которые при троттлинге более точны, представляет лишь два из множества возможных положительных следствий создания контейнеров с лимитами CPU.
Когда контейнер пытается использовать больше CPU, чем доступно – игнорируя предварительные смягчающие меры, вроде перемещения подов между узлами – узел «троттлит» выделение CPU для контейнера, по сути, замедляя его выполнение. Именно поэтому в документации Kubernetes процессоры обозначены как «сжимаемые» ресурсы.
Из этого вытекает распространённое предположение, что контейнеры могут безопасно переживать события троттлинга. Однако это предположение натыкается на неизбежное следствие, связанное с пробами Kubernetes.
Когда kubelet троттлит контейнер, это может воздействовать на все потоки внутри данного контейнера, включая те, что обрабатывают пробы активности и готовности.
Даже если в контейнере установлен запрос CPU, его излишне высокая внутренняя нагрузка может потреблять непропорциональную долю этого выделения, замедляя реакцию проб.
Последующие сбои или таймауты пробы готовности приводят к тому, что kubelet перенаправляет трафик от этого контейнера. Если же сбои/таймауты касаются пробы активности, то kubelet завершает контейнер.
![](https://habrastorage.org/webt/lx/cv/sy/lxcvsy6zkbcp-w-betzfj8thixe.png)
Сохранение контроля использования CPU также полезно для вашего контейнера. Когда он испытывает троттлинг, излишне активный поток может препятствовать выделению времени CPU для других потоков, ставя активность всего контейнера под угрозу.
Я обширно раскрыл тему хороших практик при проектировании проб контейнеров (англ.), которые увеличивают вероятность реагирования проб на kubelet, когда контейнер находится в «стрессовом» состоянии. Но эта вероятность уменьшается прямо пропорционально излишнему использованию контейнером мощности CPU поверх установленного запросом объёма.
[Если троттлинг для проб вреден, то установка лимитов CPU только всё ухудшит, поскольку число случаев троттлинга возрастёт].
Смысл не в том, что запуск в продакшен контейнеров с лимитами CPU сделает их более устойчивыми к событиям троттлинга. Естественно, этого не произойдёт. Ключевой аспект здесь заключается в изначальном создании контейнеров с лимитами CPU в течение всей стадии разработки.
Последующее наблюдение нежелательного поведения под троттлингом – предполагая систематический запуск контейнера под высокой нагрузкой в ограниченных по ресурсам узлах – помогает принимать важные решения относительно дизайна и реализации.
Подобные наблюдения могут, например, предоставить возможность:
- задействовать больше внутреннего состояния контейнера в пробе готовности, и тем самым помочь kubelet перенаправлять трафик на другие, менее нагруженные, реплики;
- пересматривать дизайн и корректировать пулы внутренних потоков;
- представить дополнительные параметры настройки для системных администраторов.
С обратной же стороны, если создавать контейнеры без лимитов CPU, их поведение будет зависеть от наличия свободных циклов процессора в узле. Контейнер, который устойчиво хорошо работает в кластере разработки с низкой нагрузкой, может совсем иначе повести себя в условиях недоступности тех же объёмов свободного CPU.
Вывод: создавайте и тестируйте контейнеры с включённым лимитом потребления CPU, чтобы заставить их работать устойчиво в условиях троттлинга. Когда kubelet троттлит нагрузки, его не беспокоят возможные последствия для них.
▍ Причина #2: утрата статуса “Guaranteed“
Обновлено 9/28: этот раздел назывался «Повышенные штрафы для повышенных нагрузок» и ошибочно утверждал, что превышение установленного запроса CPU, влияет на алгоритм исключения подов для восполнения ресурсов узлов (node-pressure eviction).
Это косвенная причина, но её стоит упомянуть.
Настройка запросов и лимитов ресурсов для каждого контейнера в поде определяет класс качества обслуживания (QoS) этого пода:
- отсутствие запросов или лимитов классифицирует под как “BestEffort”. Этот QoS в данном случае исключается, поскольку мы изначально предположили, что для всех контейнеров запросы памяти и CPU установлены;
- если для контейнера определён только запрос CPU, но не лимит, тогда под классифицируется как “Burstable”;
- единственный способ получить для пода QoS “Guaranteed” – это установить идентичные значения запроса/лимита CPU и памяти для каждого контейнера пода.
Наиболее примечательный случай применения QoS – это “node-pressure eviction”, когда kubelet анализирует, какие поды нужно удалить из узла, чтобы освободить дополнительные ресурсы.
Guaranteed поды стоят в списке на удаление последними – при условии равных приоритетов подов – а Burstable, наоборот, исключаются первыми.
Имейте в виду, что анализ на предмет удаления подразумевает оценку только памяти, использования диска и индексных дескрипторов файловой системы. В этом плане технически может случиться так, что Burstable под превысит установленный запрос CPU, но в случае анализа на удаление всё равно будет оценён вровень с подами Guaranteed.
Менее распространённый (а может менее популярный) случай связан с использованием политик управления CPU, когда преимущества «политики Static» касаются только Guaranteed подов (использующих целочисленные запросы CPU). Рекомендую подробнее почитать о преимуществах использования CPU Manager, чтобы понять, как он содействует работе этих конкретных нагрузок.
Вывод: ещё раз скажу – стремитесь управлять использованием CPU внутри контейнера.
Отсутствие лимита CPU опускает под до QoS “Burstable”, лишая его привилегий в некоторых сценариях планирования и троттлинга.
▍ Причина #3: использование CPU и памяти связано
К лучшему это или к худшему, но CPU и память в вычислительной технике связаны неразрывно. Именно поэтому ни один облачный провайдер не предлагает виртуальные машины с непропорциональным соотношением мощностей CPU и объёма памяти, например, узел 16х2 (16 CPU и 2 гигабайта ОЗУ). Чаще всего соотношение этих ресурсов в вычислительных инстансах нацелено в обратном направлении, когда число процессоров составляет одну четвёртую или одну вторую от объёма памяти (в гигабайтах), например, 8х16, 16х64 или 32х64.
Для подобного правила есть множество практических причин, но, в общем, можно выделить две:
- для считывания и записи больших объёмов памяти требуется множество CPU;
- использование множества CPU означает использование множества потоков (сопровождающихся стеками потоков в памяти) и производство больших объёмов данных, которые должны помещаться в память (даже в случае их кэширования на пути в постоянное хранилище).
Кто-то может вообразить сценарии, в которых множество CPU передают вывод сразу на диск (без кэширования данных в памяти?). И всё же, если вы прилежно выполняли домашнюю работу в ходе разработки, сложно представить, как контейнер, запрашивающий 200m CPU и 256МБ ОЗУ, может эффективно использовать 2 CPU (в десять раз больше запрошенного количества), не требуя больше памяти (даже если не в 10 раз больше).
Вывод: исключение лимита на использование CPU (вплоть до лимита CPU всего узла) без позволения задействовать соответствующие дополнительные объёмы памяти не совпадает с паттерном использования большинства приложений. С точки зрения системы будет лучше последовать рекомендации следующего раздела.
▍ Причина #4: автоскейлы подов могут справляться лучше
Один из аргументов в пользу того, чтобы позволять контейнерам свободно превышать установленные лимиты CPU, гласит – если некоторая доля CPU узла не используется, можно дать подам возможность задействовать этот свободный ресурс.
Я с этим принципом согласен, но, как говорилось в предыдущем разделе, этот свободный ресурс CPU для поддержания какой-бы то ни было дополнительной работы, зачастую требует больше памяти.
Автоскейлеры подов прекрасно справляются с задачей расширения ресурсов подов — прогнозируемым и сбалансированным образом. Я подробно писал о них в статье “Infinite scaling with containers and Kubernetes”, так что здесь приведу лишь краткие тезисы:
- HPA и KPA могут увеличить число реплик пода, тем самым добавив больше агрегированных ресурсов CPU и памяти для всей рабочей нагрузки без побочного эффекта «гиперактивного» потока, выводящего пробы контейнера за рамки их операционного диапазона;
- VPA может увеличить общие лимиты для CPU и памяти, обеспечив для пода сбалансированную корректировку его запросов и лимитов ресурсов.
[Но автоскейлеры подов принимают решения на основе сопоставления использования ресурсов и запрошенных значений, а не лимитов].
Это так. Правда, к делу не относится.
Суть здесь в том, чтобы отвергнуть подход усиленного использования CPU в качестве механизма для выполнения большего объёма работы в пользу применения компонентов, специально для этой цели созданных.
Вывод: автоскейлеры подов предназначены для максимизации сбалансированного использования ресурсов. Несмотря на то, что предоставление контейнеру возможности превышать свой резерв CPU может в некоторых сценариях сыграть наруку, автоскейлинг подов в большинстве случаев справляется лучше, повышая лимиты ресурсов рациональным образом.
▍ Причина #5: гиперскейлеры требуют установки для контейнеров лимитов
Обновлено 9/30: изначально этот раздел назывался «Автоскейлеры кластеров не любят резких движений» и ошибочно утверждал, что использование CPU (сверх запросов CPU контейнерами) может запускать события автоскейлинга. Читатель Шир Монетер в разделе комментариев (см. в оригинале статьи) правильно указал, что это не так. В связи с этим я переписал раздел, чтобы оставить только часть об управляемых контейнерных службах.
Это лишь косвенная причина, но я всё же решил её сюда включить.
Если ваши рабочие нагрузки вдруг превысят возможности одного кластера, и вы решите использовать для запуска контейнеров управляемую контейнерную службу, например, Code Engine или ECS, то эти службы требуют установки лимитов CPU и памяти.
Данное требование помогает провайдерам реализовывать выделение ресурсов и работает в вашу пользу, поскольку вы платите за выделение ресурсов, а безграничное использование означает безграничную стоимость.
▍ Заключение
Преимущества использования свободных ресурсов CPU в узле кластера уменьшаются прямо пропорционально той степени, в которой вы на эти свободные ресурсы опираетесь.
Создание контейнеров без лимитов CPU зачастую ведёт к незримыми зависимостям от свободных циклов CPU, которые в некоторых средах могут оказаться недоступны. В результате пробы контейнеров вытесняются из жизненно важных циклов CPU, (слегка) возрастает вероятность удаления подов в случае недостатка ресурсов, а также происходит неравномерное выделение (неограниченного) объёма CPU для (ограниченного) объёма памяти.
Если цель исключения лимитов CPU состоит в том, чтобы максимизировать выделение его мощностей, а также ресурсов узла и кластера, то специально предназначенные для этих целей компоненты, такие как автоскейлеры подов и кластеров, зачастую справятся лучше, попутно повысив выделение памяти в соответствии с возрастанием потребления CPU.
Разработка контейнеров для сбалансированного использования ресурсов, когда все лимиты ресурсов контейнеров во время разработки установлены близко (или равными) к запросам ресурсов, является успешным подходом, особенно в сочетании с эффективным проектированием проб (англ.).
Грамотно продуманные лимиты контейнеров и пробы позволяют kubelet понять, когда нужно перенаправить траффик на другие реплики пода в кластере, позволяя при этом именно автоскейлерам определять, когда и как корректировать совокупный объём рабочей нагрузки пода.
![](https://habrastorage.org/webt/ym/oc/6_/ymoc6_v0doy8yrm1y4xsrjlxotc.jpeg)