Привет, Хабр.
Меня зовут Максим Качинский, я Head of Ops в Rocketdata. В этой статье разберу KEDA как практический инструмент для Kubernetes-workloads с непостоянной нагрузкой: очередями, расписаниями, HTTP-пиками и внешними метриками.
Материал не про «поставили автоскейлер и сразу сэкономили», а про production-подход: где KEDA действительно помогает, какие параметры нужно ограничивать заранее, как проверять экономику пилота и что может пойти не так при раскатке.
В сообществе «Практики FinOps» мы разбираем не только отчеты, аллокацию и бюджетные процессы, но и инженерные механики, которые помогают влиять на инфраструктурные косты до того, как они превращаются в строку в биллинге.
TL;DR
Проблема: HPA масштабирует по CPU/Memory, но не видит очереди, расписания и бизнес-метрики. Сервисы простаивают 18 часов в сутки и жгут бюджет, пока ждут редкую нагрузку.
Решение: KEDA масштабирует поды по событиям — длина очереди, cron, Prometheus-метрики, HTTP-трафик. Сервис включается под работу и выключается в простое.
FinOps-профит:
Scale-to-zero: 0 подов когда нагрузки нет → экономия на холостом простое
maxReplicaCount: финансовый предохранитель от бесконтрольного разгона
Экономия 40–70% на workloads с непостоянной нагрузкой (воркеры, batch-задачи, dev-окружения)
Дальше разберем KEDA именно как практический FinOps-гардрейл для Kubernetes: где HPA уже не хватает, как устроен ScaledObject, как безопасно подходить к scale-to-zero, какие ограничения ставить через maxReplicaCount и какие метрики собрать до пилота, чтобы потом доказать эффект цифрами.
В Kubernetes легко привыкнуть к мысли, что автоскейлинг уже есть, значит проблема с динамической нагрузкой решена. На практике все сложнее. Стандартный HPA хорошо работает там, где нагрузка выражается через CPU и память. Но часть сервисов живет по другой логике: ждет сообщения в очереди, включается по расписанию, получает краткие пики HTTP-трафика или обрабатывает задачи пачками.
С точки зрения FinOps это важный слой. Если сервис держит поды и ноды включенными «на всякий случай», инфраструктура оплачивается не за полезную работу, а за ожидание. KEDA помогает сдвинуть масштабирование ближе к фактической нагрузке: событиям, очередям, расписанию, HTTP-трафику и внешним метрикам.
Это не замена управлению затратами, аллокации и ownership. Но это рабочий инженерный гардрейл, который помогает не держать ресурсы включенными без необходимости и заранее ограничивать верхнюю границу масштабирования.
Где стандартного HPA уже не хватает
Horizontal Pod Autoscaler обычно воспринимается как базовый ответ на вопрос «как масштабировать поды». В типовом сценарии он принимает решения по CPU и памяти.
Но не вся нагрузка проявляется через эти метрики.
Пример: есть воркер, который разбирает сообщения из очереди. Он может почти не грузить CPU, потому что ждет новые сообщения или ответа от внешнего API. При этом очередь уже растет, время обработки увеличивается, SLA начинает проседать.
Для HPA картина может выглядеть спокойно:
CPU: 2% Memory: stable Replicas: 2
А фактическая бизнес-нагрузка выглядит так:
Queue length: 3 250 messages Oldest message age: 18 minutes SLA: at risk
Если в этот момент масштабироваться только по CPU, реакция будет запаздывать. Если держать большой резерв подов постоянно, команда платит за мощность, которая большую часть времени простаивает.
Это и есть классический over-provisioning: ресурсы заняты, счет растет, полезной работы в этот момент нет.
Что делает KEDA
KEDA, Kubernetes Event-driven Autoscaling, добавляет в Kubernetes событийное масштабирование. Важная деталь: KEDA не заменяет HPA, а расширяет его. Он позволяет масштабировать контейнеры в Kubernetes по количеству событий, которые нужно обработать. Официальная документация KEDA.
KEDA может работать с разными источниками нагрузки:
очереди сообщений (RabbitMQ, Kafka, SQS, Azure Service Bus);
расписания (cron);
базы данных (PostgreSQL, MySQL, Redis);
HTTP-трафик (через HTTP Add-on);
метрики из Prometheus;
внешние event source через 60+ встроенных скейлеров.
Как это работает внутри
┌─────────────┐ ┌──────────────┐ │ RabbitMQ │────────▶│ KEDA Scaler │ │ Queue: 150 │ │ polls every │ └─────────────┘ │ 30 sec │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ External │ │ Metrics API │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ HPA decides │ │ 150/10 = 15 │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ Deployment │ │ scales to 15 │ └──────────────┘
KEDA проверяет источник событий, передает метрику в Kubernetes через External Metrics API, а HPA уже управляет количеством реплик. В документации KEDA отдельно описывает scaling для Deployments, StatefulSets и Custom Resources через scale subresource. KEDA: Scaling Deployments, StatefulSets & Custom Resources.
Установка KEDA через Helm
Базовая установка через Helm выглядит так:
helm repo add kedacore https://kedacore.github.io/charts helm repo update helm install keda kedacore/keda \ --namespace keda \ --create-namespace
После установки можно проверить поды в namespace keda:
kubectl get pods -n keda
Такой способ установки указан в официальной документации KEDA: добавить Helm-репозиторий, обновить его и установить chart в отдельный namespace. KEDA: Deploying KEDA.
Базовая структура ScaledObject
Основная сущность для масштабирования workload — это ScaledObject.
Упрощенно структура выглядит так:
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: example-scaler spec: scaleTargetRef: name: example-deployment minReplicaCount: 0 maxReplicaCount: 10 pollingInterval: 30 cooldownPeriod: 300 triggers: - type: ... metadata: ...
Ключевые параметры:
scaleTargetRef - Целевой Deployment, StatefulSet или другой ресурс, который KEDA будет масштабировать.
minReplicaCount - Минимальное количество реплик. Для scale-to-zero обычно указывают 0.
maxReplicaCount - Верхняя граница реплик. Это технический лимит роста, который помогает заранее ограничить масштабирование.
pollingInterval - Как часто KEDA проверяет источник событий (в секундах).
cooldownPeriod - Пауза перед масштабированием обратно к 0 после того, как триггер перестал быть активным.
triggers - Источник нагрузки и параметры конкретного скейлера.
По спецификации ScaledObject, pollingInterval по умолчанию составляет 30 секунд, cooldownPeriod по умолчанию 300 секунд, minReplicaCount по умолчанию 0, а maxReplicaCount по умолчанию 100. KEDA: ScaledObject specification.
Scale-to-zero как FinOps-механика
Scale-to-zero — одна из главных функций KEDA с точки зрения FinOps.
Если событий нет, очередь пуста, расписание не наступило или трафика нет, KEDA может уменьшить количество реплик приложения до 0. Это значит, что сервис не держит вычислительные ресурсы только потому, что «вдруг пригодится».
В обычной инфраструктурной логике часто появляется постоянный резерв:
min replicas: 2 traffic: 0 queue: 0 cost: continues
С scale-to-zero логика меняется:
min replicas: 0 traffic: 0 queue: 0 cost for workload compute: reduced
Здесь важно не переобещать. Scale-to-zero не означает, что счет за весь кластер станет нулевым. Control plane, системные компоненты, ноды, диски, логи, сеть и другие ресурсы могут продолжать стоить денег. Но для конкретного workload это снижает холостой простой.
FinOps-смысл простой: платить не за ожидание, а за обработку нагрузки.
Реальный кейс: фоновый воркер обработки заказов
Рассмотрим типичный сценарий — воркер, который обрабатывает заказы из RabbitMQ.
До внедрения KEDA
Конфигурация:
Deployment с фиксированным количеством реплик: 5
Режим работы: 24/7
Пиковая нагрузка: рабочие дни 10:00–18:00
Ночью и в выходные: очередь пустая
Метрики:
Постоянно: 5 реплик × 24 часа = 120 pod-hours/день
Средняя утилизация CPU: 8% (большую часть времени воркеры простаивают)
Очередь пустая ~18 часов в сутки
Стоимость compute для workload: ~$180/месяц (из расчета $0.05/pod-hour)
Проблема: платим за 5 подов круглосуточно, хотя реальная работа происходит 6–8 часов в день.
После внедрения KEDA
Конфигурация:
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: orders-worker-scaler spec: scaleTargetRef: name: orders-worker minReplicaCount: 0 maxReplicaCount: 10 triggers: - type: rabbitmq metadata: queueName: orders mode: QueueLength value: "20"
Поведение:
10:00–18:00 (пик): очередь 150–200 сообщений → 8–10 реплик
18:00–22:00 (спад): очередь 20–40 сообщений → 1–2 реплики
22:00–10:00 + выходные: очередь пустая → 0 реплик
Метрики:
Среднее за день: ~40 pod-hours/день
Утилизация CPU в активное время: 45–60% (поды заняты работой)
Cold start при появлении первого сообщения: ~12 секунд
SLA на обработку сообщения (5 минут): не нарушен
Стоимость compute для workload: ~$60/месяц
Результат:
Экономия: 67% на этом workload ($120/месяц)
Утилизация выросла с 8% до 45–60%
SLA не пострадал — cold start укладывается в допустимые рамки
maxReplicaCount: 10 защищает от бесконтрольного роста при аномалиях
График поведения реплик

