Дайджесты новостей
Изометрический разрез ночного кластера Kubernetes с усыплением непроизводственных подов до нуля с помощью KEDA Cron Scaler.

Масштабирование Kubernetes в ноль с KEDA: усыпляем dev-кластеры на ночь

Посчитай на досуге простую арифметику своего облачного биллинга. Стандартная рабочая неделя инженеров, тестировщиков и продакт-менеджеров длится около 45–50 часов: с девяти утра до семи вечера с понедельника по пятницу. В сутках 24 часа, в неделе — 168 часов. Это означает, что тестовые и предпроизводственные среды (development, QA, staging) используются активными людьми меньше чем треть времени.

Остальные 118 часов в неделю — а это почти 70% всего времени жизни кластера — дорогостоящие виртуальные машины в AWS EKS, Google GKE или арендованные ноды в дата-центре просто молотят воздух. Поды крутятся, базы данных простаивают, а счетчик денег в облачной панели монотонно тикает в пользу провайдера.

Казалось бы, решение лежит на поверхности: настроить расписание и гасить поды на ночь. Но в стандартном арсенале Kubernetes тебя ждет неприятный сюрприз.

Архитектурный тупик Horizontal Pod Autoscaler

Первая мысль любого системного инженера — воспользоваться нативным механизмом Horizontal Pod Autoscaler (HPA). Но здесь мы упираемся в жесткое архитектурное ограничение ядра Kubernetes: поле minReplicas в спецификации HPA принципиально не может быть меньше единицы.

Если ты попытаешься применить манифест с minReplicas: 0, API-сервер отвергнет его на этапе валидации схемы. Разработчики контроллера исходили из консервативной логики: HPA оценивает загрузку CPU и памяти существующих подов. Если подов в кластере ноль, то и метрики с них собирать невозможно, а значит, система никогда не узнает, когда нужно снова проснуться.

В итоге команды либо пишут громоздкие самописные bash-скрипты, которые по ночам через cron дергают kubectl scale deployment --replicas=0, либо мирятся с гигантскими переплатами.

К счастью, в экосистеме Cloud Native есть зрелый проект уровня CNCF Graduated — KEDA (Kubernetes Event-Driven Autoscaling). Инженер Мейндерт Стёй (Mijndert Stuij) в своем руководстве показал, как элегантно усыпить dev-окружение с помощью триггера KEDA Cron Scaler.

Как KEDA обходит ограничение нулевой реплики

Секрет KEDA кроется в четком разделении обязанностей между двумя разными уровнями управления:

  1. Режим бодрствования (от 1 до Max реплик): когда рабочее время активно, KEDA динамически создает и контролирует обычный HPA-объект, позволяя сервису свободно масштабироваться под нагрузкой.
  2. Переход в ноль (Scale to Zero): когда наступает время сна, контроллер KEDA берет управление на себя и напрямую перезаписывает поле .spec.replicas в целевом Deployment, выставляя его в ноль.

В манифесте ScaledObject мы объявляем временное окно доступности сервиса:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: backend-service-autoscaler
  namespace: development
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend-service
  minReplicaCount: 0
  maxReplicaCount: 5
  cooldownPeriod: 300
  triggers:
    - type: cron
      metadata:
        timezone: Europe/Moscow
        start: 0 8 * * 1-5    # Старт в 08:00 по будням
        end: 0 20 * * 1-5      # Усыпление в 20:00 по будням
        desiredReplicas: "1"

Обрати внимание на поле timezone: указание явного часового пояса спасает от классической путаницы со временем UTC, в котором по умолчанию работают ноды кластера. Вне интервала с 08:00 до 20:00 KEDA автоматически переводит поды в состояние 0 current / 0 desired.

Подводный камень GitOps: война ArgoCD и KEDA

Если в твоей компании настроен честный GitOps через ArgoCD или Flux, внедрение KEDA в лоб немедленно приведет к «войне контроллеров».

Картина выглядит так: в 20:00 KEDA выставляет в деплойменте 0 реплик. ArgoCD видит, что живое состояние кластера разошлось с описанием в Git-репозитории (где было указано replicas: 1), объявляет статус OutOfSync и услужливо возвращает одну реплику обратно.

Чтобы подружить KEDA с ArgoCD, необходимо настроить два важных правила:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: development-backend
  namespace: argocd
spec:
  # 1. Запрещаем ArgoCD перезаписывать количество реплик, управляемых KEDA
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas
  syncPolicy:
    automated:
      selfHeal: true
      prune: true
    syncOptions:
      # 2. Обязательно включаем ServerSideApply для установки тяжелых CRD KEDA
      - ServerSideApply=true

Директива ignoreDifferences сообщает GitOps-движку: «Следи за версиями образов, переменными окружения и портами, но оставь число подов на усмотрение внешнего автомасштабировщика».

Экономика и реальность холодного старта

Какую выгоду дает эта схема на практике? Если твой dev-кластер состоит из десятка микросервисов, усыпление на ночь и выходные экономит от 60% до 67% затрат на вычислительные узлы. Если в кластере настроен Karpenter или Cluster Autoscaler, при усыплении подов пустые виртуальные машины будут автоматически выводиться из пула (node consolidation), физически снижая чек за инфраструктуру.

Но чудес не бывает, и за экономию приходится платить понятным инженерным компромиссом — холодным стартом (cold start). Когда первый разработчик или тестировщик открывает сервис утром, кластеру требуется от 15 до 60 секунд, чтобы поднять контейнеры, прогреть рантайм и пройти Readiness-пробы.

Для сервисов на Go, Rust или Node.js задержка практически незаметна: они стартуют за пару секунд. А вот для тяжелых монолитов на Spring Boot или сервисов машинного обучения прогрев может занять пару минут. Самый простой и элегантный способ обойти эту задержку — настроить расписание start на 15–30 минут раньше фактического начала рабочего дня команды. В 08:30 кластер плавно просыпается, и к девяти утра все сервисы уже ждут инженеров в полной боевой готовности.