Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Requests и limits: как задавать ресурсы пода

Requests и limits в Kubernetes: планирование, троттлинг и OOM

Requests и limits: как планировщик использует requests, что происходит при превышении limits, классы QoS и практические правила подбора значений.

Прямой ответ

У каждого контейнера в Kubernetes два параметра ресурсов: requests и limits. Они решают разные задачи, и путаница между ними — основная причина проблем с производительностью и внезапных перезапусков.

Requests — заявка планировщику. Кубернетес ищет узел, где сумма requests всех подов плюс новый под не превысит доступную ёмкость. Requests не ограничивают потребление: контейнер может использовать больше, если на узле есть свободные ресурсы.

Limits — жёсткий потолок через cgroups. Поведение при превышении различается по типу ресурса:

Ресурс Что происходит при превышении limits
CPU Троттлинг: контейнер получает меньше процессорного времени, растёт задержка
Память OOMKill: контейнер завершается и перезапускается

Это различие принципиально: CPU — сжимаемый ресурс, память — несжимаемый. Превышение по CPU ухудшает производительность, по памяти — убивает процесс.

Классы качества обслуживания (QoS) определяются автоматически:

Класс Условие Порядок вытеснения
Guaranteed requests == limits для CPU и памяти во всех контейнерах Вытесняется последним
Burstable Задан хотя бы один requests, но не равен limits Вытесняется вторым
BestEffort Не задано ничего Вытесняется первым

При нехватке ресурсов на узле kubelet вытесняет поды в порядке от BestEffort к Guaranteed. Поэтому приложение, которому не задали ресурсы, перестаёт работать первым — при том, что выглядит «настроенным так же, как остальные».

resources:
  requests:
    cpu: 250m        # 0,25 ядра: гарантия планирования
    memory: 512Mi
  limits:
    cpu: "1"         # потолок: выше — троттлинг
    memory: 768Mi    # потолок: выше — OOMKill

Важно: limits не может быть меньше requests — манифест не пройдёт валидацию. CPU задаётся в долях ядра (250m = 0,25), память — в байтах с двоичными (Mi, Gi) или десятичными (M, G) приставками; разница между Mi и M составляет около 5 %.

Практика: как подбирать значения

1. Requests по памяти — критично. Если сумма requests ниже фактического потребления, узел уходит в состояние давления памяти и начинает вытеснять поды. Практика: requests по памяти ставят близко к фактическому рабочему объёму, с запасом 10–20 %.

2. Limits по CPU часто мешают. Троттлинг проявляется как рост p99 задержки при невысокой средней загрузке CPU — симптом, который трудно связать с причиной. Многие команды отказываются от лимита CPU, оставляя только requests.

3. Limits по памяти обязательны. Без них один контейнер с утечкой занимает всю память узла и провоцирует каскадное вытеснение.

4. Измеряйте перед настройкой. Значения «на глаз» почти всегда неверны. Ориентироваться нужно не на среднее, а на 95-й процентиль потребления за представительный период, включая пики суточной нагрузки и моменты сборки мусора:

kubectl top pod -n billing --sort-by=memory
kubectl describe node worker-3 | sed -n '/Allocated resources/,/Events/p'

5. Автомасштабирование опирается на requests. HPA вычисляет загрузку как отношение фактического потребления к requests, а не к limits. Заниженный requests даёт ложную картину «высокой загрузки» и преждевременное масштабирование — см. HPA.

6. JVM и рантаймы с собственной кучей. Если процесс управляет памятью сам, лимит контейнера должен учитывать не только кучу, но и метаспейс, стеки потоков и нативные аллокации. Установка -Xmx равной лимиту контейнера — типичная причина OOMKill: нативные структуры размещаются вне кучи, и суммарное потребление превышает потолок. Практическое правило — оставлять не менее 20–25 % лимита контейнера за пределами кучи.

7. Пересматривайте значения после изменений в коде. Добавление кэша, нового пула соединений или фоновой задачи меняет профиль потребления. Значения, подобранные полгода назад, перестают соответствовать действительности, и расхождение обнаруживается вытеснением или троттлингом, а не на этапе ревью.

Рекомендации:

  1. Начинайте с requests и оставляйте CPU без limits. Это предсказуемое поведение и отсутствие троттлинга; ограничение памяти защищает узел.
  2. Проверяйте класс QoS для критичных сервисов. Если важно, чтобы под вытеснялся последним, нужны равные requests и limits, то есть класс Guaranteed.
  3. Учитывайте overhead пауз сборки мусора. Пики потребления памяти при GC выше среднего значения; requests, выставленный по среднему, приводит к вытеснению в момент пика.
  4. Не копируйте значения между сервисами. Профиль потребления у API с короткими запросами и у воркера пакетной обработки различается на порядок.
  5. Свяжите с планированием ёмкости. Сумма requests определяет, сколько подов помещается на узел; ошибки в requests приводят либо к простоивающему железу, либо к постоянному вытеснению — о методике оценки в статье про capacity planning.

Практический сценарий: периодические перезапуски пода без видимых причин

Контекст: под API-сервиса перезапускался два-три раза в сутки с кодом 137. В логах приложения ошибок не было.

  1. Диагностика. kubectl describe pod показал причину OOMKilled. Лимит памяти составлял 512 Mi, requests — 128 Mi.
  2. Наблюдение. Метрики показали, что потребление держалось на уровне 420–470 Mi, но в момент сборки мусора достигало 560 Mi. То есть превышение было коротким и периодическим.
  3. Причина. Лимит выставили по среднему потреблению, а не по пику. Дополнительно JVM была настроена с -Xmx512m, что не оставляло места на метаспейс и нативные структуры.
  4. Действия. (а) Лимит памяти увеличили до 768 Mi, requests подняли до 512 Mi, чтобы планировщик учитывал реальный объём; (б) -Xmx снизили до 480 m, оставив запас внутри контейнера; (в) добавили алерт на потребление выше 80 % лимита; (г) для воркеров пакетной обработки значения подобрали отдельно по их профилю.
  5. Результат. Перезапуски прекратились; p99 задержки снизился, потому что прекратились холодные старты после OOMKill.

Вывод: значение limits по памяти должно отражать пик потребления, а не среднее. Перезапуск пода из-за OOMKill выглядит как случайный сбой, но всегда имеет измеримую причину.

FAQ

Что произойдёт, если не задать resources вовсе? Под получит класс BestEffort и будет вытеснен первым при нехватке ресурсов на узле. Кроме того, HPA не сможет вычислить загрузку, а планировщик разместит под без учёта его потребления. В namespace с квотой такой под вообще не создастся: при действующей квоте на CPU или память указание requests становится обязательным.

Можно ли задать только limits без requests? Можно: в этом случае requests автоматически приравнивается к limits, и под получает класс Guaranteed. Такое поведение удобно для критичных сервисов, но требует точного знания потребления.

Почему CPU-лимит вызывает задержки при низкой загрузке? Троттлинг применяется в рамках коротких периодов квотирования: процесс может исчерпать квоту за долю периода и ожидать до его конца. В метриках это выглядит как низкая средняя загрузка при высоких хвостовых задержках.

Как узнать фактическое потребление, если под постоянно перезапускается? Через метрики до перезапуска либо запуском копии с повышенным лимитом в тестовом окружении под нагрузкой. Оценка по «спокойному» состоянию даёт заниженные значения.

Источники: Kubernetes docs (manage resources, QoS classes, node-pressure eviction) — ссылки в шапке.