CPU throttling в Kubernetes: почему приложение тормозит при свободном CPU
На узле свободны несколько ядер, но у API выросло время ответа. После добавления потоков становится ещё хуже, хотя память не заканчивается. Одна из возможных причин — ограничение CPU контейнера. Linux может приостановить его выполнение после расхода квоты, даже если физические ядра в этот момент простаивают. Это следует проверить до масштабирования всего кластера.
Находим границу, применённую к контейнеру
kubectl -n demo get pod api-example -o yaml
kubectl -n demo top pod api-example --containers
kubectl -n demo describe deployment apiУкажите свой Pod и контейнер. В resources найдите requests.cpu и limits.cpu, включая значения, добавленные admission-политикой. 500m — половина CPU в среднем по механизму ограничения. Это не закрепление за половиной одного ядра. Несколько потоков могут одновременно использовать CPU и быстрее израсходовать выделенное на период время.
Request помогает scheduler разместить Pod и участвует в распределении CPU при конкуренции. Limit задаёт верхнюю границу потребления. Поэтому увеличение request при сохранённом маленьком limit не устраняет квоту. Эта задача отличается от Pending: Pod уже размещён и работает, проблема проявляется во время исполнения.
Смотрим признаки ограничения во времени
На Linux с cgroup v2 в контейнере часто доступен cpu.stat. Если образ содержит cat и runtime предоставляет этот путь, прочитайте файл дважды с интервалом под одинаковой нагрузкой:
kubectl -n demo exec api-example -c api -- cat /sys/fs/cgroup/cpu.stat
nr_periods показывает число периодов учёта, nr_throttled — число периодов с ограничением, throttled_usec — накопленное время ограничения. Сравнивайте приращения, а не абсолютное значение с момента запуска. При другой версии cgroup путь и имена полей отличаются. Отсутствие файла не доказывает отсутствие throttling: обратитесь к метрикам runtime или настройке cgroup на узле.
Если cAdvisor экспортирует соответствующие метрики CPU, долю периодов с ограничением можно посмотреть в Prometheus. Фильтр ограничен контейнером api и namespace demo:
sum by (pod) (rate(container_cpu_cfs_throttled_periods_total{namespace="demo",container="api"}[5m]))
/
sum by (pod) (rate(container_cpu_cfs_periods_total{namespace="demo",container="api"}[5m]))Результат 0,3 означает, что ограничение встречалось в 30% учтённых периодов. Это не доля потерянных запросов и не точный процент отнятого CPU. При нулевом знаменателе отношение неинформативно. Проверьте доступность обеих метрик и ваши labels; набор экспортируемых данных зависит от runtime и конфигурации мониторинга.
Почему средний график скрывает проблему
Пятиминутное среднее сглаживает короткие всплески. Например, запрос обрабатывается параллельно несколькими потоками, быстро расходует квоту, затем ждёт следующий период. Средний расход по узлу остаётся низким. Сопоставьте интервалы throttling с p95 задержки, длиной очереди и распределением запросов между репликами. Совпадение по времени поддерживает гипотезу, но ещё не исключает ожидание базы или блокировки в приложении.
Количество потоков runtime иногда выбирает по доступным ядрам иначе, чем ожидает автор приложения. Проверьте настройки параллелизма и поддержку ограничений контейнера вашей версией языка. Увеличение числа workers может увеличить конкуренцию за ту же квоту и ухудшить хвост распределения задержки. Для коротких CPU-всплесков полезнее сравнить несколько разумных конфигураций под одинаковым потоком запросов.
Меняем ресурс с проверяемой целью
resources:
requests:
cpu: "500m"
limits:
cpu: "1500m"Это пример увеличенной квоты, а не универсальная настройка. Измените limit в источнике Deployment и убедитесь, что политика кластера допускает выбранное значение. В некоторых окружениях команда принимает решение работать без CPU limit, сохраняя request и контроль ёмкости; применимость зависит от изоляции и конкурирующих нагрузок. Удалять ограничения массово без измерения влияния на соседей не следует.
Повторите ту же нагрузку и сравните задержку, очередь, потребление CPU и частоту ограничений. Если throttling уменьшился, а задержка осталась прежней, ищите следующее ограничение в цепочке запроса. Если улучшились оба показателя, сохраните параметры эксперимента и запас узла. Так решение связано с работой приложения, а не с попыткой обнулить любую техническую метрику.
Ещё по теме
- DNS в Kubernetes отвечает медленно: проверяем CoreDNS, search и ndots
- Forbidden в Kubernetes: проверяем ServiceAccount, Role и RoleBinding
Продолжить изучение
Хотите увереннее разворачивать приложения и разбирать сбои в кластере? Сети, конфигурация, хранилища и обновления входят в программу «Kubernetes с нуля до PRO».
Продолжить на практике
- Kubernetes с нуля до PRO ↗
Разворачивайте и сопровождайте приложения в Kubernetes: от первого Pod до релизов, сетей, хранилищ, масштабирования и диагностики. Итоговый проект — сервис с базой данных, мониторингом и проверенным откатом.
