Ниже рассматривается только HPA (Horizontal Pod Autoscaling) с apiVersion: autoscaling/v2, чья поддержка появилась начиная с Kubernetes v1.12.

Для настройки HPA требуется:

  • определить, что именно масштабируется (.spec.scaleTargetRef);
  • определить диапазон масштабирования (.spec.minReplicas, .scale.maxReplicas);
  • зарегистрировать в API Kubernetes и определить метрики, на основе которых производится масштабирование (.spec.metrics).

Метрики с точки зрения HPA бывают трех видов:

  • классические — с типом (.spec.metrics[].type) «Resource», используются для простейшего масштабирования по потреблению процессора и памяти;
  • кастомные — с типами (.spec.metrics[].type) «Pods» или «Object»;
  • внешние — с типом (.spec.metrics[].type) «External».

Важно! По умолчанию HPA использует разные подходы при масштабировании:

  • Если метрики указывают на требование масштабировать вверх, это происходит незамедлительно (spec.behavior.scaleUp.stabilizationWindowSeconds = 0). Единственное ограничение — скорость прироста: за 15 секунд поды могут удвоиться, но если подов меньше 4, добавятся 4 новых пода.
  • Если метрики указывают на то, что требуется масштабировать вниз, это происходит в течение 5 минут (spec.behavior.scaleUp.stabilizationWindowSeconds = 300): собираются предложения о новом количестве реплик, в результате чего выбирается самое большое значение. Нет ограничений на количество удаляемых подов за один раз.

Если имеются проблемы с колебаниями метрик и происходит резкое увеличение ненужных реплик приложения, применяются следующие подходы:

  • Оборачивание метрики агрегирующей функцией (например, avg_over_time()), если метрика определена PromQL-запросом. Подробнее см. пример.
  • Увеличение времени стабилизации (параметр spec.behavior.scaleUp.stabilizationWindowSeconds) в ресурсе HorizontalPodAutoscaler. В течение обозначенного периода будут собираться предложения об увеличении количества реплик, в результате чего будет выбрано самое скромное предложение. Это решение тождественно применению агрегирующей функции min_over_time(<stabilizationWindowSeconds>), но только в том случае, если метрика растет и требуется масштабирование вверх. Для масштабирования вниз, как правило, достаточно стандартных настроек. Подробнее см. пример.
  • Ограничение скорости прироста новых реплик с помощью политик spec.behavior.scaleUp.policies.

Типы масштабирования

Используйте следующие метрики для масштабирования приложений:

  1. Классического типа.
  2. Кастомные namespace-scoped-метрики. При условии, если у вас одно приложение, источник метрик находится внутри namespace и связан с одним из объектов.
  3. Кастомные cluster-wide-метрики. При условии, если у вас много приложений используют одинаковую метрику, источник которой находится в namespace приложения, и она связана с одним из объектов. Подобные метрики предусмотрены на случай необходимости выделения общих инфраструктурных компонентов в отдельный деплой («infra»).
  4. Если источник метрики не привязан к namespace приложения, используйте внешние метрики. Например, метрики облачного провайдера или внешнего SaaS-сервиса.

Важно! Рекомендуется использовать вариант 1 (классические метрики), или вариант 2 (кастомные метрики, определяемые в Namespace). В этом случае, рекомендуется определить конфигурацию приложения, включающую его автоматическое масштабирование, в репозиторий самого приложения. Следует рассматривать варианты 3 и 4 только в том случае, если у вас имеется большая коллекция идентичных микросервисов.

Классическое масштабирование по потреблению ресурсов

Пример HPA для масштабирования по базовым метрикам из metrics.k8s.io: CPU и памяти подов. Особое внимание на averageUtulization — это значение отражает целевой процент ресурсов, который был реквестирован.

