HPA научился убирать последнюю реплику и возвращать её при появлении работы. Но для этого недостаточно поставить minReplicas: 0: сигнал нагрузки должен пережить исчезновение workers, а контроллер должен отличить собственное решение от ручной остановки. Ошибка в любой из этих двух границ оставляет очередь без обработчиков.

Основа разбора — публикация Johannes Würbach в официальном блоге Kubernetes от 2 сентября 2026 года. Материал адаптирован на русский по CC BY 4.0: структура переработана, добавлены эксплуатационные сценарии и план проверки. Факты сверены с документацией 8 сентября 2026 года; описанные проверки — рекомендации для пилота, а не отчёт об испытаниях нашего кластера.

Последний Pod тоже стоит денег

Представим обработчик документов: задания приходят пачками, между ними очередь часами пуста. Один постоянно работающий Pod удерживает ресурсы, хотя полезной работы нет. Если обработчику нужен выделенный CPU или GPU, этот минимальный запас особенно заметен. Удалить последнюю реплику легко; сложнее гарантировать, что кто-нибудь запустит следующую.

В Kubernetes 1.37 механизм HPAScaleToZero перешёл в beta и включён по умолчанию. Он позволяет HPA масштабировать подходящую нагрузку до нуля и обратно. Это не означает автоматического перевода существующих Deployment в такой режим: требуется соответствующая конфигурация HPA, включая минимальное число реплик и подходящую метрику.

Сначала определите, может ли работа ждать. Для долговечной очереди ожидание часто допустимо. Для синхронного HTTP-запроса исчезновение всех готовых Pod меняет доступность сервиса: Kubernetes Service не становится буфером запросов. Без отдельного слоя ожидания пользователь может получить ошибку раньше, чем появится обработчик. Экономию нельзя оценивать отдельно от этого поведения.

Метрика должна жить дольше обработчика

CPU и память подходят для масштабирования работающих Pod, но не дают сигнала пробуждения после исчезновения последнего экземпляра. Отсутствие измерений не сообщает, сколько новых заданий ждёт обработки. Поэтому для minReplicas: 0 нужен хотя бы один источник типа Object или External; конфигурация только с ресурсными метриками не проходит проверку API.

Практический пример — длина очереди, которую измеряет независимый компонент. Workers могут отсутствовать, но система мониторинга продолжает видеть задания. Это требование относится ко всей цепочке: источник данных, сборщик, хранилище метрик и адаптер не должны исчезать вместе с целевой нагрузкой. Экспортёр, встроенный только в сам worker, эту задачу не решает.

В исходной статье показан Prometheus Adapter, который публикует значение через External Metrics API. Здесь важен не конкретный продукт, а проверяемая граница: HPA должен получать актуальную метрику через Kubernetes API. Зелёного графика в Prometheus для этого недостаточно. Правила discovery, сопоставление namespace и селекторы могут отрезать нужный ряд уже после успешного сбора.

Для пилота проверьте три состояния: очередь содержит задания, очередь пуста, источник метрики недоступен. Последние два состояния нельзя смешивать. Пустая очередь даёт измеренный ноль; потерянный источник означает неизвестную нагрузку. Подмена ошибки нулём способна скрыть отказ именно тогда, когда обработчики должны проснуться.

Кто поставил ноль, тот определяет пробуждение

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

Для различения состояний контроллер использует condition ScaledToZero. Когда HPA сам уменьшает число реплик до нуля, он записывает ScaledToZero=True и продолжает оценивать подходящие метрики. После обратного масштабирования condition становится False с причиной NotScaledToZero. Эти правила описаны в документации по масштабированию до нуля.

Отсюда важная деталь запуска: начните с хотя бы одной реплики и дайте HPA самостоятельно убрать её. Нагрузка, вручную оставленная на нуле без ScaledToZero=True, остаётся на паузе. Тест «создали Deployment сразу с нулём и положили сообщение» не проверяет полный штатный цикл scale-to-zero.

