K8s Q A Horisontal Pod Autoscaler
HorizontalPodAutoscaler
В этой статье рассмотрим использование HorizontalPodAutoscaler - объектов, предназначенных для автоматического масштабирования количества подов (Pods) в
Replication Controller, Replica Set или Deployment, основываясь на использовании CPU (или, при поддержке custom metrics, на других метриках приложения).
Сразу стоит отметить, что HorizontalPodAutoscaler не может быть применен к объектам, которые не предназначены для масштабирования, например DaemonSets.
Horizontal Pod Autoscaler состоит из Kubernetes ресурса (объекта) и контроллера, поведение которого описывается ресурсом.
C периодичностью 15 секунд (можно изменить с помощью параметра --horizontal-pod-autoscaler-sync-period), контроллер собирает данные по использованию метрик,
определенных в манифесте ресурса HorizontalPodAutoscaler.
Метрики собираются или с resource metrics API (метрики использования ресурсов подами) или с custom metrics API (остальные метрики, например, метрики приложения).
Для каждого подконтрольного пода, контроллер собирает метрики (например, использования CPU) с resource metrics API (metrics.k8s.io, предоставляется metrics-server).
Далее, происходит вычисление текущего значения использования CPU в процентах от запрошенных ресурсов (resource request) контейнерами каждого пода,
после чего это значение сравнивается с “целевым” (target) значением - порогом, после которого количество подов должно быть увеличено.
Рассмотрим конкретный пример. Создадим файл test-hpa.yaml с описанием ресурса HorizontalPodAutoscaler такого содержания:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: test-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: test-api-deploy minReplicas: 10 maxReplicas: 29 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 80
Создадим данный объект в кластере Kubernetes:
kubectl create -f test-hpa.yaml
Проверим наличие объекта:
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE test-hpa Deployment/test-api-deploy <unknown>/80% 10 29 0 7s
Спустя некоторое время, вместо <unknown>, мы должны увидеть текущее использование CPU подами в деплойменте test-api-deploy, однако в моем случае этого не произошло.
Начинаем разбираться - для начала, убедимся, что metrics.k8s.io доступно:
kubectl get --raw "/apis/metrics.k8s.io/" | jq { "kind": "APIGroup", "apiVersion": "v1", "name": "metrics.k8s.io", "versions": [ { "groupVersion": "metrics.k8s.io/v1beta1", "version": "v1beta1" } ], "preferredVersion": { "groupVersion": "metrics.k8s.io/v1beta1", "version": "v1beta1" } }
Проверим, что метрики использования CPU доступны. Первый вариант:
kubectl top pod | grep test-api-deploy test-api-deploy-5f77b79896-2t9x9 738m 43931Mi test-api-deploy-5f77b79896-fhr7b 643m 43999Mi test-api-deploy-5f77b79896-gcrlc 700m 44028Mi test-api-deploy-5f77b79896-lx24k 666m 44201Mi test-api-deploy-5f77b79896-mzlzb 660m 44048Mi test-api-deploy-5f77b79896-ndjwx 651m 44136Mi test-api-deploy-5f77b79896-q2nvw 654m 44177Mi test-api-deploy-5f77b79896-qmw4t 692m 44051Mi test-api-deploy-5f77b79896-rl4bb 650m 43979Mi test-api-deploy-5f77b79896-xhpbx 752m 44116Mi
Второй вариант (метрики только одного конкретного пода):
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/test-api-deploy-5f77b79896-xhpbx | jq { "kind": "PodMetrics", "apiVersion": "metrics.k8s.io/v1beta1", "metadata": { "name": "test-api-deploy-5f77b79896-xhpbx", "namespace": "default", "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/test-api-deploy-5f77b79896-xhpbx", "creationTimestamp": "2019-06-11T13:50:00Z" }, "timestamp": "2019-06-11T13:49:41Z", "window": "30s", "containers": [ { "name": "envoy", "usage": { "cpu": "489151208n", "memory": "45692Ki" } }, { "name": "test", "usage": { "cpu": "7125240328n", "memory": "45515856Ki" } } ] }
Как видим, метрики доступны. Получим детальное описание нашего HorizontalPodAutoscaler:
kubectl describe hpa test-hpa Name: test-hpa Namespace: default Labels: app.kubernetes.io/managed-by=spinnaker app.kubernetes.io/name=test Annotations: artifact.spinnaker.io/location: default artifact.spinnaker.io/name: test-hpa artifact.spinnaker.io/type: kubernetes/horizontalpodautoscaler kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"autoscaling/v2beta1","kind":"HorizontalPodAutoscaler","metadata":{"annotations":{"artifact.spinnaker.io/location":"default"... moniker.spinnaker.io/application: test moniker.spinnaker.io/cluster: horizontalpodautoscaler test-hpa CreationTimestamp: Tue, 11 Jun 2019 11:21:03 +0300 Reference: Deployment/test-api-deploy Metrics: ( current / target ) resource cpu on pods (as a percentage of request): <unknown> / 80% Min replicas: 10 Max replicas: 29 Deployment pods: 10 current / 10 desired Conditions: Type Status Reason Message ---- ------ ------ ------- AbleToScale True SucceededGetScale the HPA controller was able to get the target's current scale ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count: missing request for cpu Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulRescale 7m17s horizontal-pod-autoscaler New size: 10; reason: Current number of replicas below Spec.MinReplicas Warning FailedComputeMetricsReplicas 4m15s (x12 over 7m2s) horizontal-pod-autoscaler failed to get cpu utilization: missing request for cpu Warning FailedGetResourceMetric 2m15s (x20 over 7m2s) horizontal-pod-autoscaler missing request for cpu
Здесь самое важное - сообщение the HPA was unable to compute the replica count: missing request for cpu. И действительно, в манифесте развертывания (Deployment) не указаны resource requests для одного из контейнеров (с именем envoy):
apiVersion: apps/v1 kind: Deployment metadata: annotations: # From https://www.spinnaker.io/reference/providers/kubernetes-v2/#strategy strategy.spinnaker.io/use-source-capacity: "true" name: test-api-deploy spec: # replicas: 15 selector: matchLabels: deployment: test-api-deploy strategy: rollingUpdate: maxSurge: 0 type: RollingUpdate template: metadata: labels: deployment: test-api-deploy spec: containers: - image: envoyproxy/envoy:v1.10.0 name: envoy ports: - containerPort: 8080 name: http volumeMounts: - mountPath: /etc/envoy name: envoy-config - env: - name: JAVA_OPTS value: -Xms40g -Xmx40g image: index.docker.io/ealebed/test:v1 name: test resources: limits: memory: 55Gi requests: cpu: "10" memory: 55Gi volumes: - configMap: name: envoy-config name: envoy-config
Важно! Если не указаны resource request хотя бы для одного из контейнеров в Replication Controller, Replica Set или Deployment,
то текущее значение использование CPU подами не может быть корректно определено, и, в результате, HorizontalPodAutoscaler
не будет предпринимать никаких действий по масштабированию.
После исправления этой досадной ошибки, HorizontalPodAutoscaler, базируясь на полученных метриках, начинает масштабировать поды в развертывании:
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE test-hpa Deployment/test-api-deploy 86%/80% 10 29 29 9m10
Формула, по которой HorizontalPodAutoscaler вычисляет требуемое количество реплик выглядит так:
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
Например,
если текущее значение метрики (currentMetricValue) равно 200m, а ожидаемое (desiredMetricValue) установлено в 100m, то количество реплик будет удвоено (200.0 / 100.0 == 2.0). Если же текущее значение метрики равно всего лишь 50m, то количество реплик должно быть уменьшено вдвое (50.0 / 100.0 == 0.5). Если соотношение текущего значения метрики к ожидаемому значению достаточно близко к 1, то никаких действий не будет предпринято.
Так как мы указали targetAverageUtilization при описании ресурса HorizontalPodAutoscaler, то текущее значение метрики (currentMetricValue) использования CPU рассчитывается как среднее значение этой метрики для всех подов, контролируемых данным автоскейлером.
После того, как текущее значение использования CPU снизилось и оставалось низким в течении 5 минут (устанавливается с помощью параметра --horizontal-pod-autoscaler-downscale-stabilization), количество реплик было автоматически уменьшено:
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE test-hpa Deployment/test-api-deploy 70%/80% 20 29 23 1h
TODO: более сложный вариант автоскейлинга, базирующийся на метриках приложения.
Еще пример
HorizontalPodAutoscaler Рассмотрим другую ситуацию: что происходит, если на приложение приходит незапланированная нагрузка, которая значительно выше той, что мы «привыкли» обрабатывать? Да, ничто не мешает вручную зайти в кластер и отмасштабировать pod’ы… но ради чего тогда мы тут все тогда собрались, если все делать руками? На помощь приходит HorizontalPodAutoscaler (HPA). Этот механизм позволяет указать нужную метрику(и) настроить автоматический порог масштабирования pod’ов в зависимости от изменения её значений. Представьте, что вы спокойно спите, но внезапно, ночью, приходит небывалая нагрузка — скажем, заокеанские пользователи узнали про ваш сервис на Reddit. Нагрузка на CPU (или показатель иной метрики) у pod’ов вырастает, достигает порога… после чего HPA начинает доблестно масштабировать pod’ы, чтобы способствовать распределению нагрузки благодаря выделению новых ресурсов. В итоге, все входящие запросы обработаны в нормальном режиме. Причем — и это важно! — как только нагрузка вернется в привычное русло, HPA отмасштабирует pod’ы обратно, тем самым снижая затраты на инфраструктуру. Звучит здорово, не так ли? Разберемся, как именно HPA вычисляет, сколько реплик надо добавить. Вот формула из документации: desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )] Предположим: текущее количество реплик = 3; текущее значение метрики = 100; пороговое значение метрики = 60. Получаем следующее выражение: 3 * ( 100 / 60 ), т.е. на выходе получаем «около» 5 (HPA округлит результат в большую сторону). Таким образом, приложению будут добавлены еще две реплики. А значение будет по-прежнему вычисляться по формуле, чтобы, как только нагрузка снизится, уменьшилось и количество необходимых реплик для обработки этой нагрузки. Здесь начинается самое интересное. Что же выбрать в качестве метрики? Первое, что приходит на ум, — это базовые показатели, такие как CPU, Memory… И такое решение действительно сработает, если у вас… нагрузка на CPU и Memory растет прямо пропорционально входящей нагрузке. Но что, если pod’ы обрабатывают разные запросы: одни могут потребовать много тактов процессора, другие — много памяти, а третьи — вообще укладываются в минимальные ресурсы? Рассмотрим на примере с очередью на RabbitMQ и теми, кто эту очередь будет разбирать. Допустим, в очереди 10 сообщений. Мы видим (спасибо мониторингу!), что очередь разбирается довольно быстро. То есть для нас нормально, когда в среднем в очереди скапливается до 10 сообщений. Но вот пришла нагрузка — очередь сообщений выросла до 100. Однако нагрузка на CPU и Memory не изменится у worker’ов: они будут монотонно разбирать очередь, оставляя там уже около 80-90 сообщений. А ведь если бы мы настроили HPA по нашей (кастомной) метрике, описывающей количество сообщений в очереди, то получили бы понятную такую картину: текущее количество реплик = 3; текущее значение метрики = 80; пороговое значение метрики = 15. Т.е.: 3 * ( 80 / 15 ) = 16. Тогда HPA начнет масштабировать worker’ы до 16 реплик, и они быстро разберут все сообщения в очереди (после чего HPA может масштабировать их вниз). Однако для этого важно, чтобы мы были «инфраструктурно готовы» к тому, что дополнительно может развернуться еще столько pod’ов. То есть они должны влезть на текущие узлы, или же новые узлы должны быть заказаны у поставщика инфраструктуры (облачного провайдера), если вы используете Cluster Autoscaler. В общем, это очередная отсылка к планированию ресурсов кластера. Теперь взглянем на несколько манифестов: apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:
name: php-apache
spec:
scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: php-apache minReplicas: 1 maxReplicas: 10 targetCPUUtilizationPercentage: 50
Тут все просто. Как только pod достигает нагрузки по CPU в 50%, HPA начнет масштабировать максимум до 10. А вот более интересный вариант: apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:
name: worker
spec:
scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: worker minReplicas: 1 maxReplicas: 10 metrics: - type: External external: metric: name: queue_messages target: type: AverageValue averageValue: 15
Мы уже смотрим на custom metrics. Опираясь на значение queue_messages, HPA будет принимать решение о необходимости масштабирования. Учитывая, что для нас нормально, если в очереди около 10 сообщений, здесь выставлено среднее значение в 15 как пороговое. Так можно контролировать количество реплик уже более точно. Согласитесь, что и автомасштабирование будет куда лучше и точнее [чем по условному CPU] в случае разбора очереди? Дополнительные фичи Возможности настройки HPA уже весьма разнообразны. Например, можно комбинировать метрики. Вот что получится для размер очереди сообщений и CPU: apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:
name: worker
spec:
scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: worker minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50 - type: External external: metric: name: queue_messages target: type: AverageValue averageValue: 15
Как будет считать HPA? У какой из метрик при подсчете получилось большее количество реплик, на тот он и будет опираться. Если из расчета потребления CPU выходит, что надо масштабировать до 5, а по расчетам на основании размерности очереди — до 3, то произойдет масштабирование до 5 pod’ов. С релиза Kubernetes 1.18 появилась возможность прописывать политики scaleUp и scaleDown. Например: behavior:
scaleDown: stabilizationWindowSeconds: 60 policies: - type: Percent value: 5 periodSeconds: 20 - type: Pods value: 5 periodSeconds: 60 selectPolicy: Min scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 10
Здесь заданы две секции: одна определяет параметры масштабирования вниз (scaleDown), вторая — вверх (scaleUp). В каждой из секций есть интересный параметр — stabilizationWindowSeconds. Он позволяет избавиться от «флаппинга», то есть ситуации, при которой HPA будет масштабировать то вверх, то вниз. Грубо говоря, это некий таймаут после последней операции изменения количества реплик. Теперь о политиках, и начнем со scaleDown. Эта политика позволяет указать, какой процент pod’ов (type: Percent) можно масштабировать вниз за указанный период времени. Если мы понимаем, что нагрузка на приложение — волнообразная, что спадает она так же волнообразно, надо выставить процент поменьше, а период — побольше. Тогда при снижении нагрузки HPA не станет сразу убивать множество pod’ов по своей формуле, а будет вынужден делать это постепенно. Вдобавок, мы можем указать явное количество pod’ов (type: Pods), больше которого за период времени убивать никак нельзя. Также стоит обратить внимание на параметр selectPolicy: Min — он указывает на необходимость исходить из политики минимального количества pod’ов. То есть: если 5 процентов меньше, чем 5 единиц pod’ов, будет выбрано это значение. А если наоборот, то убирать будем 5 pod’ов. Соответственно, выставление selectPolicy: Max даст обратный эффект. Со scaleUp аналогичная ситуация. В большинстве случаев требуется, чтобы масштабирование вверх происходило с минимальной задержкой, поскольку это может повлиять на пользователей и их опыт взаимодействия с приложением. Поэтому stabilizationWindowSeconds здесь выставлен в 0. Зная, что нагрузка приходит волнами, мы позволяем HPA при необходимости поднять реплики до значения maxReplicas, которое определено в манифесте HPA. За это отвечает политика, позволяющая раз в periodSeconds: 10, поднимать до 100% реплик. Наконец, для случаев, когда мы не хотим, чтобы HPA масштабировал вниз, если уже прошла нагрузка, можно указать: behavior:
scaleDown: selectPolicy: Disabled
Как правило, политики нужны тогда, когда у вас HPA работает не так, как вы на это рассчитываете. Политики дают большую гибкость, но усложняют восприятие манифеста. А в скором времени получится даже опираться на ресурсы конкретного контейнера в pod’е (представлено как alpha в Kubernetes 1.20). Итог по HPA Закончим примером финального манифеста для HPA: apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata:
name: worker
spec:
scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: worker minReplicas: 1 maxReplicas: 10 metrics: - type: External external: metric: name: queue_messages target: type: AverageValue averageValue: 15 behavior: scaleDown: stabilizationWindowSeconds: 60 policies: - type: Percent value: 5 periodSeconds: 20 - type: Pods value: 5 periodSeconds: 60 selectPolicy: Min scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 10
NB. Этот пример подготовлен исключительно в ознакомительных целях. Помните, что его необходимо адаптировать под свои условия.
Подведем итог по Horizontal Pod Autoscaler. Использовать HPA для production-окружений всегда полезно. Но выбирать метрики, на которых он будет основываться, надо тщательно. Неверно выбранная метрика или некорректный порог ее срабатывания будет приводить к тому, что либо получится перерасход по ресурсам (из-за лишних реплик), либо клиенты увидят деградацию сервиса (если реплик окажется недостаточно). Внимательно изучайте поведение вашего приложения и проверяйте его, чтобы достичь нужного баланса.