lenec ru

← все посты

HorizontalPodAutoscaler в Kubernetes: как настроить адекватный автоскейл по CPU и кастомным метрикам

15K

HPA в k8s настраивается за пять строк YAML, и в этом проблема. Приклеил к Deployment targetCPUUtilizationPercentage: 70, поставил minReplicas: 2, maxReplicas: 10 — и спишь спокойно. Через месяц приходит инцидент: на пике трафика поды плодятся, БД захлебывается, latency растёт. Автоскейл вместо помощи становится частью проблемы.

Покажу, как настроить HPA так, чтобы он реально работал. Что важно учесть, что не учитывает дефолт, и где использовать кастомные метрики вместо CPU. Версии — k8s 1.30, autoscaling/v2 API, prometheus-adapter 0.12.

Почему дефолтный HPA не справляется

Стандартный HPA смотрит на среднее CPU utilization по подам. Если выше target — добавляет реплики. Это работает для CPU-bound сервисов с равномерной нагрузкой, но плохо подходит для:

  • I/O-bound API-сервисов, где CPU 30%, а очередь запросов растёт.
  • Воркеров с очередями (Kafka, RabbitMQ) — нужно скейлиться по длине очереди, а не по CPU.
  • Web-приложений с переменной нагрузкой и медленным стартом — пока scale up отрабатывает, пик уже прошёл.
  • Memory-bound нагрузок — CPU низкий, память жрут, OOMKilled.

HPA умеет всё это, если правильно настроить.

Базовая структура autoscaling/v2

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api
  namespace: prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 100
          periodSeconds: 30
        - type: Pods
          value: 4
          periodSeconds: 30
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60

Ключевая часть — behavior. Дефолтные значения в k8s — консервативные для scale up (15 секунд stabilization, медленно добавляет) и слишком агрессивные для scale down (поэтому pods «прыгают»).

scaleUp policies

В моём примере: за 30 секунд можно либо удвоить количество реплик (Percent 100%), либо добавить 4 пода (Pods 4). selectPolicy: Max — берёт более агрессивный вариант. То есть если у тебя 2 пода — за 30 секунд может стать 6. Если 10 — может стать 14.

Это нужно, потому что трафик на e-commerce может удваиваться за минуту (рассылка, рекламная кампания, флешсейл). Дефолт скейлится медленнее, чем растёт нагрузка.

scaleDown policies

Стабилизация 300 секунд (5 минут) — это окно, в течение которого HPA смотрит на максимум метрики, прежде чем уменьшить. Без него 60-секундный спад нагрузки → лишние поды убиваются → новый пик → нужно ждать scale up. Поды дёргаются туда-сюда, latency качается.

Я ставлю минимум 5 минут на scale down, иногда 10 для тяжёлых сервисов с долгим прогревом кешей.

Custom metrics: скейлим по очереди

Допустим, у тебя воркер, который обрабатывает Kafka. Чтобы скейлить по lag-у консьюмер-группы, нужны custom metrics через prometheus-adapter.

Установка prometheus-adapter

helm install prometheus-adapter \
  prometheus-community/prometheus-adapter \
  -n monitoring \
  --set prometheus.url=http://kube-prometheus-stack-prometheus.monitoring.svc \
  --set rules.default=false

Дальше нужно описать, какие метрики из Prometheus превращать в k8s metrics. Конфиг в values.yaml:

rules:
  custom:
    - seriesQuery: 'kafka_consumergroup_lag{namespace!=""}'
      resources:
        overrides:
          namespace:
            resource: namespace
      name:
        as: "kafka_consumergroup_lag"
      metricsQuery: 'sum(kafka_consumergroup_lag{<<.LabelMatchers>>}) by (namespace, consumergroup)'

После apply prometheus-adapter регистрирует endpoint /apis/custom.metrics.k8s.io/v1beta1, и теперь HPA может по этой метрике скейлиться.

HPA по custom-метрике

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: kafka-worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: kafka-worker
  minReplicas: 2
  maxReplicas: 30
  metrics:
    - type: Object
      object:
        describedObject:
          apiVersion: v1
          kind: Service
          name: kafka-worker
        metric:
          name: kafka_consumergroup_lag
        target:
          type: Value
          value: "1000"

