Реклама
Перетяжка // Коробка 3.0

Kubernetes 1.37 научил HPA гасить воркеры до нуля и будить их по очереди

HorizontalPodAutoscaler в Kubernetes 1.37 умеет уменьшать workload до нуля реплик по объектной или внешней метрике и поднимать его обратно без сторонних операторов. Функция в бете и включена по умолчанию.

Обложка: Kubernetes 1.37 научил HPA гасить воркеры до нуля и будить их по очереди

В Kubernetes 1.37 встроенный автоскейлер HorizontalPodAutoscaler научился уменьшать число реплик до нуля и снова поднимать их, когда метрика меняется. Об этом 2 сентября написал в блоге проекта Йоханнес Вюрбах. Функция перешла в статус beta и включена по умолчанию: в спецификации HPA можно указать minReplicas: 0, и последний простаивающий под уйдёт сам.

До 1.37 для этого нужен был сторонний компонент либо alpha-гейт. Теперь это часть ядра, и владельцы очередей и batch-обработчиков могут перестать платить за поды, которые часами ждут задач. Экономия заметнее всего там, где под резервирует дорогие ресурсы: выделенные ядра или GPU. Обычным HTTP-сервисам функция не подходит в чистом виде, и авторы предупреждают об этом прямо.

Ключевые выводы
  • Beta по умолчанию: feature gate HPAScaleToZero включён и в kube-apiserver, и в kube-controller-manager Kubernetes 1.37.
  • minReplicas: 0 работает только с объектной или внешней метрикой (например, длина очереди); HPA только на CPU и памяти API-сервер отклонит.
  • Kubernetes Service не буферизует запросы, пока нет готовых подов, поэтому для HTTP нужен отдельный слой буферизации.
  • Условие ScaledToZero=True отличает автоматическое уменьшение до нуля от ручной паузы: HPA не разбудит workload, который остановил не он.
  • Перед откатом версии или выключением гейта нужно вернуть minReplicas не меньше 1 и поднять всё, что стоит на нуле.

Почему CPU и память для этого не годятся

Обычно HPA масштабирует по загрузке CPU или памяти, а эти метрики приходят из работающих подов. Когда реплик ноль, измерять нечего, и сигнала «пора подниматься» не будет. Объектные и внешние метрики от подов не зависят: длина очереди существует независимо от того, есть ли у неё потребители. Поэтому minReplicas: 0 требует хотя бы одной такой метрики в спецификации, а HPA, где есть только ресурсные метрики, API-сервер отклоняет.

В блоге приводится пример с метрикой Prometheus queue_consumer_lag. Чтобы HPA её увидел, нужен адаптер, который отдаёт значение через External Metrics API, например Prometheus Adapter с правилом в externalRules. Перед созданием HPA авторы советуют проверить, что метрика читается:

			kubectl get --raw \
  '/apis/external.metrics.k8s.io/v1beta1/namespaces/default/queue_consumer_lag?labelSelector=name%3Dworker_tasks'
		

Если запрос не возвращает значение, сначала чинится конвейер метрик: HPA не сможет поднять workload с нуля, пока метрика недоступна. В этом случае он выставляет условие ScalingActive=False с причиной вроде FailedGetExternalMetric, и восстановить мощность придётся либо починкой метрики, либо ручным масштабированием.

Как выглядит HPA с нулём реплик

Пример из блога нацелен на Deployment queue-worker, допускает от нуля до десяти реплик и просит по одной реплике на каждые 30 задач в очереди:

			apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: queue-worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: queue-worker
  minReplicas: 0
  maxReplicas: 10
  metrics:
  - type: External
    external:
      metric:
        name: queue_consumer_lag
        selector:
          matchLabels:
            name: worker_tasks
      target:
        type: Value
        value: "30"
		

Когда очередь пуста, HPA уменьшает Deployment до нуля. Когда приходят задачи, внешняя метрика по-прежнему доступна, контроллер пересчитывает число реплик и поднимает поды в пределах maxReplicas. Остальные правила HPA продолжают действовать: в частности, окно стабилизации при уменьшении по умолчанию пять минут, так что короткий провал длины очереди не снесёт всех воркеров разом. Окно настраивается через spec.behavior.scaleDown.