Этот кейс типичен для:
воркеров обработки заказов, уведомлений, email-рассылок
batch-задач по расписанию
внутренних сервисов с рабочими часами
dev/staging окружений
Сценарий №1. Очереди: от постоянного резерва к событийному масштабированию
Классический сценарий для KEDA — фоновые воркеры, которые разбирают сообщения из RabbitMQ, Kafka, SQS или другой очереди.
Допустим, есть orders-worker, который обрабатывает сообщения из очереди orders.
Нужно добиться такой логики:
0 сообщений -> 0 подов 10 сообщений -> 1 под 100 сообщений -> 10 подов очередь опустела -> поды удалились
Для RabbitMQ можно использовать trigger rabbitmq. В режиме QueueLength параметр value задает порог на одну реплику. Например, если один под должен обрабатывать 10 сообщений, значение можно поставить равным 10. В документации KEDA для RabbitMQ указано, что mode: QueueLength масштабирует по числу сообщений в очереди, а value задает threshold per instance. Там же приведен пример: при пороге 10 и 30 сообщениях скейлер поднимет до 3 подов. KEDA: RabbitMQ Queue scaler.
Безопасное хранение credentials
Credentials для подключения к RabbitMQ нельзя хардкодить в YAML.
❌ Плохо:
metadata: host: "amqp://admin:password123@rabbitmq:5672/"
✅ Хорошо — Kubernetes Secret:
apiVersion: v1 kind: Secret metadata: name: rabbitmq-connection namespace: app type: Opaque stringData: host: amqp://user:password@rabbitmq.app.svc.cluster.local:5672/ --- apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: rabbitmq-auth namespace: app spec: secretTargetRef: - parameter: host name: rabbitmq-connection key: host
✅✅ Еще лучше — External Secrets Operator:
Для production рекомендую использовать External Secrets Operator с Vault, AWS Secrets Manager или аналогами:
apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: rabbitmq-connection namespace: app spec: secretStoreRef: name: vault-backend kind: SecretStore target: name: rabbitmq-connection data: - secretKey: host remoteRef: key: prod/rabbitmq property: connection_string
Это позволяет:
Централизованно управлять секретами
Ротировать credentials без правки YAML
Аудитировать доступ к секретам
Не хранить пароли в Git
Пример ScaledObject для RabbitMQ
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: orders-worker-rabbitmq-scaler namespace: app spec: scaleTargetRef: name: orders-worker minReplicaCount: 0 maxReplicaCount: 10 pollingInterval: 30 cooldownPeriod: 300 triggers: - type: rabbitmq metadata: protocol: amqp queueName: orders mode: QueueLength value: "10" authenticationRef: name: rabbitmq-auth
Что здесь важно с точки зрения эксплуатации:
maxReplicaCount: 10ограничивает верхнюю границу ростаminReplicaCount: 0разрешает scale-to-zerovalue: "10"задает целевую длину очереди на одну репликуcooldownPeriod: 300не удаляет поды мгновенно после снижения нагрузки
С точки зрения FinOps это убирает постоянный «горячий резерв» там, где он не нужен круглосуточно.
Сценарий №2. Расписание: выключаем dev-окружения на ночь
Не вся нагрузка приходит из очереди. Иногда она заранее известна:
ночные ETL-процессы
утренние отчеты
рабочие часы B2B-сервиса
внутренние сервисы, которые не нужны ночью
dev- и staging-окружения
Для таких сценариев подходит cron scaler.
Допустим, B2B-сервис должен работать в рабочие дни с 08:50 до 19:00. В это окно нужно держать 3 реплики. Вне окна сервис можно опускать до 0.
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: b2b-api-office-hours namespace: app spec: scaleTargetRef: name: b2b-api minReplicaCount: 0 maxReplicaCount: 5 cooldownPeriod: 300 triggers: - type: cron metadata: timezone: Europe/Moscow start: "50 8 * * 1-5" end: "0 19 * * 1-5" desiredReplicas: "3"
Для масштабирования до 0 вне рабочего окна KEDA рекомендует указывать minReplicaCount: 0 в ScaledObject и увеличивать число реплик в рабочее время через desiredReplicas. Фактическое масштабирование вниз произойдет после cooldownPeriod, по умолчанию через 5 минут после end. KEDA: Cron scaler.
Почему это лучше bash-скриптов
Раньше для таких сценариев писали bash-скрипты:
#!/bin/bash # /opt/scripts/scale-dev-env.sh if [ $(date +%H) -ge 19 ] || [ $(date +%H) -lt 9 ]; then kubectl scale deployment/api --replicas=0 -n dev else kubectl scale deployment/api --replicas=3 -n dev fi
И запускали через cron на каком-то сервере.
Проблемы такого подхода:
Кто владелец? Скрипт может работать годами, пока не сломается. Тогда выясняется, что автор уволился 2 года назад.
Где документация? Почему 3 реплики? Почему 19:00? Почему этот namespace?
Где версионность? Скрипт лежит на сервере, не в Git.
Где observability? Упал скрипт или отработал — непонятно без ручной проверки.
Боязнь трогать: “Работает — не трожь” превращается в technical debt.
Cron Scaler не отменяет необходимость нормального ownership, но переносит расписание в понятную Kubernetes-конфигурацию:
Декларативная конфигурация в Git
Стандартный lifecycle через kubectl/GitOps
События в Kubernetes Events
Метрики KEDA в Prometheus
Сценарий №3. Масштабирование по метрикам Prometheus
Не всегда источник нагрузки — это очередь или расписание. Иногда нужная метрика уже есть в Prometheus.
Например, можно масштабировать API по количеству HTTP-запросов в секунду (RPS):
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: api-rps-scaler namespace: app spec: scaleTargetRef: name: api-service minReplicaCount: 1 maxReplicaCount: 20 pollingInterval: 30 cooldownPeriod: 300 triggers: - type: prometheus metadata: serverAddress: http://prometheus.monitoring.svc.cluster.local:9090 query: sum(rate(http_requests_total{deployment="api-service"}[2m])) threshold: "100"
По документации KEDA, Prometheus scaler масштабирует workload по запросу к Prometheus. При этом query должен возвращать один scalar или vector single element response, а threshold задает порог для масштабирования. KEDA: Prometheus scaler.
Важные проверки перед production
В этом сценарии важно проверить саму метрику:
Query должен возвращать один результат — scalar или vector с одним элементом
Метрика должна быть стабильной — без провалов и дублирования
Окно
rateдолжно соответствовать характеру нагрузки:[2m]для быстрой реакции на пики[5m]для более плавного масштабирования
Threshold нужно подбирать по нагрузочному тесту, а не на глаз:
Запустить нагрузочное тестирование
Замерить RPS, который выдерживает 1 под
Поставить threshold с запасом (например, 70–80% от максимума)
maxReplicaCount должен учитывать лимиты:
Пропускная способность базы данных
Rate limits внешних API
Лимиты connections pool
Примеры других метрик для масштабирования
# Масштабирование по задержкам (latency) query: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{service="api"}[5m])) threshold: "0.5" # 500ms на p95 # Масштабирование по количеству активных соединений query: sum(nginx_connections_active{deployment="nginx"}) threshold: "1000" # Масштабирование по длине очереди в Redis query: redis_list_length{list="tasks"} threshold: "50" # Масштабирование по custom business-метрике query: sum(rate(orders_created_total[5m])) threshold: "10" # 10 заказов/сек на реплику
Для FinOps это важный сценарий, потому что масштабирование можно привязать ближе к бизнес-нагрузке — RPS, конверсия, количество активных пользователей — а не только к CPU.
Сценарий №4. HTTP-трафик и краткие пики нагрузки
Маркетинговая акция, email-рассылка или внешний инфоповод могут резко увеличить трафик в несколько раз. Держать сервис постоянно готовым к такому пику дорого, потому что большую часть времени эта мощность будет простаивать.
Для HTTP-нагрузок у KEDA есть HTTP Add-on. Он масштабирует HTTP workloads по входящему трафику и поддерживает сценарии scale-to-zero.
Как работает HTTP Add-on
┌──────────┐ ┌─────────────────┐ ┌─────────────┐ │ Ingress │─────▶│ HTTP Interceptor│─────▶│ Application │ │ │ │ (queue proxy) │ │ Pods │ └──────────┘ └─────────────────┘ └─────────────┘ │ ▼ ┌─────────────────┐ │ KEDA Scaler │ │ (по RPS/pending)│ └─────────────────┘
HTTP Interceptor перехватывает запросы:
Если поды есть → проксирует запросы
Если подов 0 → ставит запросы в очередь, сигналит KEDA → поды поднимаются → запросы передаются
Пример конфигурации
apiVersion: http.keda.sh/v1alpha1 kind: HTTPScaledObject metadata: name: api-http-scaler namespace: app spec: scaleTargetRef: name: api-service kind: Deployment service: api-service port: 8080 replicas: min: 0 max: 20 scalingMetric: requestRate: targetValue: 100 window: 1m
Production checklist для HTTP Add-on
Перед раскаткой на production важно проверить:
1. Cold start
Сколько времени требуется от 0 до первого ready-пода?
Укладывается ли в timeout клиентов?
Настроен ли
initialDelaySecondsв readiness probe?
2. Таймауты
Timeout на Ingress
Timeout на HTTP Interceptor
Timeout клиента
Должны быть согласованы: client > interceptor > ingress > pod
3. Readiness probes
readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 3 failureThreshold: 2
4. Лимиты соединений
Проверить connection pool базы данных
Проверить rate limits внешних API
Настроить
maxReplicaCountс учетом этих лимитов
5. План отката
# Быстрый откат: ставим min replicas обратно kubectl patch httpscaledobject api-http-scaler -n app \ --type='json' -p='[{"op": "replace", "path": "/spec/replicas/min", "value": 2}]' # Или полное отключение KEDA для этого workload kubectl delete httpscaledobject api-http-scaler -n app kubectl scale deployment/api-service --replicas=3 -n app
Верхняя граница: maxReplicaCount как финансовый предохранитель
FinOps — это не только экономия в простое. Важно еще ограничивать расходы сверху.
Автоскейлинг без верхней границы может разогнать счет, если:
в коде появился баг (бесконечный retry, memory leak)
очередь начала расти из-за деградации внешнего сервиса
метрика настроена слишком агрессивно
приложение попало под аномальную нагрузку (DDoS, scraping)
downstream-система стала отвечать медленнее → воркеры висят дольше → KEDA поднимает еще воркеров
maxReplicaCount задает максимальное количество реплик, выше которого workload не поднимется:
spec: minReplicaCount: 0 maxReplicaCount: 10
Как правильно выбрать maxReplicaCount
Здесь важно смотреть на систему целиком:
❌ Слишком низко:
Защитил счет на облако
Но получил очередь, задержки, нарушение SLA
Downstream-системы в порядке, а ты не обрабатываешь
❌ Слишком высоко:
Защитил SLA
Но увеличил финансовый риск
Можешь задавить базу или внешний API
✅ Правильный подход:
Замерить пропускную способность сервиса:
# Нагрузочное тестирование # Сколько RPS выдерживает 1 под? # Сколько сообщений/сек обрабатывает 1 воркер?Проверить лимиты зависимостей:
PostgreSQL connection pool: 100 Если каждый под держит 10 connections → максимум 10 подовДобавить запас на пики:
Обычная нагрузка: 5 подов Пиковая нагрузка: 8 подов → maxReplicaCount: 12 (50% запас)Настроить алерт на приближение к лимиту:
# Prometheus alert - alert: KEDANearMaxReplicas expr: keda_scaledobject_replicas / keda_scaledobject_max_replicas > 0.8 for: 5m annotations: summary: "ScaledObject {{ $labels.scaledobject }} at 80% of maxReplicaCount"
Нормальный подход — не «поставить красивое число», а связать лимит с реальными возможностями инфраструктуры.
cooldownPeriod и плавное масштабирование вниз
cooldownPeriod задает паузу перед масштабированием обратно к 0 после того, как триггер перестал быть активным.
spec: cooldownPeriod: 300 # 5 минут
Это значит, что после прекращения активности KEDA не будет сразу уводить workload в 0. Пауза помогает избежать ситуации, когда поды создаются и удаляются слишком часто из-за кратких колебаний нагрузки (flapping).
Production-настройка с плавным scale down
apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: api-rps-scaler namespace: app spec: scaleTargetRef: name: api-service minReplicaCount: 1 maxReplicaCount: 20 cooldownPeriod: 300 advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 - type: Pods value: 2 periodSeconds: 60 selectPolicy: Min # Выбирать более консервативную политику scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 15 - type: Pods value: 4 periodSeconds: 15 selectPolicy: Max # Выбирать более агрессивную политику triggers: - type: prometheus metadata: serverAddress: http://prometheus.monitoring.svc.cluster.local:9090 query: sum(rate(http_requests_total{deployment="api-service"}[2m])) threshold: "100"
Что здесь происходит:
Scale Down (консервативный):
stabilizationWindowSeconds: 300— смотреть на метрики за последние 5 минутУменьшать либо на 50% либо на 2 пода (что меньше) каждую минуту
selectPolicy: Min— выбирать более осторожный вариант
Scale Up (агрессивный):
stabilizationWindowSeconds: 0— реагировать мгновенноУвеличивать либо на 100% либо на 4 пода (что больше) каждые 15 секунд
selectPolicy: Max— выбирать более быструю реакцию
Такая конфигурация готова к production: быстро растем под нагрузкой, медленно сжимаемся при спаде.
Мониторинг KEDA в production
Внедрить KEDA без мониторинга — значит летать вслепую. Крайне рекомендую сделать дашборд с алертами, примеры под катом:
Запросы и алерты
# Количество активных ScaledObjects keda_scaler_active # Текущее количество реплик keda_scaledobject_replicas # Максимальное количество реплик (из spec) keda_scaledobject_max_replicas # Текущее значение метрики скейлера keda_scaler_metrics_value # Количество ошибок скейлера rate(keda_scaler_errors_total[5m]) # Latency проверки скейлера keda_scaler_metrics_latency
Обязательные алерты
1. Упор в maxReplicaCount
- alert: KEDAMaxReplicasReached expr: keda_scaledobject_replicas >= keda_scaledobject_max_replicas for: 5m labels: severity: warning annotations: summary: "ScaledObject {{ $labels.scaledobject }} reached maxReplicaCount" description: "Workload hit the ceiling. Check if queue/load is growing."
2. Ошибки скейлера
- alert: KEDAScalerErrors expr: rate(keda_scaler_errors_total[5m]) > 0 for: 2m labels: severity: critical annotations: summary: "KEDA scaler {{ $labels.scaler }} has errors" description: "KEDA cannot fetch metrics. Autoscaling may be broken."
3. Рост очереди при максимальных репликах
- alert: QueueGrowingAtMaxScale expr: | keda_scaler_metrics_value{scaler="rabbitmq"} > 1000 and keda_scaledobject_replicas >= keda_scaledobject_max_replicas for: 10m labels: severity: critical annotations: summary: "Queue growing but already at max replicas" description: "Need to increase maxReplicaCount or optimize workers."
4. Долгий cold start
- alert: SlowColdStart expr: | avg_over_time(keda_scaledobject_replicas[5m]) == 0 and avg_over_time(some_app_request_duration_seconds[5m]) > 30 for: 5m annotations: summary: "Cold start taking too long" description: "App scaled from 0 but startup is slow. Check image size, probes."
Debugging: что смотреть когда не работает
# 1. Логи KEDA operator kubectl logs -n keda deployment/keda-operator -f # 2. Логи metrics server kubectl logs -n keda deployment/keda-metrics-apiserver -f # 3. Статус ScaledObject kubectl describe scaledobject <name> -n <namespace> # Смотрим на поля: # - Status.Conditions — есть ли ошибки # - Status.ScaleTargetKind — правильный ли target # - Events — что происходило недавно # 4. HPA созданный KEDA kubectl get hpa -n <namespace> kubectl describe hpa keda-hpa-<name> -n <namespace> # Проверяем: # - Current metrics # - Desired replicas vs Current replicas # - Conditions # 5. События в namespace kubectl get events -n <namespace> --sort-by='.lastTimestamp' | tail -20 # 6. Проверить доступность источника метрик # Для Prometheus scaler: kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl "http://prometheus.monitoring.svc:9090/api/v1/query?query=your_query" # Для RabbitMQ scaler: kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl "http://user:pass@rabbitmq.app.svc:15672/api/queues/%2F/orders"
Типичные проблемы и решения
1. Cold start съедает SLA
Проблема: Первый запрос после scale-to-zero ждет 30+ секунд.
Причины:
Большой Docker-образ
Долгая инициализация приложения
База данных требует warm-up connection pool
Загрузка конфигов/секретов
Решения:
а) Если критично — отключить scale-to-zero:
minReplicaCount: 1 # вместо 0
б) Оптимизировать образ:
Пример оптимизации для python имеджа
# Multi-stage build FROM python:3.11-slim as builder RUN pip install --user package1 package2 FROM python:3.11-slim COPY --from=builder /root/.local /root/.local COPY app.py . CMD ["python", "app.py"] # Результат: образ 150MB вместо 800MB # Cold start: 8 секунд вместо 25
в) Настроить readiness probe:
readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 periodSeconds: 2 failureThreshold: 3
г) Для HTTP Add-on — буферизация: HTTP Interceptor может держать запросы в очереди, пока поды поднимаются.
2. Очередь растет быстрее чем scale up
Проблема: Сообщения прибывают со скоростью 1000/сек, а поды поднимаются ~30 сек. Очередь растет быстрее чем обрабатывается.
Решения:
а) Уменьшить pollingInterval:
pollingInterval: 10 # вместо 30
б) Увеличить maxReplicaCount с запасом:
maxReplicaCount: 20 # вместо 10
в) Агрессивная политика scale up:
advanced: horizontalPodAutoscalerConfig: behavior: scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 15
г) Проверить лимиты downstream:
Если база данных выдерживает только 100 connections, а каждый под держит 10 connections, то больше 10 подов = ошибки подключения Решение: - Увеличить connection pool базы - Оптимизировать использование connections в коде - Использовать connection pooler (PgBouncer)
3. Flapping: KEDA создает/удаляет поды каждые 2 минуты
Проблема: Нагрузка колеблется вокруг порога. Поды постоянно создаются и удаляются.
Симптомы:
10:00 - 5 pods 10:02 - 3 pods 10:04 - 5 pods 10:06 - 3 pods ...
Решения:
а) Увеличить cooldownPeriod:
cooldownPeriod: 600 # 10 минут вместо 5
б) Использовать stabilizationWindow:
advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 600
в) Пересмотреть threshold:
# Было value: "10" # слишком чувствительно # Стало value: "20" # более грубая гранулярность
г) Добавить гистерезис через разные метрики: Использовать разные пороги для scale up и scale down (пока не поддерживается напрямую в KEDA, но можно через composite scaler).
4. KEDA не видит метрику из Prometheus
Проблема:
ScaledObject Status: Conditions: Message: error getting metric: query returned no data
Решения:
а) Проверить query вручную:
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \ curl "http://prometheus.monitoring.svc:9090/api/v1/query?query=sum(rate(http_requests_total[2m]))"
б) Проверить формат результата:
KEDA требует scalar или vector с одним элементом: ✅ Хорошо: { "data": { "result": [{"value": [1234567890, "42.5"]}] } } ❌ Плохо (несколько элементов): { "data": { "result": [ {"metric": {"pod": "api-1"}, "value": [1234567890, "10"]}, {"metric": {"pod": "api-2"}, "value": [1234567890, "15"]} ] } }
в) Добавить aggregation:
# Было query: rate(http_requests_total{deployment="api"}[2m]) # Стало query: sum(rate(http_requests_total{deployment="api"}[2m]))
г) Проверить доступность Prometheus:
kubectl get svc -n monitoring # Убедиться что serverAddress корректный
5. maxReplicaCount срабатывает постоянно
Проблема: Workload регулярно упирается в maxReplicaCount: 10, но нагрузка продолжает расти.
Диагностика:
# Сколько времени workload на максимуме avg_over_time((keda_scaledobject_replicas == keda_scaledobject_max_replicas)[1h:]) # Растет ли очередь rate(rabbitmq_queue_messages[5m]) > 0
Решения:
а) Увеличить maxReplicaCount (если инфраструктура позволяет):
maxReplicaCount: 20
б) Оптимизировать код:
Профилировать узкие места
Уменьшить время обработки одного сообщения
Добавить батчинг
в) Вертикальное масштабирование:
resources: requests: cpu: 500m # было 200m memory: 1Gi # было 512Mi
г) Проверить downstream: Возможно проблема не в воркерах, а в базе данных или внешнем API.
Доказываем экономику: какие метрики собирать до и после
Самая частая ошибка — внедрить автоскейлинг и считать, что экономия появилась сама.
Для FinOps этого недостаточно. Нужно заранее договориться, что именно сравниваем до и после.
Технические метрики
Утилизация ресурсов: - pod-hours (реплик × часов работы) - replica count over time - CPU utilization (среднее, p50, p95) - memory utilization (среднее, p50, p95) Бизнес-нагрузка: - queue length (среднее, max) - oldest message age - request rate (RPS) - throughput (сообщений/сек, заказов/час) Производительность: - latency (p50, p95, p99) - error rate - success rate - cold start time (p95, p99) - time to scale (0→N реплик) События масштабирования: - scale up events count - scale down events count - time at 0 replicas - time at max replicas
Финансовые метрики
Важно: заранее договориться, какой тип cost view используем.
Если одна команда смотрит blended cost, другая unblended, а третья amortized, обсуждение быстро превращается в спор о цифрах вместо анализа решения.
Cost dimensions: - Стоимость namespace (до/после) - Стоимость workload (до/после) - Стоимость node pool (до/после) - Стоимость idle capacity - Стоимость за единицу работы (cost per order, cost per message) Cost types (выбрать один): - Blended cost (смешанная: on-demand + spot + savings plans) - Unblended cost (чистая: без учета скидок) - Amortized cost (амортизированная: распределение reserved instances) - Net cost (с учетом всех скидок и кредитов) Time periods: - 1 неделя до внедрения - 1 месяц до внедрения - 1 неделя после внедрения - 1 месяц после внедрения
Операционные метрики
Stability: - количество инцидентов - количество нарушений SLA - MTTR (mean time to recovery) Toil: - количество ручных вмешательств - количество manual scale операций - время на troubleshooting autoscaling Reaction time: - время реакции на пик нагрузки - стабильность scale up - стабильность scale down - количество flapping events
Пример дашборда для замера эффекта
Before KEDA (week 1-2) - **Pod-hours:** 336/week (5 pods × 24h × 7d) - **Avg CPU utilization:** 8% - **Cost:** $168/week - **Manual interventions:** 3 (weekend scale-down) After KEDA (week 3-4) - **Pod-hours:** 112/week - **Avg CPU utilization:** 52% - **Cost:** $56/week - **Manual interventions:** 0 - **Cold start p95:** 12 seconds - **SLA violations:** 0 Result - **Cost savings:** 67% ($112/week, $5,824/year for this workload) - **Utilization improvement:** 8% → 52% - **Toil reduction:** 3 manual ops → 0 - **SLA:** no impact