1apiVersion: autoscaling/v2
2kind: HorizontalPodAutoscaler
3metadata:
4  name: app-hpa
5  namespace: app-prod
6spec:
7  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
8  scaleTargetRef:
9    apiVersion: apps/v1
10    kind: Deployment
11    name: app
12  # Границы масштабирования контроллера.
13  minReplicas: 1
14  maxReplicas: 10
15  # Если для приложения характерны кратковременные скачки потребления CPU,
16  # можно отложить принятие решения о масштабировании, чтобы убедиться, что оно необходимо.
17  # По умолчанию масштабирование вверх происходит немедленно.
18  behavior:
19    scaleUp:
20      stabilizationWindowSeconds: 300
21  metrics:
22  # Масштабирование по CPU и памяти.
23  - type: Resource
24    resource:
25      name: cpu
26      target:
27        # Масштабирование, когда среднее использование CPU всех подов в scaleTargetRef превышает заданное значение.
28        # Для метрики с type: Resource доступен только type: Utilization.
29        type: Utilization
30        # Масштабирование, если для всех подов из Deployment запрошено по 1 ядру и в среднем уже используется более 700m.
31        averageUtilization: 70
32  - type: Resource
33    resource:
34      name: memory
35      target:
36        # Пример масштабирования, когда среднее использование памяти всех подов в scaleTargetRef превышает заданное значение.
37        type: Utilization
38        # Масштабирование, если для подов запрошено по 1 ГБ памяти и в среднем использовано уже более 800 МБ.
39        averageUtilization: 80

Масштабирование по кастомным метрикам

Регистрация кастомных метрик в Kubernetes API

Кастомные метрики необходимо регистрировать в API /apis/custom.metrics.k8s.io/, эту регистрацию производит prometheus-metrics-adapter (и он же реализует API). На эти метрики можно будет ссылаться из объекта HorizontalPodAutoscaler. Настройка ванильного prometheus-metrics-adapter — трудоемкий процесс, мы его упростили, определив набор Custom Resources с разным Scope:

  • Namespaced:
    • ServiceMetric;
    • IngressMetric;
    • PodMetric;
    • DeploymentMetric;
    • StatefulsetMetric;
    • NamespaceMetric;
    • DaemonSetMetric (недоступен пользователям).
  • Cluster:
    • ClusterServiceMetric (недоступен пользователям);
    • ClusterIngressMetric (недоступен пользователям);
    • ClusterPodMetric (недоступен пользователям);
    • ClusterDeploymentMetric (недоступен пользователям);
    • ClusterStatefulsetMetric (недоступен пользователям);
    • ClusterDaemonSetMetric (недоступен пользователям).

С помощью cluster-wide-ресурса можно задать глобальное определение метрики, а с помощью Namespace можно переопределить её локально. Формат для всех custom resource — одинаковый.

Применяем кастомные метрики в HPA

После регистрации кастомной метрики на нее можно ссылаться. С точки зрения HPA, кастомные метрики бывают двух видов — Pods и Object.

Object — отсылает к объекту в кластере, который имеет в Prometheus метрики с соответствующими лейблами (namespace=XXX,ingress=YYY). Эти лейблы будут подставляться вместо <<.LabelMatchers>> в вашем кастомном запросе.

