Далее рассматривается только HPA с apiVersion: autoscaling/v2beta2, чья поддержка появилась начиная с 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 секунд Pod’ы могут максимум либо удвоиться, либо, если Pod’ов меньше 4-х, то прибавляется 4шт. - Если метрики говорят о том, что надо масштабировать вниз, то это происходит плавно. В течение пяти минут (
spec.behavior.scaleUp.stabilizationWindowSeconds
= 300) собираются предложения о новом количестве реплик, в результате чего выбирается самое большое значение. Ограничений на количество “уволенных” Pod’ов за раз нет.
Если есть проблемы с флаппингом метрик и наблюдается взрывной рост ненужных реплик приложения, то есть разные подходы:
- Обернуть метрику агрегирующей функцией (например,
avg_over_time()
), если метрика определена PromQL-запросом. Пример… - Увеличить
spec.behavior.scaleUp.stabilizationWindowSeconds
в ресурсеHorizontalPodAutoscaler
. В этом случае, в течение обозначенного периода будут собираться предложения об увеличении количества реплик, в результате чего будет выбрано самое скромное предложение. Иными словами, это решение тождественно применению аггрегирующей функцииmin_over_time(<stabilizationWindowSeconds>)
, но только в случае если метрика растёт и требуется масштабирование вверх. Для масштабирования вниз, как правило, достаточно стандартных настроек. Пример… - Сильнее ограничить скорость прироста новых реплик с помощью
spec.behavior.scaleUp.policies
.
Какой тип масштабирования мне подойдёт?
- С классическим всё понятно.
- Если у вас одно приложение, источник метрик находится внутри Namespace и он связан с одним из объектов, то используйте кастомные Namespace-scoped-метрики.
- Если у вас много приложений используют одинаковую метрику, источник которой находится в Namespace приложения и которая связана с одним из объектов — используйте кастомные Cluster-wide-метрики. Подобные метрики предусмотрены на случай необходимости выделения общих инфраструктурных компонентов в отдельный деплой (“infra”).
- Если источник метрики не привязан к Namespace приложения — используйте внешние метрики. Например, метрики cloud-провайдера или внешнего SaaS-сервиса.
Важно! Настоятельно рекомендуется пользоваться или Вариантом 1. (классическими метриками), или Вариантом 2. (кастомными метриками, определяемыми в Namespace), так как в этом случае вы можете определить всю конфигурацию приложения, включая логику его автомасштабирования, в репозитарии самого приложения. Варианты 3 и 4 стоит рассматривать только если у вас большая коллекция идентичных микросервисов.
Классическое масштабирование по потреблению ресурсов
Пример HPA для масштабирования по базовым метрикам из metrics.k8s.io
: CPU и Memory Pod’ов. Особое внимание на averageUtulization
— это значение отражает целевой процент ресурсов, который был реквестирован.
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
namespace: app-prod
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
# Границы масштабирования контроллера.
minReplicas: 1
maxReplicas: 10
# Если для приложения характерны кратковременные скачки потребления CPU,
# можно отложить принятие решения о масштабировании, чтобы убедиться что оно необходимо.
# По умолчанию масштабирование вверх происходит немедленно.
behavior:
scaleUp:
stabilizationWindowSeconds: 300
metrics:
# Масштабирование по CPU и memory.
- type: Resource
resource:
name: cpu
target:
# Масштабирование, когда среднее использование CPU всех Pod'ов в scaleTargetRef превышает заданное значение.
# Для метрики с type: Resource доступен только type: Utilization
type: Utilization
# Масштабирование, если для всех Pod'ов из Deployment запрошено по 1 ядру и в среднем уже используется более 700m.
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
# Пример масштабирования, когда среднее использование memory всех Pod'ов в scaleTargetRef превышает заданное значение.
type: Utilization
# Масштабирование, если для Pod'ов запрошено по 1GB памяти и в среднем использовано уже более 800MB.
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-scoped-ресурса можно определить метрику глобально, а с помощью Namespaced-ресурса можно её локально переопределять. Формат у всех CR — одинаковый.
Применяем кастомные метрики в HPA
После регистрации кастомной метрики на нее можно сослаться. С точки зрения HPA, кастомные метрики бывают двух видов — Pods
и Object
. С Object
всё просто — это отсылка к объекту в кластере, который имеет в Prometheus метрики с соответствующими лейблами (namespace=XXX,ingress=YYY
). Эти лейблы будут подставляться вместо <<.LabelMatchers>>
в вашем кастомном запросе.
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 1
maxReplicas: 2
# Метрики, используемые для масштабирования.
# Пример использования кастомных метрик.
metrics:
- type: Object
object:
# Объект, который обладает метриками в Prometheus.
describedObject:
apiVersion: extensions/v1beta1
kind: Ingress
name: myingress
metric:
# Метрика, зарегистрированная с помощью CR IngressMetric или ClusterIngressMetric.
name: mymetric
target:
# Для метрик типа Object можно использовать только `type: Value`.
type: Value
# Масштабирование, если значение кастомной метрики больше 10.
value: 10
C Pods
сложнее — из ресурса, который масштабирует HPA, будут извлечены все Pod’ы и по каждому будет собраны метрики с соответствующими лейблами (namespace=XXX,pod=YYY-sadiq
,namespace=XXX,pod=YYY-e3adf
,…). Из этих метрик HPA посчитает среднее и использует для масштабирования. Пример…
Пример использования кастомных метрик с размером очереди RabbitMQ
Имеем очередь send_forum_message
в RabbitMQ, для которого зарегистрирован сервис rmq
. Если сообщений в очереди больше 42 — масштабируем.
apiVersion: deckhouse.io/v1beta1
kind: ServiceMetric
metadata:
name: rmq-queue-forum-messages
namespace: mynamespace
spec:
query: sum (rabbitmq_queue_messages{<<.LabelMatchers>>,queue=~"send_forum_message",vhost="/"}) by (<<.GroupBy>>)
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myconsumer
minReplicas: 1
maxReplicas: 5
metrics:
- type: Object
object:
describedObject:
apiVersion: v1
kind: Service
name: rmq
metric:
name: rmq-queue-forum-messages
target:
type: Value
value: 42
Пример использования нестабильной кастомной метрики
Улучшение предыдущего примера.
Имеем очередь send_forum_message
в RabbitMQ, для которого зарегистрирован сервис rmq
. Если сообщений в очереди больше 42 — масштабируем.
При этом мы не хотим реагировать на кратковременные вспышки, для этого усредняем метрику с помощью MQL-функции avg_over_time()
.
apiVersion: deckhouse.io/v1beta1
kind: ServiceMetric
metadata:
name: rmq-queue-forum-messages
namespace: mynamespace
spec:
query: sum (avg_over_time(rabbitmq_queue_messages{<<.LabelMatchers>>,queue=~"send_forum_message",vhost="/"}[5m])) by (<<.GroupBy>>)
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myconsumer
minReplicas: 1
maxReplicas: 5
metrics:
- type: Object
object:
describedObject:
apiVersion: v1
kind: Service
name: rmq
metric:
name: rmq-queue-forum-messages
target:
type: Value
value: 42
Примеры с использованием кастомных метрик типа Pods
Хотим, чтобы среднее количество php-fpm-воркеров в Deployment mybackend
было не больше 5.
apiVersion: deckhouse.io/v1beta1
kind: PodMetric
metadata:
name: php-fpm-active-workers
spec:
query: sum (phpfpm_processes_total{state="active",<<.LabelMatchers>>}) by (<<.GroupBy>>)
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mybackend
minReplicas: 1
maxReplicas: 5
metrics:
# Указание HPA обойти все Pod'ы Deployment'а и собрать с них метрики.
- type: Pods
# Указывать describedObject в отличие от type: Object не надо.
pods:
metric:
# Кастомная метрика, зарегистрированная с помощью CR PodMetric.
name: php-fpm-active-workers
target:
# Для метрик с type: Pods можно использовать только AverageValue.
type: AverageValue
# Масштабирование, если среднее значение метрики у всех Pod'ов Deployment'а больше 5.
averageValue: 5
Масштабируем Deployment по процентному количеству active-воркеров php-fpm.
---
apiVersion: deckhouse.io/v1beta1
kind: PodMetric
metadata:
name: php-fpm-active-worker
spec:
# Процент active-воркеров в php-fpm. Функция round() для того, чтобы не смущаться от милли-процентов в HPA.
query: round(sum by(<<.GroupBy>>) (phpfpm_processes_total{state="active",<<.LabelMatchers>>}) / sum by(<<.GroupBy>>) (phpfpm_processes_total{<<.LabelMatchers>>}) * 100)
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: {{ .Chart.Name }}-hpa
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1beta1
kind: Deployment
name: {{ .Chart.Name }}
minReplicas: 4
maxReplicas: 8
metrics:
- type: Pods
pods:
metric:
name: php-fpm-active-worker
target:
type: AverageValue
# Масштабирование, если в среднем по Deployment 80% воркеров заняты.
averageValue: 80
Регистрируем внешние метрики в Kubernetes API
Модуль prometheus-metrics-adapter
поддерживает механизм externalRules
, с помощью которого можно определять кастомные PromQL-запросы и регистрировать их как метрики.
В наших инсталляциях мы добавили универсальное правило, которое позволяет создавать свои метрики без внесения настроек в prometheus-metrics-adapter
— “любая метрика в Prometheus с именем kube_adapter_metric_<name>
будет зарегистрирована в API под именем <name>
”. То есть, остаётся либо написать exporter, который будет экспортировать подобную метрику, либо создать recording rule в Prometheus, которое будет агрегировать вашу метрику на основе других метрик.
Пример CustomPrometheusRules
:
apiVersion: deckhouse.io/v1
kind: CustomPrometheusRules
metadata:
# Рекомендованный шаблон для названия вашего CustomPrometheusRules.
name: prometheus-metrics-adapter-mymetric
spec:
groups:
# Рекомендованный шаблон.
- name: prometheus-metrics-adapter.mymetric
rules:
# Название вашей новой метрики.
# Важно! Префикс 'kube_adapter_metric_' обязателен.
- record: kube_adapter_metric_mymetric
# Запрос, результаты которого попадут в итоговую метрику, нет смысла тащить в неё лишние лейблы.
expr: sum(ingress_nginx_detail_sent_bytes_sum) by (namespace,ingress)
Применяем внешние метрики в HPA
После регистрации внешней метрики, на нее можно сослаться.
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 1
maxReplicas: 2
metrics:
# Используем внешние метрики для масштабирования.
- type: External
external:
metric:
# Метрика, которую мы зарегистрировали с помощью создания метрики в Prometheus kube_adapter_metric_mymetric, но без префикса 'kube_adapter_metric_'.
name: mymetric
selector:
# Для внешних метрик можно и нужно уточнять запрос с помощью лейблов.
matchLabels:
namespace: mynamespace
ingress: myingress
target:
# Для метрик типа External можно использовать только `type: Value`.
type: Value
# Масштабирование, если значение нашей метрики больше 10.
value: 10
Пример с размером очереди в Amazon SQS
Для интеграции с SQS вам понадобится установка экспортера. Для этого нужно завести отдельный “служебный” git-репозитарий (или, например, использовать “инфраструктурный” репозитарий) и разместить в нем установку этого экспортера, а также создание необходимого
CustomPrometheusRules
, таким образом интегрировав кластер. Если же вам нужно настроить автомасштабирование только для одного приложения (особенно живущего в одном пространстве имен), лучше ставить экспортер вместе с этим приложением и воспользоватьсяNamespaceMetrics
.
В следующем примере подразумевается, что в Amazon SQS работает очередь send_forum_message
. Если сообщений в очереди больше 42 — масштабируем. Для получения метрик из Amazon SQS понадобится exporter (например — sqs-exporter).
apiVersion: deckhouse.io/v1
kind: CustomPrometheusRules
metadata:
# Рекомендованное название — prometheus-metrics-adapter-<metric name>.
name: prometheus-metrics-adapter-sqs-messages-visible
spec:
groups:
# Рекомендованный шаблон названия.
- name: prometheus-metrics-adapter.sqs_messages_visible
rules:
# Важно! Префикс 'kube_adapter_metric_' обязателен.
- record: kube_adapter_metric_sqs_messages_visible
expr: sum (sqs_messages_visible) by (queue)
---
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta2
metadata:
name: myhpa
namespace: mynamespace
spec:
# Указывается контроллер, который нужно масштабировать (ссылка на deployment или statefulset).
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myconsumer
minReplicas: 1
maxReplicas: 5
metrics:
- type: External
external:
metric:
# name должен совпадать с CustomPrometheusRules record без префикса 'kube_adapter_metric_'.
name: sqs_messages_visible
selector:
matchLabels:
queue: send_forum_messages
target:
type: Value
value: 42
Способы отладки
Как получить список кастомных метрик?
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/
Как получить значение метрики, привязанной к объекту?
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/services/*/my-service-metric
Как получить значение метрики, созданной через NamespaceMetric
?
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/my-namespace/metrics/my-ns-metric
Как получить external-метрики?
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1"