K8s Q A CPU
Ресурсы в Kubernetes - CPU
Итак, что имеется в виду под CPU когда мы говорим о Kubernetes?
Один CPU это эквивалент “одного процессорного ядра”, предоставляемого операционной системой рабочего узла,
вне зависимости от того, какое это ядро - физическое (physical core), поток физического ядра (hyper-thread),
виртуальное ядро (например, EC2 vCPU, которое по сути, тоже является потоком физичекого ядра).
В отличие от ограничений (limits) по памяти, лимиты по CPU с точки зрения Kubernetes являются “сжимаемыми”,
следовательно может работать так называемый CPU Throttling - снижение частоты процессора,
и, как следствие, производительности. Когда вы устанавливаете значение limits для CPU:
... 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:
- 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) можно подобрать оптимальную конфигурацию.
В крайнем случае, можно даже получить кое-какие цифры для анализа выполнив внутри контейнера команду:
cat /sys/fs/cgroup/cpu,cpuacct/cpu.stat nr_periods 345489 nr_throttled 502 throttled_time 109456473902
Так можно получить общее количество периодов запуска, количество раз, когда производительность процессора для данного контейнера была принудительно снижена (CPU Throttled) и общее время троттлинга в наносекундах.