# k8s CPU

Источник: https://ealebed.github.io/posts/2019/ресурсы-в-kubernetes-часть-2-cpu/

## Что имеется в виду под CPU когда мы говорим о Kubernetes? 

Один CPU это эквивалент “одного процессорного ядра”, предоставляемого операционной системой рабочего узла, вне зависимости от того, какое это ядро - физическое (physical core), поток физического ядра (hyper-thread), виртуальное ядро (например, EC2 vCPU, которое по сути, тоже является потоком физичекого ядра).

В отличие от ограничений (limits) по памяти, лимиты по CPU с точки зрения Kubernetes являются “сжимаемыми”, следовательно может работать так называемый CPU Throttling - снижение частоты процессора, и, как следствие, производительности. Когда вы устанавливаете значение limits для CPU:

```yaml
...
    resources:
      limits:
        cpu: "1"
        memory: 2Gi
...
```

вы на самом деле указываете время использования процессора (CPU time) на всех доступных процессорных ядрах рабочего узла (ноды), а не “привязываете” свой контейнер к конкретному ядру (или группе ядер). Это значит, что даже если вы указываете в .limits.cpu число меньшее общего количества ядер ноды, то контейнер все равно будет “видеть” и использовать все ядра, ограничиваясь только временем их использования.

К примеру, если контейнеру, который запускается на рабочем узле с общим количеством ядер 8, в значении CPU limits установить 4, то контейнер будет использовать эквивалент 4-х ядер, распределенных по всем 8 CPU ноды. В данном примере максимально допустимое использование процессора (CPU usage) на рабочем узле будет равняться 50%.

## Стоит ли указывать значения limits больше, чем requests?

Если ваше приложение стабильно использует предсказуемый объем оперативной памяти, то устанавливать разные значения параметров `requests` и `limits` для памяти нет смысла. В случае с [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md), разница между заданными значениями `requests` и `limits` может не устанавливаться (при условии, что эти же ресурсы не используются другими контейнерами и их не нужно “делить”).

## Как это выглядит с точки зрения Docker? 