1apiVersion: deckhouse.io/v1beta1
2kind: IngressMetric
3metadata:
4  name: mymetric
5  namespace: mynamespace
6spec:
7  query: sum(rate(ingress_nginx_detail_requests_total{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>) OR on() vector(0)
8---
9kind: HorizontalPodAutoscaler
10apiVersion: autoscaling/v2
11metadata:
12  name: myhpa
13  namespace: mynamespace
14spec:
15  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
16  scaleTargetRef:
17    apiVersion: apps/v1
18    kind: Deployment
19    name: myapp
20  minReplicas: 1
21  maxReplicas: 2
22  # Метрики, используемые для масштабирования.
23  # Пример использования кастомных метрик.
24  metrics:
25  - type: Object
26    object:
27      # Объект, который обладает метриками в Prometheus.
28      describedObject:
29        apiVersion: networking.k8s.io/v1
30        kind: Ingress
31        name: myingress
32      metric:
33        # Метрика, зарегистрированная с помощью custom resource IngressMetric или ClusterIngressMetric.
34        # Можно использовать rps_1m, rps_5m или rps_15m которые поставляются с модулем prometheus-metrics-adapter.
35        name: mymetric
36      target:
37        # Для метрик типа Object можно использовать `Value` или `AverageValue`.
38        type: AverageValue
39        # Масштабирование происходит, если среднее значение кастомной метрики для всех подов в Deployment сильно отличается от 10.
40        averageValue: 10

Pods — из ресурса, которым управляет HPA, будут выбраны все поды и для каждого пода будут собраны метрики с соответствующими лейблами (namespace=XXX, pod=YYY-sadiq, namespace=XXX, pod=YYY-e3adf, и т. д.). Из этих показателей HPA рассчитает среднее значение и использует для масштабирования.

Пример использования кастомных метрик с размером очереди RabbitMQ

В представленном примере рассматривается очередь send_forum_message в RabbitMQ, для которого зарегистрирован сервис rmq. Если количество сообщений в этой очереди превышает 42, выполняется масштабирование.

1apiVersion: deckhouse.io/v1beta1
2kind: ServiceMetric
3metadata:
4  name: rmq-queue-forum-messages
5  namespace: mynamespace
6spec:
7  query: sum (rabbitmq_queue_messages{<<.LabelMatchers>>,queue=~"send_forum_message",vhost="/"}) by (<<.GroupBy>>)
8---
9kind: HorizontalPodAutoscaler
10apiVersion: autoscaling/v2
11metadata:
12  name: myhpa
13  namespace: mynamespace
14spec:
15  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
16  scaleTargetRef:
17    apiVersion: apps/v1
18    kind: Deployment
19    name: myconsumer
20  minReplicas: 1
21  maxReplicas: 5
22  metrics:
23  - type: Object
24    object:
25      describedObject:
26        apiVersion: v1
27        kind: Service
28        name: rmq
29      metric:
30        name: rmq-queue-forum-messages
31      target:
32        type: Value
33        value: 42

Пример использования нестабильной кастомной метрики

Улучшение предыдущего примера.

В представленном примере рассматривается очередь send_forum_message в RabbitMQ, для которого зарегистрирован сервис rmq. Если количество сообщений в этой очереди превышает 42, выполняется масштабирование. Мы не хотим реагировать на краткосрочные всплески, поэтому используется MQL-функцию avg_over_time(), чтобы усреднить метрику.

1apiVersion: deckhouse.io/v1beta1
2kind: ServiceMetric
3metadata:
4  name: rmq-queue-forum-messages
5  namespace: mynamespace
6spec:
7  query: sum (avg_over_time(rabbitmq_queue_messages{<<.LabelMatchers>>,queue=~"send_forum_message",vhost="/"}[5m])) by (<<.GroupBy>>)
8---
9kind: HorizontalPodAutoscaler
10apiVersion: autoscaling/v2
11metadata:
12  name: myhpa
13  namespace: mynamespace
14spec:
15  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
16  scaleTargetRef:
17    apiVersion: apps/v1
18    kind: Deployment
19    name: myconsumer
20  minReplicas: 1
21  maxReplicas: 5
22  metrics:
23  - type: Object
24    object:
25      describedObject:
26        apiVersion: v1
27        kind: Service
28        name: rmq
29      metric:
30        name: rmq-queue-forum-messages
31      target:
32        type: Value
33        value: 42

Примеры с использованием кастомных метрик типа Pods

Пример масштабирования воркеров по процентному количеству активных php-fpm-воркеров. В представленом примере среднее количество php-fpm-воркеров в Deployment mybackend не больше 5.

1apiVersion: deckhouse.io/v1beta1
2kind: PodMetric
3metadata:
4  name: php-fpm-active-workers
5spec:
6  query: sum (phpfpm_processes_total{state="active",<<.LabelMatchers>>}) by (<<.GroupBy>>)
7---
8kind: HorizontalPodAutoscaler
9apiVersion: autoscaling/v2
10metadata:
11  name: myhpa
12  namespace: mynamespace
13spec:
14  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
15  scaleTargetRef:
16    apiVersion: apps/v1
17    kind: Deployment
18    name: mybackend
19  minReplicas: 1
20  maxReplicas: 5
21  metrics:
22  # Указание HPA обойти все поды Deployment'а и собрать с них метрики.
23  - type: Pods
24    # Указывать describedObject в отличие от type: Object не надо.
25    pods:
26      metric:
27        # Кастомная метрика, зарегистрированная с помощью custom resource PodMetric.
28        name: php-fpm-active-workers
29      target:
30        # Для метрик с type: Pods можно использовать только AverageValue.
31        type: AverageValue
32        # Масштабирование, если среднее значение метрики у всех подов Deployment'а больше 5.
33        averageValue: 5

Масштабируется Deployment по процентному количеству активных php-fpm-воркеров.

1---
2apiVersion: deckhouse.io/v1beta1
3kind: PodMetric
4metadata:
5  name: php-fpm-active-worker
6spec:
7  # Процент активных php-fpm-воркеров. Функция round() для того, чтобы не смущаться от миллипроцентов в HPA.
8  query: round(sum by(<<.GroupBy>>) (phpfpm_processes_total{state="active",<<.LabelMatchers>>}) / sum by(<<.GroupBy>>) (phpfpm_processes_total{<<.LabelMatchers>>}) * 100)
9---
10kind: HorizontalPodAutoscaler
11apiVersion: autoscaling/v2
12metadata:
13  name: {{ .Chart.Name }}-hpa
14spec:
15  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
16  scaleTargetRef:
17    apiVersion: apps/v1beta1
18    kind: Deployment
19    name: {{ .Chart.Name }}
20  minReplicas: 4
21  maxReplicas: 8
22  metrics:
23  - type: Pods
24    pods:
25      metric:
26        name: php-fpm-active-worker
27      target:
28        type: AverageValue
29        # Масштабирование, если в среднем по Deployment 80% воркеров заняты.
30        averageValue: 80

Регистрация внешних метрик в Kubernetes API

Модуль prometheus-metrics-adapter поддерживает механизм externalRules, с помощью которого можно определять кастомные PromQL-запросы и регистрировать их как метрики.

В примерах инсталляций добавлено универсальное правило, которое позволяет создавать собственные метрики без настроек в prometheus-metrics-adapter, — «любая метрика в Prometheus с именем kube_adapter_metric_<name> будет зарегистрирована в API под именем <name>». После чего, остается написать экспортер (exporter), который будет экспортировать подобную метрику, или создать правило recording rule в Prometheus, которое будет агрегировать вашу метрику на основе других метрик.

Пример CustomPrometheusRules:

В примере представлены пользовательские правила Prometheus для метрики mymetric.

В примере представлены пользовательские правила Prometheus для метрики mymetric.

1apiVersion: deckhouse.io/v1
2kind: CustomPrometheusRules
3metadata:
4  # Рекомендованный шаблон для названия ваших CustomPrometheusRules.
5  name: prometheus-metrics-adapter-mymetric
6spec:
7  groups:
8  # Рекомендованный шаблон.
9  - name: prometheus-metrics-adapter.mymetric
10    rules:
11    # Название вашей новой метрики.
12    # Важно! Префикс 'kube_adapter_metric_' обязателен.
13    - record: kube_adapter_metric_mymetric
14      # Запрос, результаты которого попадут в итоговую метрику, нет смысла тащить в нее лишние лейблы.
15      expr: sum(ingress_nginx_detail_sent_bytes_sum) by (namespace,ingress)

Применение внешних метрик в HPA

После регистрации внешней метрики на нее можно сослаться.

1kind: HorizontalPodAutoscaler
2apiVersion: autoscaling/v2
3metadata:
4  name: myhpa
5  namespace: mynamespace
6spec:
7  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
8  scaleTargetRef:
9    apiVersion: apps/v1
10    kind: Deployment
11    name: myapp
12  minReplicas: 1
13  maxReplicas: 2
14  metrics:
15  # Используем внешние метрики для масштабирования.
16  - type: External
17    external:
18      metric:
19        # Метрика, которую мы зарегистрировали с помощью создания метрики в Prometheus kube_adapter_metric_mymetric, но без префикса 'kube_adapter_metric_'.
20        name: mymetric
21        selector:
22          # Для внешних метрик можно и нужно уточнять запрос с помощью лейблов.
23          matchLabels:
24            namespace: mynamespace
25            ingress: myingress
26      target:
27        # Для метрик типа External можно использовать только `type: Value`.
28        type: Value
29        # Масштабирование, если значение нашей метрики больше 10.
30        value: 10

Пример с размером очереди в Amazon SQS

Чтобы установить экспортер для интеграции с SQS:

  1. Cоздайте отдельный “служебный” репозиторий Git (или, к примеру, можно использовать “инфраструктурный” репозиторий).
  2. Pазместите в нем инсталляцию экспортера и сценарий для создания требуемого CustomPrometheusRules.

Готово, вы объединили кластер. Если необходимо настроить автомасштабирование только для одного приложения (в одном пространстве имен), лучше ставить экспортер вместе с этим приложением и воспользоваться NamespaceMetrics.

Ниже приведен пример экспортера (например, sqs-exporter) для получения метрик из Amazon SQS, если:

  • в Amazon SQS работает очередь send_forum_message;
  • выполняется масштабирование при количестве сообщений в этой очереди больше 42.
1apiVersion: deckhouse.io/v1
2kind: CustomPrometheusRules
3metadata:
4  # Рекомендованное название — prometheus-metrics-adapter-<metric name>.
5  name: prometheus-metrics-adapter-sqs-messages-visible
6spec:
7  groups:
8  # Рекомендованный шаблон названия.
9  - name: prometheus-metrics-adapter.sqs_messages_visible
10    rules:
11    # Важно! Префикс 'kube_adapter_metric_' обязателен.
12    - record: kube_adapter_metric_sqs_messages_visible
13      expr: sum (sqs_messages_visible) by (queue)
14---
15kind: HorizontalPodAutoscaler
16apiVersion: autoscaling/v2
17metadata:
18  name: myhpa
19  namespace: mynamespace
20spec:
21  # Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
22  scaleTargetRef:
23    apiVersion: apps/v1
24    kind: Deployment
25    name: myconsumer
26  minReplicas: 1
27  maxReplicas: 5
28  metrics:
29  - type: External
30    external:
31      metric:
32        # name должен совпадать с CustomPrometheusRules record без префикса 'kube_adapter_metric_'.
33        name: sqs_messages_visible
34        selector:
35          matchLabels:
36            queue: send_forum_messages
37      target:
38        type: Value
39        value: 42

Способы отладки

Как получить список кастомных метрик?

1kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/

Как получить значение метрики, привязанной к объекту?

1kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/services/*/my-service-metric
2kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/ingresses/*/rps_1m
3kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/ingresses/*/mymetric

Как получить значение метрики, созданной через NamespaceMetric?

1kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/metrics/my-ns-metric

Как получить external-метрики?

1kubectl get --raw /apis/external.metrics.k8s.io/v1beta1
2kubectl get --raw /apis/external.metrics.k8s.io/v1beta1/namespaces/d8-ingress-nginx/d8_ingress_nginx_ds_cpu_utilization