Перевести minReplicas с 1 на 0 технически несложно. Сложность начинается после первого периода простоя: приходит задание, но обслуживать его некому. Теперь доступность компонента зависит не только от приложения и Kubernetes, но и от свежести метрики, цикла HPA, планировщика, наличия ноды, загрузки образа и прохождения проверок готовности.
Это не обычная оптимизация числа реплик. Scale-to-zero меняет модель отказов: сигнал спроса становится частью критического пути, а холодный запуск — частью пользовательской задержки.
В Kubernetes 1.37 функция HPAScaleToZero получила статус Beta и включена по умолчанию. Но это не делает её универсальным способом снизить стоимость кластера. Хороший кандидат — обработчик долговечной очереди, способной переждать холодный запуск. Плохой — синхронный HTTP API, где первый запрос должен одновременно обнаружить спрос и дождаться готовности приложения.
До настройки автомасштабирования нужно ответить на два вопроса:
Где находится работа, пока нет готовых Pod’ов?
Как долго она может там находиться без нарушения SLA?
Для асинхронного обработчика ответом может быть долговечная очередь. Задание уже принято, сохранено и дождётся потребителя. У обычного Kubernetes Service нет встроенной очереди, которая удерживала бы HTTP-запрос до появления Ready Pod’а.
Kubernetes Service не превращается в очередь при исчезновении Ready Pod’ов. Он не удерживает запрос до запуска приложения. Поэтому scale-to-zero для HTTP endpoint без внешнего буфера, прокси с явной семантикой ожидания или постоянно работающей реплики создаёт окно ошибок и тайм-аутов. Это принципиальное отличие от платформ, где приём запроса и запуск экземпляра объединены в управляемую модель (Kubernetes Blog, KEP-2021).
Поэтому полезно разделять три разных результата:
ноль Pod’ов — приложение не занимает запрошенные ресурсы на уровне планировщика;
меньше нод — автомасштабирование или консолидация действительно удалили оплачиваемую инфраструктуру;
serverless-поведение — платформа приняла событие или запрос, сохранила его и сама организовала запуск среды выполнения.
Native HPA обеспечивает первый механизм. Второй зависит от автомасштабирования нод и размещения остальных нагрузок. Третий нужно проектировать отдельно.
HPA может использовать minReplicas: 0, только если задана хотя бы одна метрика типа Object или External. Конфигурация только с CPU или памятью будет отклонена API server (документация HPA).
Причина не косметическая: при нуле Pod’ов нет потребления CPU и памяти, по которому можно вычислить необходимость запуска. Сигнал должен существовать независимо от масштабируемой нагрузки. Типичные примеры — размер очереди, возраст самого старого задания или другой внешний показатель спроса.
Исторически replicas: 0 могло означать ручную остановку нагрузки. Чтобы HPA не возобновил её неожиданно, Kubernetes различает:
ноль, достигнутый самим HPA;
ноль, выставленный вручную или старым контроллером.
При автоматическом переходе HPA устанавливает условие ScaledToZero=True. Если нагрузка находится в replicas: 0, но этого условия нет, HPA сохраняет семантику ручной паузы и не обязан её поднимать.
Это важно для диагностики. Наличие объекта HPA и внешней метрики ещё не доказывает, что компонент автоматически выйдет из нуля. В эксплуатационной инструкции следует проверять как фактическое число реплик, так и условия HPA.
Во время обновления API server может уже принимать HPA с minReplicas: 0, пока старый kube-controller-manager ещё не умеет выводить нагрузку из нуля. Старый контроллер интерпретирует replicas: 0 как ручную паузу.
Поэтому безопасный откат или отключение feature gate требует предварительных действий:
изменить затронутые HPA на minReplicas >= 1;
убедиться, что каждая нагрузка поднята хотя бы до одной реплики;
только после этого откатывать контроллер или отключать функцию.
Документация описывает сам риск, но в управляемом Kubernetes всё равно нужна репетиция обновления и отката: доступ к feature gate и флагам control plane может зависеть от провайдера.
| Модель | Переход из нуля | Что происходит с первым запросом или заданием | Особенности отказов |
|---|---|---|---|
HPA с minReplicas: 1 | Не требуется | Обрабатывается тёплой репликой | Постоянная стоимость, но меньше зависимостей в критическом пути |
| Native HPA 1.37 | По Object или External metric | Должен ожидать во внешнем буфере или очереди | Отказ метрики может оставить нагрузку в нуле |
| KEDA | Operator управляет переходом 0↔1, HPA — 1↔N | Зависит от источника события и его семантики | Есть порог активации, резервный режим, периодический опрос и уведомления об активности |
| Cloud Run или Lambda | Управляется платформой | Платформа берёт на себя часть ожидания и запуска | Действуют собственные ограничения, цены и правила повторной доставки |
minReplicas: 1 остаётся сильным базовым вариантомДля многих SaaS-компонентов одна маленькая постоянно работающая реплика дешевле сложного тракта метрик. Она устраняет холодный запуск из обычного пути, снижает зависимость от адаптера метрик и уменьшает объём аварийных процедур.
Scale-to-zero особенно мало даёт при частых коротких периодах простоя. Компонент успевает завершиться, но вскоре запускается снова; задержка и операционная сложность растут, а нода может так и остаться оплаченной.
Native HPA требует, чтобы Object или External metric уже была опубликована через Kubernetes Metrics API. Если источником служит Prometheus, обычно нужен адаптер, преобразующий запрос HPA в PromQL.
KEDA предлагает другую архитектуру. Operator определяет активность источника и управляет переходами 0↔1, после чего созданный KEDA объект HPA масштабирует нагрузку в диапазоне 1↔N. KEDA предоставляет множество готовых интеграций с источниками событий и внешний gRPC scaler (концепции KEDA, External scalers).
У KEDA можно разделить:
порог активации — достаточно ли спроса, чтобы выйти из нуля;
порог масштабирования — сколько реплик требуется после запуска.
Например, при пороге масштабирования 10 и пороге активации 50 значение 40 оставит нагрузку в нуле. Это позволяет не запускать дорогой обработчик ради единичного задания, но неправильно выбранный порог способен задерживать работу, хотя обычный расчёт HPA уже рекомендовал бы рост.
KEDA не становится ненужной после Kubernetes 1.37. Native HPA стандартизирует scale-to-zero, а KEDA по-прежнему полезна для готовых интеграций, push-активации, специфичных для источника порогов и резервного режима при отказе метрик.
Cloud Run по умолчанию может масштабироваться до нуля и самостоятельно связывает очередь запросов с запуском экземпляров. При достижении максимального числа экземпляров запрос ожидает ограниченное время, после чего может получить 429. Минимальное число экземпляров снижает задержку холодного старта, но оплачивается и остаётся best-effort-механизмом, а не абсолютной гарантией доступной мощности (автомасштабирование Cloud Run, minimum instances).
AWS Lambda показывает другой вариант тёплого резерва: Provisioned Concurrency заранее инициализирует среды выполнения ради более предсказуемой задержки. При этом интеграция с SQS всё равно требует согласовать тайм-аут функции, visibility timeout, повторные попытки и DLQ (жизненный цикл Lambda, SQS event source mapping).
Такие платформы снимают часть работы с команды, но не отменяют проектирование доставки и повторной обработки. Они также не являются прямой заменой Kubernetes: у них другая модель выполнения, ограничения и ценообразование.
Документированный интервал базового цикла HPA по умолчанию составляет 15 секунд и задаётся параметром --horizontal-pod-autoscaler-sync-period. Но считать холодный старт равным 15 секундам нельзя.
Практическая декомпозиция выглядит так:
T_wake ≈
T_metric_freshness
+ T_wait_next_HPA_sync
+ T_API/Deployment
+ T_schedule
+ T_image/init
+ T_readiness
+ T_node_provision, если подходящей ноды нет
Это инженерная модель, а не SLA Kubernetes.
В неё входят:
время до обновления исходной метрики;
ожидание следующего цикла HPA;
изменение желаемого числа реплик через API и контроллер Deployment;
планирование Pod’а;
загрузка образа и выполнение init containers;
инициализация приложения, JVM или модели;
прохождение проверок готовности;
при нехватке ресурсов — создание новой ноды.
Универсального значения p95 или p99 для этой цепочки нет. Оно зависит от размера образа, расположения реестра, конкуренции за планировщик, конфигурации проб, инициализации приложения и состояния пула нод. Его нужно измерять в целевом кластере.
Если HPA получает метрику через Prometheus, задержка начинается до цикла HPA. Глобальный scrape_interval Prometheus по умолчанию равен одной минуте, если не задано другое значение (конфигурация Prometheus). Затем Prometheus Adapter преобразует запрос External metric в PromQL согласно externalRules и metricsQuery.
Ошибки в labels, сопоставлении namespace или запросе могут привести к отсутствию либо неверному значению сигнала (External Metrics в Prometheus Adapter). В результате HPA может не получить сигнал для scale-up, и Pod может не запуститься, хотя задания уже накопились.
Таким образом, «HPA проверяет каждые 15 секунд» не означает «обработчик проснётся за 15 секунд». Метрика может стать видимой позже, а создание ноды и инициализация приложения добавят основную часть задержки.
KEDA также использует периодический опрос масштабировщиков через pollingInterval. Внешний масштабировщик, отправляющий уведомления об активности, может использовать долгоживущий StreamIsActive, который сообщает об активации вне обычного периода опроса. Кэширование метрик уменьшает нагрузку на источник и помогает соблюдать его ограничения частоты запросов, но меняет компромисс между свежестью сигнала и устойчивостью системы метрик (спецификация ScaledObject).
Планировщик и автомасштабирование нод опираются прежде всего на resource requests, а не на фактическое потребление CPU и памяти. Исчезновение Pod’а освобождает запрошенные ресурсы, но деньги экономятся только тогда, когда это позволяет удалить или заменить оплачиваемую ноду (Node Autoscaling).
Экономического эффекта может не быть, если:
у пула нод задан ненулевой минимум;
на ноде остаются другие Pod’ы;
DaemonSet’ы или ограничения размещения мешают консолидации;
освободившиеся ресурсы слишком фрагментированы;
GPU-ноду нельзя быстро удалить или вернуть;
оплаченная мощность зарезервирована независимо от использования.
Для оценки полезно разделить стоимость на три части:
TotalCost = IdleCapacityCost + ActivationCost + ReliabilityCost
Где:
IdleCapacityCost — стоимость постоянно удерживаемых Pod’ов, нод или GPU во время простоя;
ActivationCost — холодные запуски и временная мощность для всплесков;
ReliabilityCost — нарушения SLO, повторные попытки, дубли и операционное сопровождение.
Сравнивать minReplicas: 0 и minReplicas: 1 следует на реальном распределении интервалов простоя и всплесков, а не по средней загрузке. Нужны цены конкретных ресурсов, минимальные размеры групп нод, накладные расходы DaemonSet’ов и фактическая история консолидации.
При одной внешней метрике её недоступность может оставить нагрузку в нуле. HPA выставит ScalingActive=False, например с причиной FailedGetExternalMetric, но сам по себе этот статус не создаст резервную мощность.
Если HPA использует несколько метрик и одну из них получить не удалось, он не выполняет уменьшение числа реплик, когда доступные метрики предлагают уменьшение. При этом увеличение остаётся возможным, если хотя бы одна доступная метрика требует роста. Это полезная защита от ошибочного scale-down, но она не решает проблему единственного внешнего сигнала в состоянии нуля.
Для такого компонента Prometheus, адаптер и соответствующий запрос фактически становятся частью рабочего пути, хотя не обрабатывают сами задания. Их нужно наблюдать как производственную зависимость:
свежесть исходной метрики;
ошибки запросов HPA к Metrics API;
задержку и ошибки адаптера;
возраст старейшего задания в очереди;
расхождение между очередью и числом реплик;
продолжительность состояния ScalingActive=False.
KEDA при ошибке источника по умолчанию сохраняет текущее число экземпляров. Для поддерживаемых типов триггеров после заданного числа последовательных ошибок механизм fallback может синтетически задать HPA фиксированное число реплик; для CPU/memory triggers fallback не поддерживается. Это не гарантирует постоянно доступную мощность, а его поведение нужно проверять для выбранного типа триггера, установленной версии KEDA и соответствующего CRD. В native HPA аналогичную защиту нужно строить отдельно или предусматривать ручное восстановление (KEDA troubleshooting).
Исчезновение Pod’а безопасно только тогда, когда обработчик умеет корректно остановиться. При завершении Kubernetes отправляет процессу сигнал остановки, ждёт grace period, а затем может принудительно завершить его (Pod Lifecycle).
Корректный обработчик очереди должен:
перестать брать новые задания после начала завершения;
закончить или откатить текущую операцию;
зафиксировать результат в долговечном хранилище;
только после этого подтвердить сообщение;
допускать повторную доставку без повторного необратимого эффекта.
Типичная семантика таких систем — at-least-once, а не exactly-once. Например, при ошибке обработки пакета SQS/Lambda возвращает сообщения для повторной доставки. Без partial batch response повторно могут обрабатываться и записи, которые уже завершились успешно.
В зависимости от семантики брокера и протокола потребителя практический шаблон может включать:
долговечную очередь;
настройку тайм-аута видимости или аренды, согласованную со временем обработки;
подтверждение после фиксации результата;
идемпотентный ключ;
политику повторов;
DLQ;
корректное завершение обработки при SIGTERM.
HPA управляет числом реплик. Семантика брокера и протокол потребителя определяют, будут ли потеряны или продублированы данные.
| Тип компонента | Оценка | Основное условие |
|---|---|---|
| Пакетная обработка с длительными периодами простоя | Хороший кандидат | Задания сохранены и допускают ожидание |
| Формирование отчётов | Хороший кандидат | Пользователь не ждёт результат в синхронном запросе |
| Обработка видео или GPU-задач | Хороший кандидат | Допустим запуск образа, модели и при необходимости GPU-ноды |
| Асинхронные интеграции | Условно безопасно | Есть долговечная очередь, повторы и идемпотентность |
| Внутренние административные задания | Зависит от SLA | Допустима задержка первого запуска |
| Webhook receiver | Опасно без буфера | Входящий запрос должен быть быстро и надёжно принят |
| Публичный синхронный API | Обычно не подходит | Kubernetes Service не удерживает запрос до готовности Pod’а |
| Аутентификация, оформление заказа | Не подходит без тёплого резерва | Холодный старт переносится в критический пользовательский путь |
| Синхронный inference с жёстким p99 | Не подходит без тёплого резерва | Загрузка модели и ноды создаёт непредсказуемый хвост задержки |
Показательный безопасный сценарий — асинхронный GPU-обработчик видео. Пользовательский запрос завершается после долговечного принятия задания, а не после обработки. Очередь существует независимо от Pod’ов. Проверять при этом нужно три разные задержки: реакцию HPA, загрузку образа и модели, предоставление GPU-ноды.
Противоположный случай — endpoint аутентификации или публичный синхронный API. Здесь первый запрос не может безопасно исполнять роль сигнала пробуждения, если перед приложением нет отдельного постоянно работающего слоя, способного принять и удержать работу.
Начинать стоит не с массового изменения HPA, а с одного компонента и явной гипотезы: какую оплачиваемую мощность предполагается освободить и какой бюджет задержки допускается для первого задания.
До эксперимента должны быть известны:
максимально допустимый возраст задания;
допустимая задержка первого запуска после простоя;
поведение при недоступности метрики;
требуемая резервная мощность;
допустимость повторной обработки;
ресурс, который действительно может быть удалён: Pod, нода или GPU.
Проведите серии запусков после разных периодов простоя и собирайте p50, p95 и p99 для:
появления задания в источнике;
обновления метрики;
решения HPA или KEDA;
создания Pod’а;
назначения на ноду;
начала контейнера;
успешной проверки готовности;
начала обработки первого задания.
Отдельно повторите эксперимент при отсутствии свободной подходящей ноды. Иначе измерение покажет только тёплый кластер и скроет T_node_provision.
Минимальный набор проверок:
недоступен источник метрики;
адаптер возвращает ошибку или пустой результат;
очередь растёт, пока число реплик равно нулю;
контейнер получает сигнал SIGTERM во время обработки;
завершение превышает grace period;
сообщение доставляется повторно;
control plane обновляется или откатывается при наличии HPA с minReplicas: 0.
Для native HPA должна существовать инструкция ручного подъёма мощности. Для KEDA нужно проверить реальное поведение fallback для выбранного типа триггера, установленной версии оператора и CRD, а не только текущей документации.
Полезные сигналы для оповещений:
ScaledToZero и длительность нахождения в нуле;
ScalingActive=False;
ошибки External Metrics API, адаптера или KEDA;
возраст старейшего задания;
неудачные проверки готовности;
активация fallback;
принудительные завершения Pod’ов;
доля повторно обработанных сообщений;
время от появления спроса до первой готовой реплики.
После эксперимента сравните два периода: с одной постоянно работающей репликой и с нулевым минимумом. Проверяйте не только часы работы Pod’ов, но и:
удалялись ли ноды;
уменьшились ли оплачиваемые GPU- или VM-часы;
сколько добавилось холодных запусков;
выросли ли повторы и нарушения SLO;
сколько операционного времени потребовал тракт метрик.
Если Pod исчезает, но нода остаётся оплаченной, scale-to-zero улучшил размещение ресурсов, а не обязательно бюджет. Если одна тёплая реплика укладывается в стоимость и устраняет длинный хвост задержки, minReplicas: 1 может быть более экономичным решением с учётом надёжности.
Практическое правило простое: применять HPA scale-to-zero стоит там, где спрос хранится вне Pod’ов, холодный запуск укладывается в измеренный SLA, а освобождённые requests действительно позволяют сократить оплачиваемую мощность. Во всех остальных случаях ноль реплик может оказаться не экономией, а переносом инфраструктурного риска в пользовательскую задержку и эксплуатацию.
Разберёмся вместе —
подскажем, как решить
задачу быстро и эффективно