Thank you for reading this post, don't forget to subscribe!
В данной статье рассмотрим нюансы настройки параметров requests
и limits
в контексте работы процессора (CPU). Давайте разберемся!
Итак, что имеется в виду под CPU
когда мы говорим о Kubernetes
? Один CPU
это эквивалент “одного процессорного ядра”, предоставляемого операционной системой рабочего узла, вне зависимости от того, какое это ядро - физическое (physical core), поток физического ядра (hyper-thread), виртуальное ядро (например, EC2 vCPU, которое по сути, тоже является потоком физичекого ядра).
В отличие от ограничений (limits
) по памяти, лимиты по CPU
с точки зрения Kubernetes
являются “сжимаемыми”, следовательно может работать так называемый CPU Throttling - снижение частоты процессора, и, как следствие, производительности. Когда вы устанавливаете значение limits
для CPU:
1 2 3 4 5 6 7 |
... resources: limits: cpu: "1" memory: 2Gi ... |
вы на самом деле указываете время использования процессора (CPU time) на всех доступных процессорных ядрах рабочего узла (ноды), а не “привязываете” свой контейнер к конкретному ядру (или группе ядер). Это значит, что даже если вы указываете в .limits.cpu
число меньшее общего количества ядер ноды, то контейнер все равно будет “видеть” и использовать все ядра, ограничиваясь только временем их использования.
К примеру, если контейнеру, который запускается на рабочем узле с общим количеством ядер 8, в значении CPU limits
установить 4, то контейнер будет использовать эквивалент 4-х ядер, распределенных по всем 8 CPU ноды. В данном примере максимально допустимое использование процессора (CPU usage) на рабочем узле будет равняться 50%.
Как это выглядит с точки зрения Docker? Kubernetes
управляет ограничениями CPU передавая параметры cpu-period
и cpu-quota
. Параметр cpu-period
определяет период времени, в течении которого отслеживается использование процессора (CPU utilisation) контейнером и он всегда равен 100000µs (100ms). Параметр cpu-quota
- это общее количество процессорного времени, которое контейнер может использовать в каждом cpu-period
'е. Эти два параметра влияют на работу CFS (абсолютно честного планировщика ядра, Completely Fair Scheduler). Конкретный пример соответствия значений CPU limits
значениям cpu-quota
в конфигурации Docker:
1 2 3 4 |
limits 1: cpu-quota=100000 limits 4: cpu-quota=400000 limits 0.5: cpu-quota=50000 |
Здесь limits 1
означает, что каждые 100ms контейнером могут использоваться 100% эквивалента 1 процессорного ядра рабочего узла, limits 4
указывает, что контейнер может использовать 400% эквивалента 1 ядра (ну или 100% процессорных 4-х ядер) и т.д. Не забываем, что это использование “размазывается” на все доступные ядра рабочей ноды, без привязки к конкретным ядрам. Благодаря работе “абсюлютно честного планировщика” (CFS), любому контейнеру, превышающему свою квоту в данный период (имеется в виду cpu-period
рассмотренный выше), будет запрещено использовать процессор до наступления следующего периода.
Напомню, что вы можете указать сколько процессорных ядер (CPU) необходимо для работы вашему контейнеру с помощью параметра requests
- это значение (важно!) учитывается планировщиком Kubernetes
при размещении контейнера на рабочих узлах кластера (общее значение параметров CPU requests
всех контейнеров на конкретном рабочем узле не может быть больше, чем общее количество процессорных ядер данной ноды).
Таким образом, при использовании requests
, вам гарантирован эквивалент количества указанных CPU, но что произойдет, если рабочий узел кластера будет находиться под чрезмерной нагрузкой (использование процессора на 100% или внезапные скачки LA)? В этом случае приоритет использования процессорного времени будет вычисляться исходя из значения, указанного в CPU requests
и умноженного на 1024 - результат будет передан Docker'у как параметр cpu-shares
. Это так называемый “вес” - если все контейнеры данного рабочего узла имеют одинаковый вес, то они будут иметь одинаковый приоритет при планировании и использовании процессорного времени при чрезмерной нагрузке; если у контейнеров рабочего узла вес разный, то контейнер с большим весом будет иметь высший приоритет и получит больше процессорного времени при чрезмерной нагрузке процессора на рабочей ноде.
В предыдущей статье мы уже упоминали о QoS (классах качества сервиса) - они справедливы и в контексте CPU. Используя класс Burstable
вы можете получить дополнительные периоды времени использования CPU (при условии, что эти же ресурсы не требуются другим контейнерам). Потенциально, это позволяет более эффективно использовать ресурсы кластера, правда, за счет большей непредсказуемости - повышенное использование CPU одним контейнером на рабочем узле повлияет на “соседей”, работающих на той же ноде кластера.
Опять же, если вы новичок в Kubernetes
, лучше всего обеспечить класс сервиса Guaranteed QoS
, устанавливая значения requests
и limits
одинаковыми. Когда вы соберете больше данных (метрик) и лучше разберетесь с использованием процессорных ресурсов контейнерами, есть смысл начать использовать класс сервиса Burstable QoS
для обдуманной оптимизации расходов на инфраструктуру.
Сколько CPU стоит выделить контейнеру при написании манифеста? К сожалению, не существует универсального ответа на этот вопрос - все зависит от характеристик вашего приложения, требуемой производительности, места размещения контейнера, стоимости и т. д. Но если вы достаточно хорошо знаете, как работает ваше приложение “под капотом” и при наличии приличных инструментов для сбора и анализа метрик (например, Prometheus) можно подобрать оптимальную конфигурацию. В крайнем случае, можно даже получить кое-какие цифры для анализа выполнив внутри контейнера команду:
1 2 3 4 5 6 |
cat /sys/fs/cgroup/cpu,cpuacct/cpu.stat nr_periods 345489 nr_throttled 502 throttled_time 109456473902 |
Так можно получить общее количество периодов запуска, количество раз, когда производительность процессора для данного контейнера была принудительно снижена (CPU Throttled) и общее время троттлинга в наносекундах.