Kubernetes управляет ограничениями CPU передавая параметры `cpu-period` и `cpu-quota`. Параметр `cpu-period` определяет период времени, в течении которого отслеживается использование процессора (CPU utilisation) контейнером и он всегда равен [100000µs (100ms)](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kuberuntime/helpers_linux.go#L28). Параметр `cpu-quota` - это общее количество процессорного времени, которое контейнер может использовать в каждом `cpu-period‘е`. Эти два параметра влияют на работу CFS (абсолютно честного планировщика ядра, [Completely Fair Scheduler](https://en.wikipedia.org/wiki/Completely_Fair_Scheduler)). Конкретный пример соответствия значений [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) `limits` значениям `cpu-quota` в конфигурации Docker:

```yaml
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](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md)) необходимо для работы вашему контейнеру с помощью параметра `requests` - это значение (**важно!**) учитывается планировщиком [Kubernetes](../4.3.6.2%20Kubernetes(k8s).md) при размещении контейнера на рабочих узлах кластера (общее значение параметров [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) `requests` всех контейнеров на конкретном рабочем узле не может быть больше, чем общее количество процессорных ядер данной ноды).

Таким образом, при использовании `requests`, вам гарантирован эквивалент количества указанных [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md), но что произойдет, если рабочий узел кластера будет находиться под чрезмерной нагрузкой (использование процессора на 100% или внезапные скачки Load Average - LA)? В этом случае приоритет использования процессорного времени будет вычисляться исходя из значения, указанного в [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) `requests` и умноженного на 1024 - результат будет передан Docker’у как параметр cpu-shares. Это так называемый “вес” - если все контейнеры данного рабочего узла имеют одинаковый вес, то они будут иметь одинаковый приоритет при планировании и использовании процессорного времени при чрезмерной нагрузке; если у контейнеров рабочего узла вес разный, то контейнер с большим весом будет иметь высший приоритет и получит больше процессорного времени при чрезмерной нагрузке процессора на рабочей ноде.

## [k8s QoS (классах качества сервиса)](./4.3.6.6.6%20k8sQOS.md)

QoS (классы качества сервиса) распространяются и в контексте [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md). Используя класс [Burstable](./4.3.6.6.6%20k8sQOS.md) вы получаете доп. периоды времени использования [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) (при условии, что эти же ресурсы не требуются другим контейнерам). Потенциально, это позволяет более эффективно использовать ресурсы кластера, правда, за счет большей непредсказуемости - повышенное использование [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) одним контейнером на рабочем узле повлияет на “соседей”, работающих на той же ноде кластера.

Опять же, если вы новичок в Kubernetes, лучше всего обеспечить класс сервиса [Guaranteed QoS](./4.3.6.6.6%20k8sQOS.md), устанавливая значения `requests` и `limits` одинаковыми. Когда вы соберете больше данных (метрик) и лучше разберетесь с использованием процессорных ресурсов контейнерами, есть смысл начать использовать класс сервиса Burstable [k8s QoS](./4.3.6.6.6%20k8sQOS.md) для обдуманной оптимизации расходов на инфраструктуру.

## Сколько [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md) стоит выделить контейнеру при написании манифеста? 

К сожалению, не существует универсального ответа на этот вопрос - все зависит от характеристик вашего приложения, требуемой производительности, места размещения контейнера, стоимости и т. д. Но если вы достаточно хорошо знаете, как работает ваше приложение “под капотом” и при наличии приличных инструментов для сбора и анализа метрик (например, [Prometheus](https://ealebed.github.io/tags/prometheus/)) можно подобрать оптимальную конфигурацию.

## Проверка и анализ данных [CPU](/2%20ComputerScience/2.0%20Linux/2.0.2%20Processor(CPU).md)

Информацию активного пода по `CPU` можно получить по пути: `/sys/fs/cgroup/memory/cpu.stat` для cgroupsV1 или `cat /sys/fs/cgroup/cpu.stat` для cgroupsV2.

Вывод:

```yaml
-> cat /sys/fs/cgroup/cpu.stat
usage_usec 3397596826
user_usec 2401850974
system_usec 995745851
nr_period 3890553
nr_throttled 2595
throttled_usec 396595981
```

| Метрика          | Значение (мкс/микросекунда)       | Человеческий формат     | Что это значит |
|------------------|----------------------|--------------------------|----------------|
| `usage_usec`     | 3 397 596 826 мкс    | **56.6 минут**           | Общее процессорное время, использованное контейнером за всё время жизни |
| `user_usec`      | 2 401 850 974 мкс    | **40.0 минут**           | Время выполнения кода приложения (user space) |
| `system_usec`    | 995 745 851 мкс      | **16.6 минут**           | Время системных вызовов (kernel space) |
| `nr_period`      | 3 890 553            | 389 секунд активных      | Количество завершённых интервалов планировщика (обычно по 100 мкс) |
| `nr_throttled`   | 2 595 раз            | 2 595 срабатываний       | Сколько раз контейнер превысил лимит CPU и был заморожен |
| `throttled_usec` | 396 595 981 мкс      | **6.6 минут**            | Суммарное время, проведённое в состоянии заморозки |

- **Доля throttling:**
  `throttled_usec / usage_usec ≈ 11.7%` — **времени процесс не работал, а ждал CPU**
  
    Простыми словами: Ваш контейнер 11.7% своего времени просто "висел" в ожидании разрешения на выполнение, потому что исчерпал лимит CPU.

- **Интерпретация:**
  `nr_throttled / nr_period = 0.067% (каждый 150-й период)`

    Казалось бы, всего 0.067% периодов были с throttling'ом...

- **Каждый throttling:**
  `throttled_usec / nr_throttled = 152.8 мс` - **среднее время блокировки за одно срабатывание**

    Вывод: Когда throttling срабатывает, он срабатывает жестко — контейнер замораживается в среднем на 153 мс за раз. Это катастрофически много для latency-sensitive приложений.

### Inside Pod

Описание анализа внутренностей поды: [ссылка](https://gist.github.com/eldaroid/f759ccb490d8a80e004b4af9d18d2f94)

----------

[4.3.6.5 Practise Theme](../4.3.6.5%20Practise.md) | [Back To iTWiki Contents](https://github.com/eldaroid/iTWiki) | [4.3.6.6.2 k8s Memory Theme](./4.3.6.6.2%20k8sMemory.md)
