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 |