Thank you for reading this post, don't forget to subscribe!
В этой статье рассмотрим использование 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
такого содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
:
1 2 |
kubectl create <span class="hljs-_">-f</span> <span class="hljs-built_in">test</span>-hpa.yaml |
Проверим наличие объекта:
1 2 3 4 |
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE <span class="hljs-built_in">test</span>-hpa Deployment/<span class="hljs-built_in">test</span>-api-deploy <unknown>/80% 10 29 0 7s |
Спустя некоторое время, вместо <unknown>
, мы должны увидеть текущее использование CPU подами в деплойменте test-api-deploy, однако в моем случае этого не произошло. Начинаем разбираться - для начала, убедимся, что metrics.k8s.io
доступно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
kubectl get --raw <span class="hljs-string">"/apis/metrics.k8s.io/"</span> | jq { <span class="hljs-string">"kind"</span>: <span class="hljs-string">"APIGroup"</span>, <span class="hljs-string">"apiVersion"</span>: <span class="hljs-string">"v1"</span>, <span class="hljs-string">"name"</span>: <span class="hljs-string">"metrics.k8s.io"</span>, <span class="hljs-string">"versions"</span>: [ { <span class="hljs-string">"groupVersion"</span>: <span class="hljs-string">"<a class="vglnk" href="http://metrics.k8s.io/v1beta1" rel="nofollow">metrics.k8s.io/v1beta1</a>"</span>, <span class="hljs-string">"version"</span>: <span class="hljs-string">"v1beta1"</span> } ], <span class="hljs-string">"preferredVersion"</span>: { <span class="hljs-string">"groupVersion"</span>: <span class="hljs-string">"<a class="vglnk" href="http://metrics.k8s.io/v1beta1" rel="nofollow">metrics.k8s.io/v1beta1</a>"</span>, <span class="hljs-string">"version"</span>: <span class="hljs-string">"v1beta1"</span> } } |
Проверим, что метрики использования CPU доступны. Первый вариант:
1 2 3 4 5 6 7 8 9 10 11 12 |
kubectl top pod | grep <span class="hljs-built_in">test</span>-api-deploy <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-2t9x9 738m 43931Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-fhr7b 643m 43999Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-gcrlc 700m 44028Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-lx24k 666m 44201Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-mzlzb 660m 44048Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-ndjwx 651m 44136Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-q2nvw 654m 44177Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-qmw4t 692m 44051Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-rl4bb 650m 43979Mi <span class="hljs-built_in">test</span>-api-deploy-5f77b79896-xhpbx 752m 44116Mi |
Второй вариант (метрики только одного конкретного пода):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/<span class="hljs-built_in">test</span>-api-deploy-5f77b79896-xhpbx | jq { <span class="hljs-string">"kind"</span>: <span class="hljs-string">"PodMetrics"</span>, <span class="hljs-string">"apiVersion"</span>: <span class="hljs-string">"<a class="vglnk" href="http://metrics.k8s.io/v1beta1" rel="nofollow">metrics.k8s.io/v1beta1</a>"</span>, <span class="hljs-string">"metadata"</span>: { <span class="hljs-string">"name"</span>: <span class="hljs-string">"test-api-deploy-5f77b79896-xhpbx"</span>, <span class="hljs-string">"namespace"</span>: <span class="hljs-string">"default"</span>, <span class="hljs-string">"selfLink"</span>: <span class="hljs-string">"/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/test-api-deploy-5f77b79896-xhpbx"</span>, <span class="hljs-string">"creationTimestamp"</span>: <span class="hljs-string">"2019-06-11T13:50:00Z"</span> }, <span class="hljs-string">"timestamp"</span>: <span class="hljs-string">"2019-06-11T13:49:41Z"</span>, <span class="hljs-string">"window"</span>: <span class="hljs-string">"30s"</span>, <span class="hljs-string">"containers"</span>: [ { <span class="hljs-string">"name"</span>: <span class="hljs-string">"envoy"</span>, <span class="hljs-string">"usage"</span>: { <span class="hljs-string">"cpu"</span>: <span class="hljs-string">"489151208n"</span>, <span class="hljs-string">"memory"</span>: <span class="hljs-string">"45692Ki"</span> } }, { <span class="hljs-string">"name"</span>: <span class="hljs-string">"test"</span>, <span class="hljs-string">"usage"</span>: { <span class="hljs-string">"cpu"</span>: <span class="hljs-string">"7125240328n"</span>, <span class="hljs-string">"memory"</span>: <span class="hljs-string">"45515856Ki"</span> } } ] } |
Как видим, метрики доступны. Получим детальное описание нашего HorizontalPodAutoscaler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
kubectl describe hpa <span class="hljs-built_in">test</span>-hpa Name: <span class="hljs-built_in">test</span>-hpa Namespace: default Labels: <a class="vglnk" href="http://app.kubernetes.io/managed-by" rel="nofollow">app.kubernetes.io/managed-by</a>=spinnaker <a class="vglnk" href="http://app.kubernetes.io/name" rel="nofollow">app.kubernetes.io/name</a>=<span class="hljs-built_in">test</span> Annotations: <a class="vglnk" href="http://artifact.spinnaker.io/location" rel="nofollow">artifact.spinnaker.io/location</a>: default <a class="vglnk" href="http://artifact.spinnaker.io/name" rel="nofollow">artifact.spinnaker.io/name</a>: <span class="hljs-built_in">test</span>-hpa artifact.spinnaker.io/<span class="hljs-built_in">type</span>: kubernetes/horizontalpodautoscaler <a class="vglnk" href="http://kubectl.kubernetes.io/last-applied-configuration" rel="nofollow">kubectl.kubernetes.io/last-applied-configuration</a>: {<span class="hljs-string">"apiVersion"</span>:<span class="hljs-string">"autoscaling/v2beta1"</span>,<span class="hljs-string">"kind"</span>:<span class="hljs-string">"HorizontalPodAutoscaler"</span>,<span class="hljs-string">"metadata"</span>:{<span class="hljs-string">"annotations"</span>:{<span class="hljs-string">"<a class="vglnk" href="http://artifact.spinnaker.io/location" rel="nofollow">artifact.spinnaker.io/location</a>"</span>:<span class="hljs-string">"default"</span>… <a class="vglnk" href="http://moniker.spinnaker.io/application" rel="nofollow">moniker.spinnaker.io/application</a>: <span class="hljs-built_in">test</span> <a class="vglnk" href="http://moniker.spinnaker.io/cluster" rel="nofollow">moniker.spinnaker.io/cluster</a>: horizontalpodautoscaler <span class="hljs-built_in">test</span>-hpa CreationTimestamp: Tue, 11 Jun 2019 11:21:03 +0300 Reference: Deployment/<span class="hljs-built_in">test</span>-api-deploy Metrics: ( current / target ) resource cpu on pods (as a percentage of request): <unknown> / 80% M<span class="hljs-keyword">in</span> 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<span class="hljs-string">'</span><span class="hljs-string">s current scale ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count: missing request </span><span class="hljs-string">for</span><span class="hljs-string"> 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 </span><span class="hljs-string">(</span><span class="hljs-string">x12 over 7m2s</span><span class="hljs-string">)</span><span class="hljs-string"> horizontal-pod-autoscaler failed to get cpu utilization: missing request </span><span class="hljs-string">for</span><span class="hljs-string"> cpu Warning FailedGetResourceMetric 2m15s </span><span class="hljs-string">(</span><span class="hljs-string">x20 over 7m2s</span><span class="hljs-string">)</span><span class="hljs-string"> horizontal-pod-autoscaler missing request </span><span class="hljs-string">for</span><span class="hljs-string"> cpu </span> |
Здесь самое важное - сообщение the HPA was unable to compute the replica count: missing request for cpu
. И действительно, в манифесте развертывания (Deployment
) не указаны resource requests для одного из контейнеров (с именем envoy):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
apiVersion: apps/v1 kind: Deployment metadata: annotations: # From <a class="vglnk" href="https://www.spinnaker.io/reference/providers/kubernetes-v2/#strategy" rel="nofollow">https://www.spinnaker.io/reference/providers/kubernetes-v2/#strategy</a> <a class="vglnk" href="http://strategy.spinnaker.io/use-source-capacity" rel="nofollow">strategy.spinnaker.io/use-source-capacity</a>: "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: <a class="vglnk" href="http://index.docker.io/ealebed/test:v1" rel="nofollow">index.docker.io/ealebed/test:v1</a> 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
, базируясь на полученных метриках, начинает масштабировать поды в развертывании:
1 2 3 4 |
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE <span class="hljs-built_in">test</span>-hpa Deployment/<span class="hljs-built_in">test</span>-api-deploy 86%/80% 10 29 29 9m10 |
Формула, по которой HorizontalPodAutoscaler
вычисляет требуемое количество реплик выглядит так:
1 2 |
<span class="hljs-attr">desiredReplicas</span> = 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
), количество реплик было автоматически уменьшено:
1 2 3 4 |
kubectl get horizontalpodautoscaler NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE <span class="hljs-built_in">test</span>-hpa Deployment/<span class="hljs-built_in">test</span>-api-deploy 70%/80% 20 29 23 1h |