Представь типичный понедельник. В девять утра на сервис обрушивается трафик, поды захлебываются, latency растет, алерты звенят. Дежурный руками докручивает replicas, тушит пожар. К обеду нагрузка падает, но реплики так и висят на пике, ты жжешь деньги впустую. К вечеру акция, снова всплеск, и снова никто не успел. Это ручное управление мощностью, и оно проигрывает всегда: человек реагирует за минуты, нагрузка меняется за секунды.
Автомасштабирование kubernetes решает ровно это - отдает решение о количестве реплик и узлов машине, которая смотрит на метрики и крутит ручки сама. Но тут важно сразу разложить кашу по полочкам, потому что в кластере живут три принципиально разных уровня масштабирования, и их постоянно путают.
- HPA (Horizontal Pod Autoscaler) - меняет ЧИСЛО подов. Больше нагрузки - больше копий.
- VPA (Vertical Pod Autoscaler) - меняет РАЗМЕР пода, его requests и limits по CPU и памяти.
- Cluster Autoscaler / Karpenter - меняет число УЗЛОВ. Подам некуда влезть - добавь железо.

HPA: горизонтальное масштабирование подов и его механика
Kubernetes hpa - это контроллер в control plane, который раз в 15 секунд (флаг --horizontal-pod-autoscaler-sync-period у kube-controller-manager) просыпается, читает текущие метрики целевого Deployment и считает, сколько реплик нужно. Формула в основе одна и простая:
Код: Выделить всё
desiredReplicas = ceil(currentReplicas * (currentMetric / targetMetric))Откуда HPA берет метрики CPU и памяти? Из metrics-server. Это отдельный компонент, который НЕ ставится по умолчанию в ванильном кластере (в Yandex Managed Kubernetes и большинстве облаков он уже есть из коробки). metrics-server собирает данные с kubelet каждого узла через Summary API и отдает их через metrics.k8s.io. Проверяется так:
Код: Выделить всё
kubectl top pods -n production
NAME CPU(cores) MEMORY(bytes)
api-7d9c8f5b4-2xk9p 342m 512Mi
api-7d9c8f5b4-7nq4l 298m 480MiСовременный манифест использует apiVersion autoscaling/v2. Старый autoscaling/v1 умел только CPU и его пора забыть. Вот реальный пример с несколькими метриками сразу:
Код: Выделить всё
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75Критически важная связь: requests и HPA. averageUtilization это процент от requests, а не от лимита и не от физического ядра. Если в поде
Код: Выделить всё
resources.requests.cpu: 500mПоведение scaleUp и scaleDown: как победить флаппинг
Голая формула склонна к дерганью - метрика прыгнула, HPA добавил подов, метрика просела, он их убрал, и так по кругу. Это флаппинг, и он бьет по стабильности. В autoscaling/v2 для этого есть секция behavior с раздельной настройкой разгона и торможения.
Код: Выделить всё
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60policies - ограничители скорости. scaleUp разрешает либо +100% (удвоение), либо +4 пода за 30 секунд, и selectPolicy: Max берет более агрессивный вариант. scaleDown срезает не больше 50% реплик за минуту. На практике именно policies, а не окна стабилизации, защищают downstream-системы: представь, что HPA одномоментно поднял с 5 до 50 подов, и все 50 ломанулись в одну базу открывать соединения. Policies растягивают это во времени и спасают БД от лавины.
Что происходит реально, смотрим в describe:
Код: Выделить всё
kubectl describe hpa api-hpa -n production
...
Metrics: ( current / target )
resource cpu 72% (360m) / 60%
Events:
Type Reason Message
Normal SuccessfulRescale New size: 8; reason: cpu resource utilization above targetКастомные и внешние метрики: масштабируемся по бизнесу
CPU и память это часто плохой прокси для реальной нагрузки. Веб-сервер может стоять на 30% CPU, но захлебываться от тысячи запросов в секунду. Для таких случаев HPA умеет два других типа метрик.
Pods/Object метрики (type: Pods, type: Object) - кастомные метрики из приложения, например RPS или длина внутренней очереди, отданные через адаптер custom.metrics.k8s.io (обычно prometheus-adapter). External метрики (type: External) - что-то снаружи кластера: глубина очереди в RabbitMQ, лаг консьюмера Kafka, число сообщений в Yandex Message Queue. Пример масштабирования по RPS из Прометея:
Код: Выделить всё
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"VPA: вертикальное масштабирование и подбор requests
HPA добавляет копии, но что если под изначально неправильно нарезан - ему дали 2 ядра, а он ест 200m, или дали 256Mi, а он стабильно ловит OOMKilled? Тут нужен Vertical Pod Autoscaler. Он не из коробки, ставится отдельно (helm-чарт или манифесты из репозитория autoscaler), и состоит из трех частей: Recommender (считает идеальные requests по истории), Updater (решает, какие поды пересоздать) и Admission Controller (подменяет requests при создании пода).
Код: Выделить всё
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: worker-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: worker
updatePolicy:
updateMode: "Off"Код: Выделить всё
kubectl describe vpa worker-vpa
...
Recommendation:
Container Recommendations:
Container Name: worker
Lower Bound: cpu: 250m, memory: 262144k
Target: cpu: 410m, memory: 393216k
Upper Bound: cpu: 800m, memory: 786432kГлавная грабля VPA: его НЕЛЬЗЯ ставить на тот же Deployment с тем же ресурсом, что и HPA. Если HPA масштабирует по CPU и VPA в режиме Auto тоже крутит CPU requests, они начинают воевать - VPA меняет базу, от которой HPA считает проценты, и оба сходят с ума. Безопасная комбинация: HPA по кастомной метрике (RPS) плюс VPA по памяти. Или VPA только в режиме Off как советчик.
Cluster Autoscaler и Karpenter: масштабирование узлов
HPA нарастил реплики, но если в кластере нет места, новые поды зависают в статусе Pending. Кто добавит узлы? Cluster Autoscaler. Он следит за подами, которые не могут быть запланированы (unschedulable), и просит у облака новый узел через группу автомасштабирования (в Yandex Managed Kubernetes это группа узлов с автоскейлингом). Когда узел простаивает и его поды можно переселить, CA его гасит.
Код: Выделить всё
kubectl get pods
NAME READY STATUS AGE
api-7d9c8f5b4-9wm2x 0/1 Pending 42s
kubectl describe pod api-7d9c8f5b4-9wm2x
Events:
Warning FailedScheduling 0/6 nodes available: insufficient cpu
Normal TriggeredScaleUp pod triggered scale-up: group nodes-prod 6->7Karpenter - это эволюция 2026 года. Вместо групп узлов он смотрит на суммарные потребности всех Pending-подов и подбирает оптимальный инстанс под них прямо на лету, провижинит за десятки секунд, а не минуты, и сам консолидирует нагрузку, переселяя поды на более плотные/дешевые узлы. Описывается через NodePool (apiVersion karpenter.sh/v1):
Код: Выделить всё
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30sKEDA: event-driven autoscaling и масштабирование до нуля
HPA по своей природе не умеет опуститься до нуля реплик - minReplicas минимум 1, иначе ему нечего мерить. А для воркера, который раз в час разгребает очередь, держать постоянно один под это деньги на ветер. Тут выходит KEDA (Kubernetes Event-Driven Autoscaling).
KEDA - это надстройка над HPA. Она НЕ заменяет HPA, а кормит его: KEDA создает под капотом обычный HPA и подсовывает ему external-метрики из 70+ источников - длина очереди RabbitMQ, лаг Kafka, число сообщений в SQS или Yandex Message Queue, ответ HTTP-эндпоинта, даже cron-расписание. И главное - KEDA умеет масштаб до нуля и обратно: нет событий - 0 подов, пришло сообщение - поднимает первый под.
Код: Выделить всё
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: consumer-scaler
spec:
scaleTargetRef:
name: consumer
minReplicaCount: 0
maxReplicaCount: 50
cooldownPeriod: 120
triggers:
- type: kafka
metadata:
bootstrapServers: kafka:9092
consumerGroup: orders
topic: orders
lagThreshold: "100"Типичные грабли и антипаттерны
- Нет requests в подах. HPA по CPU/памяти молча не работает, метрика "unknown". Cluster Autoscaler тоже считает вместимость узла по requests - без них он не понимает, сколько подов влезает. requests это фундамент всего автоскейлинга.
- Нет metrics-server. kubectl top падает, HPA слеп. Проверяй первым делом.
- Флаппинг. Реплики дергаются туда-сюда. Лечится behavior: окно стабилизации на scaleDown и policies на обоих направлениях.
- HPA и VPA на одном ресурсе. Воюют за CPU requests. Разводи их по разным метрикам или держи VPA в режиме Off.
- Уперлись в лимиты. maxReplicas слишком мал, или у группы узлов/квоты облака потолок, или нет свободных IP в подсети. HPA хочет 30 подов, а кластер дает 12, и ты этого не видишь, пока не посмотришь Events. Потолки есть везде - в HPA, в node group, в облачной квоте.
- Медленный старт пода. Если приложение прогревается 2 минуты, мгновенный scaleUp не спасет от всплеска - реплики еще не готовы. Думай про readinessProbe и прогрев заранее.
- Масштаб по среднему CPU при неравномерной нагрузке. Один под на 100%, три на 10%, среднее 32% - HPA спокоен, а первому поду плохо. Иногда нужны кастомные метрики, а не CPU.
Повтори на тестовом кластере (подойдет k3s или minikube).
- Убедись, что есть metrics-server: . Если нет в k3s/minikube - доставь.
Код: Выделить всё
kubectl top nodes - Создай нагрузочный Deployment с обязательными requests: , затем добавь в манифест resources.requests.cpu: 200m и expose как Service.
Код: Выделить всё
kubectl create deployment php-apache --image=registry.k8s.io/hpa-example - Навесь HPA: .
Код: Выделить всё
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10 - Запусти генератор нагрузки в отдельном поде: .
Код: Выделить всё
kubectl run -it load --image=busybox -- /bin/sh -c "while true; do wget -q -O- http://php-apache; done" - Во втором терминале наблюдай: . Смотри, как TARGETS растет выше 50% и REPLICAS ползет вверх.
Код: Выделить всё
kubectl get hpa php-apache --watch - Убей генератор нагрузки и засеки по часам, через сколько HPA опустит реплики - почувствуй окно стабилизации scaleDown своими руками.
- Бонус: повесь на тот же Deployment VPA в режиме Off и через describe сравни его Target-рекомендацию с тем, что ты задал в requests.
- Почему HPA по CPU не заработает, если в подах не указан resources.requests.cpu, и от чего вообще считается averageUtilization?
- Чем отличаются scaleUp и scaleDown в секции behavior и зачем для них задают разные stabilizationWindowSeconds?
- В чем принципиальная разница между HPA, VPA и Cluster Autoscaler, и какие два из них нельзя бездумно вешать на один ресурс?
- Что умеет KEDA такого, чего не умеет голый HPA, и как KEDA связана с Cluster Autoscaler в продакшене?
Автомасштабирование это три независимых уровня: HPA крутит число подов по метрикам, VPA подбирает их размер и requests, Cluster Autoscaler или Karpenter добавляют узлы под выросшие поды. KEDA расширяет HPA до событийных метрик и масштаба до нуля. Все держится на корректных requests и живом metrics-server, а от флаппинга спасает грамотная асимметрия scaleUp/scaleDown. Настрой их вместе, и понедельничный пожар погасит машина, пока ты пьешь кофе.