Kubernetes может тормозить приложение на свободной ноде

Выглядит нелогично: процессор свободен, а приложение стоит. Но для контейнера этого CPU уже нет — он исчерпал заданный ему лимит.

Чтобы понять, как мы до этого дошли, нужно разделить две настройки: requests и limits.

Они стоят рядом в YAML, поэтому их легко принять за нижнюю и верхнюю границы потребления. Но задачи у них разные.

———

Requests: сколько ресурсов нужно Pod

По requests Kubernetes решает, на какую ноду поставить Pod. Причём смотрит не на текущее потребление, а на уже заявленные requests.

Нода может простаивать, но если Kubernetes уже пообещал все её ресурсы другим Pod, новый Pod на ней не запустится. Он будет ждать, пока освободится место или появится другая нода.

Для CPU request ещё задаёт вес контейнера при конкуренции за процессор.

Limits: больше этого потреблять нельзя

Limit — верхняя граница ресурса для контейнера. Её применяет уже не scheduler, а Linux через cgroups.

Для CPU и памяти последствия разные: — CPU закончился — контейнер начинает ждать. — Память закончилась — процесс может получить OOMKilled.

———

Что происходит с реальным потреблением

1. Потребление ниже request

Приложение работает. Неиспользуемый CPU не лежит в отдельной коробке с именем вашего Pod — его могут забрать соседи.

Но для размещения новых Pod Kubernetes всё равно считает request занятым. Если requests систематически завышены, кластер выглядит заполненным, хотя железо простаивает.

2. Потребление выше request, но ниже limit

Тоже работает. Пока на ноде есть свободные ресурсы, контейнер может потреблять больше request.

Проблема появляется при конкуренции. Если два контейнера одновременно захотели CPU, Kubernetes учитывает их CPU requests как относительные веса — CPU Shares.

Например: — Pod A: request 500m. — Pod B: request 1000m.

Когда CPU не хватает, B при прочих равных получит примерно вдвое больше процессорного времени. Когда дефицита нет, shares ничего не ограничивают.

3. Потребление упёрлось в limit

С памятью всё жёстко: процесс может быть убит по OOM. С CPU контейнер не убивают — его временно перестают выполнять. Это и есть троттлинг.

———

CPU Period и CPU Quota — без магии

Представь, что Linux выдаёт контейнеру CPU короткими порциями.

Period — длина одной такой порции времени. Обычно 100 ms. Quota — сколько процессорного времени контейнер может потратить внутри period.

Если CPU limit равен 500m, то за каждые 100 ms контейнер получает 50 ms процессорного времени:

500m × 100 ms = 50 ms

Потратил 50 ms раньше конца периода — ждёт начала следующего. Даже если на ноде есть свободные ядра.

Поэтому CPU ноды может быть свободен, а p99 расти: контейнер уже выбрал quota.

Многопоточное приложение может потратить quota ещё быстрее. Например, четыре потока на четырёх ядрах израсходуют суммарные 200 ms CPU примерно за 50 ms обычного времени.

———

Как не попасть под троттлинг

— Сначала сверь throttled time и throttled periods с p95/p99. Сам по себе счётчик троттлинга ещё не означает проблему. — Не ставь CPU limit ниже нормальных пиков. Для latency-sensitive сервиса limit можно поднять или убрать, если это допускает политика кластера. — Настрой HPA так, чтобы новые реплики появлялись до упора в limit. Если HPA использует CPU utilization, помни: request влияет на момент масштабирования. — Если limit обоснован, а CPU всё равно не хватает, добавляй реплики или оптимизируй код. Новое круглое число в YAML проблему не вылечит.

———

Какие ограничения важно помнить

— Request не должен быть больше limit для того же ресурса. — Если для нового Pod не хватает места с учётом requests, он будет ждать. — Сумма limits на ноде может быть больше её мощности. Это допустимый overcommit. — Если задать limit и не задать request, Kubernetes может использовать limit и как request.

Поэтому requests и limits лучше не подбирать по принципу «поставим красивые круглые числа».

Request отвечает за размещение и долю CPU при дефиците. Limit отвечает за потолок. А реальное потребление живёт между ними.