В дежурном сценарии смотрите не только на число Pod, но и на conditions HPA. Для объекта с именем queue-worker исходная публикация предлагает kubectl describe hpa queue-worker; выберите namespace своего объекта. Если получение внешней метрики сломано, возможен ScalingActive=False с причиной FailedGetExternalMetric. Возвращать ёмкость тогда придётся восстановлением метрики или согласованным ручным масштабированием, а не ожиданием чудес от контроллера.

Холодный старт входит в срок обработки

Между появлением задания и готовностью worker есть несколько задержек: обновление метрики, очередной цикл HPA, создание Pod, планирование, загрузка образа и запуск приложения. Если свободных ресурсов нет, добавляется ожидание вычислительной ёмкости. Для GPU-обработчика подготовка модели также может стать существенной частью времени ответа.

Это инженерные составляющие задержки, а не обещание конкретной длительности. Измеряйте путь от поступления задания до начала и завершения обработки. Статус Pod Running сам по себе не показывает, что приложение уже подключилось к очереди и готово выполнять работу. На приёмке полезнее возраст старейшего задания и время обработки первой пачки после простоя.

Обратный переход тоже не мгновенный. В обычном поведении HPA действует пятиминутное окно стабилизации уменьшения числа реплик. Оно сдерживает реакцию на короткий провал нагрузки; параметры настраиваются через spec.behavior.scaleDown. Не сокращайте окно только ради красивого графика освобождённых ресурсов: частые остановки могут увеличить число холодных стартов.

Отдельно проверьте завершение уже взятой работы. Пустая очередь ожидающих сообщений не обязательно означает отсутствие выполняющихся заданий. Это зависит от семантики брокера и выбранного показателя. Уменьшение числа workers должно сочетаться с корректным завершением обработки, подтверждением сообщения и повторной доставкой при сбое.

Пилот должен проверить возвращение, а не только остановку

Выберите одну некритичную очередь с понятным допустимым временем ожидания. Зафиксируйте владельца метрики, владельца HPA и процедуру ручного восстановления. Затем пройдите полный цикл:

  1. При работающем worker убедитесь, что Kubernetes API возвращает нужную метрику и правильный набор labels.
  2. Дождитесь опустошения очереди и автоматического перехода к нулю. Проверьте ScaledToZero=True.
  3. Добавьте тестовое задание и измерьте время до фактической обработки, включая подготовку приложения.
  4. На тестовом стенде нарушьте доступность метрики и проверьте диагностику, сигнал дежурному и ручное восстановление.
  5. Отдельно проверьте ручную паузу, чтобы не спутать её с автоматическим простоем.

Успех пилота — не минимальное число реплик на графике. Это обработанное после простоя задание в пределах вашего срока, понятный отказ при потере метрики и воспроизводимое восстановление. Если такие проверки не проходят, постоянная минимальная реплика пока остаётся осознанной платой за доступность.

Откат начинается до выключения feature gate

Поддержка требуется одновременно от kube-apiserver и kube-controller-manager: первый принимает конфигурацию, второй выполняет масштабирование. Во время обновления control plane с разными версиями компонентов не включайте сценарий раньше, чем обе стороны его поддерживают. Принятый манифест ещё не доказывает, что действующий контроллер умеет возвращать нагрузку из нуля.

Перед отключением HPAScaleToZero или откатом к версии без соответствующей реализации верните затронутым HPA minReplicas: 1 либо выше и поднимите остановленные нагрузки хотя бы до одной реплики. Проверьте доступность обработчиков до продолжения отката. Иначе контроллер с выключенной возможностью может принять оставшийся ноль за ручную паузу.

Scale-to-zero освобождает ресурсы последнего простаивающего worker, но не гарантирует уменьшения счёта: выделенный узел или GPU могут продолжать оплачиваться. Экономия зависит от тарификации и сокращения вычислительной ёмкости. Внедрять этот режим стоит там, где очередь переживает холодный старт, метрика остаётся доступной без Pod, а оператор может отличить штатный простой от остановленной системы.