С чего начать пилот
KEDA не нужно внедрять сразу во всю архитектуру.
Безопаснее идти по шагам.
Шаг 1. Найти подходящий workload
✅ Хорошие кандидаты для первого пилота:
Фоновые воркеры
Обработка очередей (email, уведомления, отчеты)
Batch-задачи
ETL-процессы
Dev/staging окружения
Не нужны ночью и в выходные
Низкий риск для production
Быстро покажут экономию
Сервисы с рабочими часами
B2B API (9:00–18:00)
Внутренние инструменты
Admin-панели
Внутренние API с понятным профилем нагрузки
Стабильная метрика (RPS, queue length)
Известные пиковые окна
Нет жестких SLA
❌ Плохие кандидаты для первого пилота:
Критичный production с жестким SLA
Stateful workloads (базы данных)
Сервисы с долгим cold start (>30 сек)
Сервисы с множеством внешних зависимостей
Монолит с shared state
Шаг 2. Проверить источник событий
Перед настройкой нужно ответить на вопросы:
Источник метрики: ✓ Есть ли стабильная метрика нагрузки? ✓ Кто владеет этой метрикой? ✓ Можно ли ей доверять? ✓ Что будет, если источник метрики недоступен? ✓ Какой лаг между событием и реакцией? Для очередей (RabbitMQ, Kafka, SQS): ✓ Можем получить длину очереди? ✓ Есть credentials для подключения? ✓ Есть ли мониторинг очереди? Для Prometheus: ✓ Query возвращает один скаляр? ✓ Метрика не проваливается? ✓ Есть алерт на отсутствие метрики? Для cron: ✓ Расписание согласовано с командой? ✓ Timezone корректный?
Если метрика нестабильна, KEDA будет честно масштабироваться по плохому сигналу.
Шаг 3. Задать границы
Для пилота нужно явно указать базовые параметры масштабирования:
minReplicaCount: 0 # или 1 если cold start критичен maxReplicaCount: 10 # выбрать по capacity planning pollingInterval: 30 # как часто проверять cooldownPeriod: 300 # пауза перед scale-to-zero
Как выбрать значения:
minReplicaCount: # 0 → если cold start < 15 сек и SLA позволяет # 1 → если нужна мгновенная реакция # 2+ → если нужна redundancy maxReplicaCount: # capacity = max_expected_load / throughput_per_pod # + 20-50% запас # Пример: 1000 msg/sec, 100 msg/sec на под → 10 подов + 50% = 15 pollingInterval: # 30 сек → для плавной нагрузки # 10-15 сек → для быстрых пиков # 5 сек → для critical workloads (но больше нагрузки на источник метрик) cooldownPeriod: # 300 сек (5 мин) → стандарт # 600 сек (10 мин) → для workloads с частыми колебаниями # 120 сек (2 мин) → для dev-окружений
Шаг 4. Настроить observability
До запуска стоит подготовить дашборд:
Обязательные панели:
replicas over time
scaler metric value (queue length / RPS / etc)
latency (p50, p95, p99)
error rate
CPU / memory utilization
FinOps панели:
pod-hours per day
cost per day (если есть cost allocation)
cost per message/request
KEDA-специфичные:
keda_scaler_errors_total
distance to max replicas
time at 0 replicas
time at max replicas
Алерты:
Critical:
KEDA scaler errors
Max replicas reached + queue growing
Cold start > SLA threshold
Warning:
Distance to max replicas < 20%
Flapping (scale up/down each 5 min)
Queue age > SLA threshold
Без такого дашборда будет сложно понять, KEDA действительно снизил простой или просто перенес проблему в задержки.
Шаг 5. Запустить пилот на 1-2 недели
Шаг 6. Собрать данные и сравнить
Через 2 недели собрать данные:
Metric | Before | After | Change |
|---|---|---|---|
Pod-hours/week | 336 | 112 | -67% |
Avg CPU util | 8% | 52% | +44pp |
Cost/week | $168 | $56 | -$112 |
P95 latency | 120ms | 150ms | +30ms |
Error rate | 0.01% | 0.01% | - |
SLA violations | 0 | 0 | - |
Manual ops | 3 | 0 | -3 |
Шаг 7. Решить: раскатывать дальше или откатить
✅ Раскатывать дальше если:
Экономия подтверждена цифрами
SLA не пострадал
Команда понимает как работает
Observability настроена
Нет блокирующих проблем
⏸️ Доработать если:
Cold start слишком долгий → оптимизировать образ
Flapping → настроить behavior
Упираемся в max replicas → capacity planning
❌ Откатить если:
SLA нарушен
Проблемы с источником метрик
Команда не может troubleshoot
Экономика не подтвердилась
Что проверить перед production
Перед раскаткой на production стоит пройти короткий чек-лист:
Application readiness: ✓ Сервис можно безопасно опускать до 0? ✓ Cold start укладывается в SLA? ✓ Приложение stateless? ✓ Нет in-memory state между запросами? ✓ Нет dependency на конкретный pod IP? Kubernetes configuration: ✓ Readiness probe настроен корректно? ✓ Liveness probe не убивает pod во время cold start? ✓ Resource requests и limits заданы? ✓ PodDisruptionBudget настроен? ✓ Anti-affinity rules учтены? KEDA configuration: ✓ minReplicaCount выбран обоснованно? ✓ maxReplicaCount учитывает лимиты downstream? ✓ pollingInterval адекватен скорости нагрузки? ✓ cooldownPeriod не слишком агрессивный? ✓ Trigger метрика стабильна? Dependencies: ✓ Понятны лимиты базы данных? ✓ Понятны лимиты брокера сообщений? ✓ Понятны rate limits внешних API? ✓ Connection pools настроены с запасом? Observability: ✓ Дашборд для workload готов? ✓ Алерты настроены и протестированы? ✓ Логи агрегируются корректно? ✓ Distributed tracing работает? Ownership: ✓ Известно, кто владеет ScaledObject? ✓ Есть runbook для troubleshooting? ✓ Команда понимает как откатиться? ✓ Есть алерт на рост очереди? ✓ Есть алерт на упор в maxReplicaCount? FinOps: ✓ Cost allocation tags проставлены? ✓ Договорились какой cost view используем? ✓ Baseline метрики собраны? ✓ План замера эффекта через месяц? Rollback plan: ✓ Известен план отката? ✓ Протестирован откат на staging? ✓ Команда знает как быстро вернуть fixed replicas?
Если хотя бы часть ответов неизвестна, это не значит, что KEDA нельзя внедрять. Это значит, что пилот нужно запускать на менее критичном участке.
Когда НЕ нужен KEDA
KEDA — не silver bullet. Есть сценарии, где он избыточен:
❌ Стабильная нагрузка 24/7 Если сервис всегда держит 10 реплик и нагрузка не меняется — HPA по CPU достаточно.
❌ Stateful workloads (базы данных) Scale-to-zero для PostgreSQL — плохая идея. Вертикальное масштабирование (VPA, bigger nodes) часто лучше.
❌ Очень короткие задачи (< 1 секунда) Если задача выполняется за 100ms, overhead на создание пода (5-15 сек) слишком велик. Лучше держать min replicas или использовать serverless (Knative, Lambda).
❌ Критичные сервисы с SLA < 1 секунды Cold start не укладывается в такие SLA. Нужен постоянный warm pool.
Q&A
Q&A
Q: KEDA заменяет HPA?
A: Нет, дополняет. KEDA создает HPA под капотом и передает ему external metrics через Metrics API Server. HPA управляет количеством реплик, KEDA — источником метрик.
Q: Можно ли использовать на stateful workloads (базы данных)?
A: С осторожностью. Scale-to-zero для БД обычно плохая идея (долгий startup, потеря кеша, state). Но:
✅ Масштабировать read-replica по нагрузке — можно
✅ Dev-окружения с БД на ночь опускать — можно
❌ Production master БД в scale-to-zero — нет
Q: Что будет если KEDA упадет?
A: HPA продолжит работать с последней известной метрикой. Но:
Новые ScaledObject не создадутся
Метрики перестанут обновляться
Workload останется на текущем количестве реплик до восстановления KEDA
Критично: мониторить состояние keda-operator и keda-metrics-apiserver pods.
Q: Работает ли в on-premise Kubernetes?
A: Да, KEDA не привязан к облаку. Работает в любом Kubernetes (on-premise, cloud, edge). Главное — чтобы источник метрик был доступен (RabbitMQ, Prometheus, etc).
Q: Сколько стоит KEDA?
A: Open source, бесплатно (Apache 2.0 license). Платите только за саму инфраструктуру (compute, storage, network)
Q: Можно ли комбинировать несколько триггеров?
A: Да, ScaledObject поддерживает несколько triggers. Можно масштабироваться по очереди И по расписанию одновременно:
triggers: - type: rabbitmq metadata: queueName: orders value: "10" - type: prometheus metadata: query: sum(rate(http_requests_total[2m])) threshold: "100"
KEDA возьмет максимум из всех триггеров.
Q: Как откатиться если KEDA сломал production?
A: Быстрый откат:
# 1. Удалить ScaledObject (HPA тоже удалится) kubectl delete scaledobject <name> -n <namespace> # 2. Вернуть фиксированное количество реплик kubectl scale deployment/<name> --replicas=5 -n <namespace> # 3. Или применить старый манифест Deployment kubectl apply -f deployment-before-keda.yaml
Важно: держите backup манифестов до внедрения KEDA.
Что в итоге
KEDA не делает FinOps за команду. Он не заменяет:
Правила аллокации затрат
Ownership и ответственность
Бюджетные алерты
Регулярный анализ cloud bill
Договоренности между командами
Cost allocation tags и chargeback
Но он хорошо закрывает конкретный инженерный слой финансовых гардрейлов в Kubernetes.
KEDA помогает:
✅ Масштабировать по фактической нагрузке Очереди, события, расписание, метрики — а не только CPU/Memory
✅ Не держать поды включенными “на всякий случай” Scale-to-zero убирает холостой простой
✅ Ограничивать верхнюю границу через maxReplicaCount Технический предохранитель от бесконтрольного роста
✅ Управлять поведением scale down
cooldownPeriod+ HPA behavior для плавного масштабирования✅ Проверять экономику через пилот Месяц на dev/staging → цифры → решение о production
✅ Снижать toil Автоматизация вместо bash-скриптов и ручных scale операций
Главная мысль
Инфраструктура не должна круглосуточно ждать редкий пик, если это не требуется бизнесом и SLA.
Она должна:
Включаться под работу → масштабироваться по событиям
Выключаться в простое → scale-to-zero когда нагрузки нет
Не расти бесконечно → maxReplicaCount как предохранитель
В этом смысле KEDA — это не просто автоскейлер. Это технический механизм, который помогает связать Kubernetes-нагрузку с финансовой логикой:
Меньше холостого простоя
Понятнее верхние лимиты
Прозрачнее эффект от оптимизации
Что дальше
Если у вас есть:
✅ Воркеры с очередями, которые простаивают ночью
✅ Dev/staging окружения работающие 24/7
✅ Внутренние сервисы с рабочими часами
✅ Batch-задачи по расписанию
✅ API с пиковой нагрузкой в определенные часы
…попробуйте KEDA на одном workload.
План первых шагов:
Выбрать пилотный workload (dev-воркер с очередью)
Установить KEDA через Helm за 5 минут
Создать ScaledObject с консервативными параметрами
Настроить мониторинг (дашборд + алерты)
Запустить на 2 недели и собрать метрики
Сравнить до/после и принять решение
Месяц пилота даст:
Конкретные цифры экономии (pod-hours, $)
Понимание подводных камней до раската на весь кластер
Опыт команды с event-driven autoscaling
Baseline для дальнейшей оптимизации
Полезные ссылки
Документация:
Буду рад обсудить опыт внедрения KEDA в комментариях или в личных сообщениях.
Материал подготовлен в рамках серии публикаций для комьюнити «Практики FinOps», где мы разбираем прикладные подходы к управлению инфраструктурными затратами, cloud-cost и FinOps-практикам в инженерных командах.
