Понимание и устранение проблем с лимитами CPU в Kubernetes
Ваши приложения в Kubernetes необъяснимо замедляются, хотя узлы кажутся незагруженными? Вероятно, вы столкнулись с троттлингом CPU (CPU throttling). Недавно я посмотрел отличный доклад на CNCF от Дэйва Чилука (Dave Chiluk), инженера компании Indeed. Рекомендую посмотреть видео целиком. Также есть связанная статья в блоге Indeed. Несмотря на то что видео вышло в 2020 году, а сама тема поднималась ещё в 2018-м, объяснение Дэйва о том, почему троттлинг CPU проявлялся в определённых версиях ядра Linux, оказалось очень полезным для понимания этого явления в целом. В этой статье я делюсь основными выводами из видео.
Троттлинг CPU в Kubernetes
В Kubernetes для подов задаются запросы CPU (CPU requests) и лимиты CPU (CPU limits). Запросы гарантируют поду пропорциональную долю процессорного времени, тогда как лимиты устанавливают жёсткий потолок потребления CPU контейнером. Именно лимиты — точнее, механизм их применения через управление полосой пропускания планировщика CFS (Completely Fair Scheduler) ядра Linux — могут приводить к троттлингу.
Что такое троттлинг? Представьте, что вашему приложению выделен фиксированный объём процессорного времени (квота, quota) на короткий временной интервал (период, period). Если приложение исчерпывает квоту до окончания периода, оно вынуждено ждать начала следующего. Это ожидание и есть троттлинг. Даже если на узле есть свободные ресурсы CPU, приложение искусственно ограничивается.
Стандартный период CFS составляет 100 миллисекунд. Если для пода установлен лимит CPU, например 400 millicores (0.4 CPU), квота на период рассчитывается следующим образом:
(Лимит CPU / 1000) * Период = Квота (400 mc / 1000) * 100 мс = 40 миллисекунд
Это означает, что приложение может использовать не более 40 мс процессорного времени каждые 100 мс. Если потребность выше — оно будет ограничено.
Диагностика проблемы
Троттлинг можно отслеживать напрямую с помощью двух ключевых метрик, которые предоставляет kubelet на каждом узле. Как правило, их собирает система мониторинга, например Prometheus.
container_cpu_cfs_throttled_periods_total-
Счётчик, увеличивающийся каждый раз, когда контейнер попадает под троттлинг.
container_cpu_cfs_periods_total-
Счётчик, отражающий общее количество периодов квоты.
На основе этих метрик можно рассчитать процент троттлинга (throttle percentage):
(Throttled Periods / Total Periods) * 100 = Throttle Percentage
Высокий процент троттлинга (например, выше 1–5%) — явный признак проблемы.
Регрессия исправления в ядре Linux
Глубокое изучение этой проблемы командой Indeed выявило интересную регрессию, возникшую после попытки исправить (коммит ядра 512ac999) связанный с расхождением часов CPU баг в механизме управления полосой пропускания CFS-Cgroup.
Суть проблемы состояла в следующем: планировщик ядра выдаёт каждому CPU небольшой временной срез (5 мс) из общего пула квот, и неиспользованное локальное время сбрасывалось в конце каждого периода планирования. На машинах с небольшим числом ядер это было несущественно, однако на серверах с большим количеством ядер потери становились значительными. Например, на машине с 88 ядрами до 87 мс процессорного времени за период могло оказываться недоступным, что приводило к избыточному троттлингу и снижению производительности приложений.
Вернёмся к приведённому ранее примеру: контейнер имеет квоту 40 мс на период 100 мс. Если за первые 10 мс периода он использует только 10 мс из этой квоты, оставшиеся 30 мс могут не переноситься на следующий срез. Такое «зависание» квоты приводило к троттлингу даже тогда, когда приложение не исчерпало полный объём выделенного времени за весь период, — особенно это проявлялось в случае всплесков нагрузки, требующих большего количества CPU в начале периода.
Решением стало устранение логики сброса неиспользованного времени, что позволяет накапливать неизрасходованные временные срезы. Это изменение обеспечивает строгое соблюдение среднего потребления CPU на более длинном временном горизонте, одновременно ограничивая burst-ёмкость. Исправление вошло в основную ветку ядра начиная с версии 5.4, что привело к более эффективному использованию CPU и улучшению производительности приложений на машинах с большим числом ядер.
Конкретные меры по устранению троттлинга CPU
Обновите ядро (наиболее эффективный способ). Исправление, устраняющее описанное поведение, доступно начиная с Linux kernel версии 5.4 и выше. Если вы используете более старые ядра, настоятельно рекомендуется обновиться: как правило, это даёт наиболее ощутимый и долгосрочный результат.
Регулярно отслеживайте метрики троттлинга.
Добавьте container_cpu_cfs_throttled_periods_total и container_cpu_cfs_periods_total в дашборды мониторинга. Настройте алерты на высокий процент троттлинга, чтобы выявлять проблемы заблаговременно.
Скорректируйте лимиты CPU (если обновление ядра невозможно). Если обновление ядра в ближайшее время недоступно, рассмотрите следующие варианты:
-
Увеличьте лимиты CPU. Если приложению действительно нужно больше CPU, поднимите лимит — квота за период вырастет пропорционально.
-
Используйте целые значения CPU (например, 1.0, 2.0 CPU). Это не всегда практично, но выравнивание лимитов по целым единицам CPU иногда смягчает проблему, особенно если приложение можно спроектировать с учётом этого ограничения.
-
Полностью уберите лимиты CPU (применяйте с осторожностью!). Для некритичных приложений или в средах с низкой конкуренцией за ресурсы отсутствие лимита полностью устраняет троттлинг. Однако это несёт риск: неконтролируемый процесс может занять всё процессорное время узла и негативно повлиять на другие приложения. Прибегайте к этому варианту только после тщательного анализа и тестирования.
Изучите профиль CPU вашего приложения. Ваше приложение работает в burst-режиме или требует постоянной нагрузки на CPU? Профилировщики помогут понять характер потребления процессорного времени и принять более обоснованные решения о лимитах ресурсов.
Заключение
Троттлинг CPU в Kubernetes — коварный убийца производительности, с которым я регулярно сталкиваюсь в кластерах под моим управлением. Наша команда постоянно взаимодействует с разработчиками, помогая им правильно настраивать запросы и лимиты CPU.
В первую очередь стремитесь к обновлению ядра, а если это невозможно — тщательно корректируйте лимиты CPU, не прекращая мониторинг производительности приложений.
Кстати, в видео также предлагается установить регулятор CPU (CPU governor) на хосте Linux в режим performance. Логика здесь такая: другие регуляторы — современный schedutil или более старый ondemand — динамически изменяют частоту CPU для экономии энергии. При всплеске нагрузки регулятору требуется время (пусть и очень небольшое) для повышения частоты до максимума. Режим performance полностью исключает эту задержку, удерживая CPU на максимальной частоте постоянно. Для персональных компьютеров и ноутбуков это вряд ли оправдано, однако в облачных виртуальных машинах, где вы платите фиксированную стоимость за vCPU экземпляра, этот вариант стоит рассмотреть.