Auto-scaling Strategies — concept page on Kubernetes autoscaling: HPA reactive on custom RPS metric, KEDA event-driven scaling on Kafka lag with scale-to-zero, Cluster Autoscaler vs Karpenter for node provisioning, predictive vs reactive scaling, asymmetric cool-down windows, scale flapping anti-pattern, and request limits/PDB importance. Two ADRs: stack choice (HPA+KEDA+Karpenter with asymmetric cool-down), and predictive vs reactive (hybrid cron preempt + reactive HPA fallback).
Динамическое управление capacity через HPA, VPA, KEDA, Cluster Autoscaler и Karpenter — как комбинировать, чтобы экономить compute и не сломать SLA.
Статичная capacity — это всегда выбор из двух плохих опций. Либо ты держишь компьют на пиковую нагрузку 24/7 (BlackFriday × 365 дней) и переплачиваешь 60-80%, либо настраиваешь fleet на среднее и в час пик у тебя p99 latency 5 секунд и пользователи видят 503.
Auto-scaling — это control loop, который динамически регулирует число pods, размер ресурсов и количество nodes под текущую нагрузку. Сделанный правильно — экономит 30-70% на compute и держит p99 в SLO. Сделанный неправильно — ломает прод сильнее, чем static capacity:
Этот гайд про четыре главных подхода (HPA, VPA, KEDA, cluster autoscaler), как их безопасно комбинировать, что такое asymmetric cool-down, чем predictive отличается от reactive, и какие мины ты обходишь стороной.
Auto-scaling — это control loop с четырьмя уровнями, работающими независимо но согласованно:
Cluster Autoscaler / Karpenter → добавляет физические nodes (EC2)
↑
HPA (Horizontal Pod Autoscaler) → добавляет pods того же Deployment
↑
VPA (Vertical Pod Autoscaler) → меняет CPU/RAM requests одного pod
↑
App-level (connection pool) → внутри процесса (DB pool, worker threads)
Каждый уровень — это метрика → решение → действие → ждать stabilization → метрика…. Все классические ошибки сводятся к трём вопросам:
«Up — fast, down — slow.» Терять capacity дешевле, чем терять requests.
Топология показывает живой K8s cluster с автомасштабированием на всех уровнях:
deployment-api — 3 живых pod (api-1..3) + 1 pending (api-4). Это API под HPA на custom metric (RPS).deployment-worker — async worker под KEDA scale-to-zero (на текущем тике idle).Ключевые рёбра:
api-* → prometheus — каждый pod публикует метрики (RPS, latency).prometheus → hpa — HPA pulls custom metrics adapter каждые 15с.hpa → deployment-api — HPA меняет spec.replicas.kafka → keda (pulse edge) — KEDA watches consumer lag.keda → deployment-worker — KEDA создаёт ScaledObject, скейлит от 0 до N.ca / karpenter → cloud — провижинят/выводят nodes на основе pending pods.ADR на ноде hpa фиксирует выбор stack (HPA + KEDA + Karpenter с asymmetric cool-down), ADR на karpenter — гибридную predictive+reactive стратегию.
Анимация прогоняет четыре отдельных flow — переключаются пилюлями в плеере.
1. hpa-traffic-spike — HPA реактивный с node provisioning. Baseline: 500 RPS на 3 pod, CPU ~40%. Стартует маркетинговая кампания → 2000 RPS. CPU взлетает до 95-98%, p99 latency 1.2с. Prometheus видит avg RPS per pod = 666 (target 200). HPA решает desired = ceil(3 × 666/200) = 10. Применяет немедленно (stabilization window=0s на scale-up). K8s scheduler пробует разместить 7 новых pods — только 2 помещаются на существующих nodes, 5 уходят в Pending. Cluster Autoscaler детектит unschedulable pods, заказывает 2 новых m5.xlarge. Node бутится ~90с, image pull ~30с, warmup ~30с — итого ~2.5 минуты от detection до полного ready. Урок: reactive scaling всегда отстаёт; для known peaks нужен cron preempt.
2. keda-queue-depth — event-driven scale-to-zero. Kafka topic пуст → KEDA убивает все worker pods, экономим compute. Producer кидает 50K messages. KEDA видит lag=50000, threshold=100/replica, считает desired=500, capped maxReplicaCount=100. Schedule 100 worker pods. Первый pod cold-start ~30с (Kafka rebalance + warm consumer). Workers drainят partitions параллельно, lag падает до 0 за ~5 минут. KEDA выдерживает 5-минутный hysteresis (cool-down), потом убивает workers обратно до нуля. Экономика: pay-per-message вместо постоянного idle fleet.
3. cluster-autoscaler-karpenter — два стиля node provisioning. HPA хочет 20 новых pods, каждый требует 2 vCPU + 4GB. Сценарий A (классический Cluster Autoscaler с m5.large ASG): нужно 20 нод, $1920/мес, kubelet overhead 0.2 vCPU × 20 = 4 vCPU выкинуто. Сценарий B (Karpenter): анализирует pending pods batch, выбирает оптимальный SKU, создаёт 1× m5.4xlarge + 1× m5.2xlarge = $1280/мес. Экономия 33%, меньше overhead, быстрее scheduling. Через 5 минут traffic падает — Karpenter consolidation: bin-packs pods на меньший набор нод, terminates лишние с уважением к PDB. Gotcha: агрессивная consolidation может churnить pods — критичные workload помечают karpenter.sh/do-not-disrupt.
4. flapping-no-cooldown — классический анти-паттерн. Симметричный cool-down (up=down=0s). CPU 75% → scale-up 3→4 → новый pod разогревается → CPU падает до 50% → scale-down 4→3 → CPU снова 75% → scale-up. Цикл бесконечный. Replicas меняются 30 раз за час, PagerDuty будит SRE. Фикс: scaleDown.stabilizationWindowSeconds=300, scaleUp.stabilizationWindowSeconds=0. Up быстро (безопасно), down медленно (не теряем capacity для следующего burst).
ADR-001 (на ноде HPA): Stack HPA + KEDA + Karpenter с asymmetric cool-down.
Контекст. Большинство наших сервисов — I/O bound (ждут БД, внешних API, очередей). CPU autoscaling даёт ложные negatives: 30% CPU при 100% IO contention выглядит как «всё ок», а на самом деле request queue растёт. Async workers — pure event-driven: idle ночью бессмысленно держать. Node provisioning через стандартный Cluster Autoscaler с фиксированными ASG group переплачивает 30-40% на kubelet overhead и неоптимальный bin-packing.
Решение. HPA на custom metric (RPS / p99 latency / queue depth через Prometheus Adapter), НЕ на CPU. KEDA для async workers с Kafka lag scaler и scale-to-zero. Karpenter вместо классического CA — provisions right-sized instances per workload, агрессивный consolidation. Asymmetric cool-down: scale-up window=0s + Percent 100 / 30s; scale-down window=300s + Percent 10 / 60s. Request limits на p95 observed usage (низкий request обманывает HPA — считает %, не absolute). PDB обязателен (maxUnavailable: 25%) чтобы scale-down + node drain не убили все replicas разом.
Последствия. Стабильное поведение под burst, экономия compute. Сложность: больше движущихся частей (HPA + KEDA + Karpenter + Prometheus Adapter), нужно понимать как они взаимодействуют. Custom metrics требуют ухода — broken Prometheus = broken HPA.
ADR-002 (на ноде Karpenter): Hybrid predictive + reactive (cron preempt + HPA fallback).
Контекст. Pure reactive отстаёт на ~2.5 минуты от detection до полного ready node. Для известных пиков (бизнес-часы, Friday 18
, BlackFriday) это болезненно — за 2 минуты сервис уже задеградировал. Pure predictive (AWS Predictive Scaling, ML) требует 14+ дней истории, плохо адаптируется к новым паттернам, opaque в reasoning при инциденте.Решение. KEDA cron scaler устанавливает minReplicaCount по расписанию: weekday 08
Последствия. Нет cold-start на известных пиках. Защита от unforeseen событий через HPA. Минус: cron конфиги дрейфуют от реальных паттернов — раз в квартал review traffic histograms и обновляем расписание.
autoscaling/v2 поддерживает custom + external metrics через adapters.Off mode только для recommendations.maxReplicas: 1000 just in case. Bug в trigger → HPA пытается создать 1000 pods → cluster autoscaler заказывает 200 nodes → bill через крышу. Ставь реалистичный потолок + alert на approach к maxReplicas.Auto + HPA на одну метрику. VPA меняет requests → HPA пересчитывает % utilization → решение меняется → restart pod → пересчёт → chaos. Либо разделяй (HPA на CPU, VPA на memory), либо VPA в Off mode только для рекомендаций.minReplicaCount: 2) или используй Knative с aggressive pre-warming.maxUnavailable: 25% или minAvailable: 2.preStop hook + terminationGracePeriodSeconds: 60 + lameduck mode в LB.Auto-scaling — не серебряная пуля. Бывают случаи, когда статичная capacity лучше:
::concept{slug="load-balancing"} (распределение нагрузки), ::concept{slug="sli-slo-sla"} (метрики, на которые скейлим), ::concept{slug="finops-cost-optimization"} (cost-aware sizing).