Есть важная деталь: Deployment нужно запускать хотя бы с одной репликой. Ручная установка нуля реплик всегда означала паузу автоскейлинга, и это поведение сохранено: HPA не разбудит workload, который остановил не он сам.

Как контроллер отличает «ноль» от «паузы»

Ноль реплик двусмыслен: либо HPA сам всё погасил, либо оператор руками остановил сервис. Разрешает это условие статуса ScaledToZero. Когда HPA уменьшает workload с одной и более реплик до нуля, он записывает ScaledToZero=True, и последующие циклы согласования знают, что нулевое состояние принадлежит контроллеру и метрики нужно продолжать читать. После подъёма условие меняется на ScaledToZero=False с причиной NotScaledToZero. Workload на нуле без условия ScaledToZero=True считается поставленным на паузу. Посмотреть условия можно командой kubectl describe hpa queue-worker.

Кому это не подходит и чем грозит откат

Плата за нулевые реплики называется cold start: HPA должен заметить изменение метрики, планировщик разместить под, приложение подняться. Для очереди, которая надёжно хранит задачи, это нормально. Для HTTP это проблема: Service в Kubernetes не буферизует запросы, пока нет готовых подов, поэтому запросы, пришедшие в «ноль», просто не будут обслужены. Запросным нагрузкам нужен отдельный буферизующий слой, и блог его не предлагает, это остаётся на стороне пользователя.

С обновлением тоже есть оговорки. В 1.37 гейт HPAScaleToZero включён по умолчанию в обоих компонентах: API-сервер принимает minReplicas: 0, а controller-manager выполняет масштабирование по условию. При поэтапном обновлении control plane создавать HPA с нулём стоит только после того, как оба компонента обновлены и гейт активен на обоих: controller-manager без гейта воспринимает нулевые реплики как ручную паузу и может оставить workload лежать. Перед выключением гейта или откатом на версию без реализации на условиях нужно перевести затронутые HPA на minReplicas: 1 и выше и поднять до одной реплики всё, что сейчас стоит на нуле.

Что делать и что дальше

  1. Проверьте версию кластера: по умолчанию функция включена начиная с 1.37. Что ещё вошло в этот релиз, мы разбирали в новости про потоковое чтение коллекций из etcd: https://tproger.ru/news/kubernetes-1-37-chitaet-bolwie-kollekcii-iz-etcd-potokom-i-ekono
  2. Выберите кандидатов: потребители очередей, batch-обработчики, задачи на GPU. HTTP-сервисы без буфера оставьте на minReplicas: 1.
  3. Убедитесь, что метрика читается через External Metrics API, до создания HPA с нулём.
  4. Стартуйте Deployment с одной репликой и следите за условиями ScaledToZero и ScalingActive в describe hpa.
  5. Оцените cold start для своего образа: время подъёма пода плюс инициализация приложения задают задержку первой задачи после простоя.

Путь у функции длинный: первая alpha-реализация появилась ещё в Kubernetes 1.16, версия 1.36 добавила условие ScaledToZero и поведение контроллера, которое отличает автоматику от ручной паузы, а 1.37 включила всё по умолчанию после интеграционных и end-to-end тестов на уменьшение до нуля и подъём по внешней метрике. Дизайн описан в KEP-2021, функцией владеет SIG Autoscaling. Про GA авторы пока не говорят: следующий шаг, по их словам, собрать эксплуатационный опыт с беты; обратную связь ждут в канале #sig-autoscaling в Slack Kubernetes.

Источники: Kubernetes v1.37: Scale Workloads to Zero with HorizontalPodAutoscaler (блог Kubernetes, Johannes Würbach), KEP-2021: HPA supports scaling to and from zero pods, Prometheus Adapter: external metrics

Изображение на обложке: Логотип: The Kubernetes Authors