Спроси любого системного администратора или DevOps-инженера, как у него настроен горизонтальный автоскейлинг подов (Horizontal Pod Autoscaler, HPA) в Kubernetes, и в девяти случаях из десяти ты услышишь один и тот же дежурный ответ: «Ну, по CPU и оперативной памяти, классический порог 70–80%».
На бумаге это выглядит вполне логично: пошел наплыв пользователей, процессоры задымились, Kubernetes заметил скачок утилизации и добавил еще пять реплик сервиса. Но в реальном высоконагруженном продакшене эта примитивная схема регулярно приводит к каскадным авариям. Когда процессор контейнера уже загружен на 90%, спасать сервис, как правило, поздно: очередь входящих запросов переполнена, тайм-ауты сыплются лавиной, а новые поды инициализируются, скачивают образ и проходят readiness-пробы еще две-три минуты. В результате нагрузка перераспределяется на оставшиеся живые поды и кластер складывается по принципу домино.
Инженеры исследовательской лаборатории Applied Computing Research Labs (ACRL) опубликовали детальный анализ узких мест масштабирования, разобрав пять неочевидных метрик, позволяющих настроить опережающий (predictive) автоскейлинг задолго до того, как процессоры уйдут в полку.
Почему процессор и память обманывают автоскейлер
Давай разберем физику процесса. Метрики утилизации CPU и RAM являются запаздывающими индикаторами (lagging indicators). Они показывают факт уже случившейся катастрофы, а не ее приближение.
Более того, в современных асинхронных микросервисах на Node.js, Go или Java процессор может быть занят всего на 30%, но при этом сервис уже будет мертв с точки зрения клиентов. Например, если пул соединений к базе данных PostgreSQL исчерпан или удаленный платежный шлюз стал отвечать с задержкой в 5 секунд, входящие потоки приложения блокируются в ожидании ввода-вывода (I/O wait). Процессор при этом простаивает! Kubernetes смотрит на загрузку CPU в 25% и считает, что всё отлично, в то время как пользователи получают ошибки HTTP 504 Gateway Timeout.
Исследователи ACRL сформулировали пять фундаментальных метрик, ориентированных на пропускную способность и глубину очередей.
Пять ключевых метрик ACRL: опережающие сигналы нагрузки
- Скорость роста очереди сообщений (Queue Ingestion Rate vs Processing Rate): В асинхронных воркерах (RabbitMQ, Kafka, SQS) ориентироваться на CPU бессмысленно. Важна не абсолютная длина очереди, а производная: скорость поступления новых сообщений за вычетом скорости их обработки. Если дельта становится положительной, масштабироваться нужно немедленно.
- Время задержки в буфере ожидания (Queue Dwell Time): Показывает, сколько миллисекунд сообщение или HTTP-запрос проводит в очереди до того, как свободный поток начнет его фактическое исполнение. Превышение порога в 200–300 мс — прямой повод для добавления подов.
- Задержка рукопожатия TCP и RTT (Connection Handshake Latency): Замеряется на уровне балансировщика Ingress. Скачок времени установления TCP-соединения свидетельствует о переполнении сокетного бэклога операционной системы в поде (
somaxconn). - Коэффициент удержания соединений пула (Pool Saturation Ratio): Отношение активных потоков выполнения к общему размеру пула воркеров. Когда пул заполнен на 85%, у сервиса не остается запаса прочности на внезапные всплески трафика.
- Процентиль задержки p99 на границе Ingress: Время ответа самых медленных запросов сигнализирует о деградации задолго до того, как вырастет средняя задержка p50.
Если ты проектируешь стресс-тесты для проверки этих метрик, обрати внимание на наше руководство: реалистичные нагрузочные тесты в Kubernetes: проектирование тестового набора на k6 для микросервисной архитектуры, где показано, как выявлять эти пороги инструментально.
Практическая конфигурация KEDA и манифеста HPA
Для реализации скейлинга по опережающим метрикам стандартных ресурсов Kubernetes недостаточно: требуется оператор KEDA (Kubernetes Event-driven Autoscaling) либо кастомный адаптер метрик Prometheus (Prometheus Adapter).
Ниже приведен готовый пример манифеста ScaledObject для KEDA, который отслеживает скорость роста очереди в Kafka и буферное время:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-processor-autoscaler
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-processor
minReplicaCount: 3
maxReplicaCount: 30
cooldownPeriod: 300
pollingInterval: 15
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-cluster:9092
topic: orders.v1
consumerGroup: order-workers
lagThreshold: "50"
activationLagThreshold: "10"
А вот так выглядит конфигурация стандартного HPA при подключении внешних метрик задержки Ingress из Prometheus с защитой от флаппинга (дребезга реплик):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-gateway-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-gateway
minReplicas: 4
maxReplicas: 40
metrics:
- type: External
external:
metric:
name: ingress_request_duration_p99_seconds
selector:
matchLabels:
service: api-gateway
target:
type: Value
averageValue: 250m
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 4
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
Обрати внимание на секцию behavior: при масштабировании вверх окно стабилизации сброшено в ноль для мгновенной реакции на пик, а при масштабировании вниз установлено пятиминутное окно (stabilizationWindowSeconds: 300). Это защищает кластер от опасного циклического создания и уничтожения подов при пилообразной нагрузке.
Переход на предиктивные метрики требует времени на калибровку Prometheus, но окупается в первую же крупную распродажу или всплеск пользовательской активности.