Целевое значение — 1000 сообщений в очереди на под. Если lag растёт — добавляется поды. Когда lag разгрёбся — снижается обратно.

В реальности я обычно комбинирую: и CPU, и custom-метрика. HPA берёт максимум из вычисленных желаемых replicas.

Внешние метрики через KEDA

Если custom metrics — это «метрика, привязанная к k8s-ресурсу», то external metrics — «что-то снаружи». Например, длина очереди в SQS, количество сообщений в RabbitMQ, размер бэклога в Pub/Sub.

Можно собрать через prometheus-adapter, но проще — KEDA. Это отдельный оператор, который умеет десятки источников из коробки.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: rabbitmq-worker
spec:
  scaleTargetRef:
    name: rabbitmq-worker
  minReplicaCount: 1
  maxReplicaCount: 50
  triggers:
    - type: rabbitmq
      metadata:
        protocol: amqp
        queueName: orders
        queueLength: "10"
        host: amqp-secret

10 — это сколько сообщений на под мы готовы держать. KEDA сам считает желаемое replicas и обновляет HPA-объект внутри. Снаружи всё равно живёт обычный HPA.

KEDA умеет даже scale-to-zero — если очередь пустая, поды могут полностью схлопнуться. Это для batch-нагрузок и стартапов, которые экономят на staging.

Где обычно ловят грабли

1. requests не выставлены

HPA по CPU считает actual / requests. Если у пода нет resources.requests.cpu, HPA не работает. В Events будет FailedGetResourceMetric. Без requests — никакого автоскейла.

2. Слишком жёсткий target

Видел: averageUtilization: 50. Это значит, что половина CPU всегда «в резерве». На больших фермах это лишние десятки процентов оверплаты. 70-80% — нормальный baseline для веб-сервисов, 90% для воркеров.

3. minReplicas: 1 для критичных сервисов

Если minReplicas=1 и под умер — кратковременный downtime. Для критичного API минимум 2-3 пода и PodDisruptionBudget, чтобы при maintenance node не уехало всё сразу.

4. Нет coolDown между scale up

Дефолт у scale up небольшой — это плюс, но если сервис стартует медленно (Java 60 секунд), новые поды ещё не готовы, а HPA уже смотрит на ту же метрику и решает добавить ещё. Лечится stabilizationWindowSeconds побольше или startupProbe такой, чтобы новый под не считался ready, пока не прогрелся.

5. HPA + VPA на одном объекте

VPA меняет requests, HPA смотрит на utilization относительно requests. Если оба активно работают — interfering. VPA в режиме Auto + HPA по CPU = bad. Лечится: VPA в Off для рекомендаций, или VPA только для не-CPU ресурсов (memory).

Мониторинг HPA

Я смотрю в Prometheus:

  • kube_horizontalpodautoscaler_status_current_replicas — текущее количество реплик.
  • kube_horizontalpodautoscaler_status_desired_replicas — целевое.
  • kube_horizontalpodautoscaler_spec_max_replicas — потолок.

Алёрт: если current_replicas == max_replicas дольше 10 минут — ты упёрся в потолок, нужно либо повышать max, либо разбираться, почему метрика не падает с ростом подов.

- alert: HPAMaxedOut
  expr: |
    kube_horizontalpodautoscaler_status_current_replicas
    == kube_horizontalpodautoscaler_spec_max_replicas
  for: 10m
  annotations:
    summary: "HPA уперся в maxReplicas"

Что запомнить

HPA настраивается не одной строкой, а через behavior. Scale up быстрый и агрессивный, scale down медленный и стабильный. CPU — для CPU-bound. Очереди и латентность — через custom metrics или KEDA. requests должны быть выставлены на всех контейнерах. minReplicas минимум 2 для критичного. Не миксуй HPA и VPA на одном ресурсе.

Куда копать: официальная документация HPA, KEDA для внешних триггеров, и обязательно посмотри prometheus-adapter — без него custom metrics не работают.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.