Kubernetes HorizontalPodAutoscaler, как видно из названия, предназначен для автоматического скейлинга Kubernetes Pods в кластере, которые управляются
ReplicationController
, Deployment
или ReplicaSet
контроллерами, основываясь на их метриках потребления ресурсов — CPU, память и т.д.
Кратко его рассматривали в посте Kubernetes: запуск metrics-server в AWS EKS для Kubernetes Pod AutoScaler, теперь разберёмся с доступными метриками.
Для HPA доступны три типа метрик:
metrics.k8s.io
: метрики использования ресурсов, как правило предоставляетсяmetrics-server
custom.metrics.k8s.io
: метрики, предоставляемые адаптерами (контроллерами), работающими в самом кластере, такими как Microsoft Azure Adapter, Google Stackdriver, Prometheus Adapter (Prometheus Adapter используем в этом посте чуть позже), см. полный список тут>>>external.metrics.k8s.io
: по сути тот же Custom Metircs API, но метрики поставляются внешней системой, например от AWS CloudWatch
См. Support for metrics APIs и Custom and external metrics for autoscaling workloads.
Кроме HPA существует Vertical Pod Autoscaling (VPA), и их можно комбинировать, хотя и с ограничениями, см. Horizontal Pod Autoscaling Limitations.
Содержание
Создание HorizontalPodAutoscaler
Создадим простой НРА, который будет выполнять скейлинг по потреблению CPU:
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: hpa-example spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: deployment-example minReplicas: 1 maxReplicas: 5 targetCPUUtilizationPercentage: 10
Тут:
apiVersion: autoscaling/v1
— API-группаautoscaling
, обращайте внимание на версию, т.к. в APIv1
доступен скейлинг только по CPU, а memory и custom metrics могут быть использованы в API `v2beta2` (вv1
доступны через аннотации), см. API Object.spec.scaleTargetRef
: указываем НРА, какой контроллер он будет скейлить (ReplicationController
,Deployment
илиReplicaSet
), в данном случае будет выполнен поиск объекта типаDeployment
с именем deployment-examplespec.minReplicas
,spec.maxReplicas
: минимальное и максимальное количество подов в контроллере, которое будет запущено этим HPAtargetCPUUtilizationPercentage
: % отrequests
использования CPU, при достижении которого НРА начнёт добавлять или удалять поды
Создаём его:
[simterm]
$ kubectl apply -f hpa-example.yaml horizontalpodautoscaler.autoscaling/hpa-example created
[/simterm]
Проверяем:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example <unknown>/10% 1 5 0 89s
[/simterm]
Сейчас у нас TARGETS
со значением <unknown>, т.к. нет подов, с которых НРА мог бы собрать метрики, хотя сами метрики в кластере доступны — проверяем:
[simterm]
$ 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" } }
[/simterm]
Добавляем Deployment с именем deployment-example, файл hpa-deployment-example.yaml
:
apiVersion: apps/v1 kind: Deployment metadata: name: deployment-example spec: replicas: 1 strategy: type: RollingUpdate selector: matchLabels: application: deployment-example template: metadata: labels: application: deployment-example spec: containers: - name: deployment-example-pod image: nginx ports: - containerPort: 80 resources: requests: cpu: 100m memory: 100Mi
Тут мы описываем Deployment, который запустит один под с NINGX, которому задаст requests
в 100 millicores и 100 mebibyte (мегабайт) памяти, см. Kubernetes best practices: Resource requests and limits.
Создаём его:
[simterm]
$ kubectl apply -f hpa-deployment-example.yaml deployment.apps/deployment-example created
[/simterm]
Проверяем HPA теперь:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 0%/10% 1 5 1 14m
[/simterm]
Наш НРА увидел деплоймент, и начал собирать с него метрики.
Проверим метрики с конкретного пода — находим его имя:
[simterm]
$ kubectl get pod | grep example | cut -d " " -f 1 deployment-example-86c47f5897-2mzjd
[/simterm]
И выполняем API-запрос:
[simterm]
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/deployment-example-86c47f5897-2mzjd | jq { "kind": "PodMetrics", "apiVersion": "metrics.k8s.io/v1beta1", "metadata": { "name": "deployment-example-86c47f5897-2mzjd", "namespace": "default", "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/deployment-example-86c47f5897-2mzjd", "creationTimestamp": "2020-08-07T10:41:21Z" }, "timestamp": "2020-08-07T10:40:39Z", "window": "30s", "containers": [ { "name": "deployment-example-pod", "usage": { "cpu": "0", "memory": "2496Ki" } } ] }
[/simterm]
Потребление CPU — ноль, памяти — 2 мегабайта — глянем в top
:
[simterm]
$ kubectl top pod deployment-example-86c47f5897-2mzjd NAME CPU(cores) MEMORY(bytes) deployment-example-86c47f5897-2mzjd 0m 2Mi
[/simterm]
«Ага, вот эти ребята!» (с)
Хорошо — метрики видим, НРА есть, деплоймент есть — попробуем посмотреть, как работает скейлинг.
Load testing HorizontalPodAutoscaler scaling
Используем loadimpact/loadgentest-wrk.
Запустим переадресацию портов с локальной машины к поду, что бы получить доступ к нашему NGINX, т.к. LoadBalancer в мир мы не создавали (см. Kubernetes: ClusterIP vs NodePort vs LoadBalancer, Services и Ingress — обзор, примеры):
[simterm]
$ kubectl port-forward deployment-example-86c47f5897-2mzjd 8080:80 Forwarding from 127.0.0.1:8080 -> 80 Forwarding from [::1]:8080 -> 80
[/simterm]
Проверяем ресурсы ещё раз:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 0%/10% 1 5 1 33m
[/simterm]
0% CPU использовано, запущен 1 под (REPLICAS
1).
Запускаем тест:
[simterm]
$ docker run --rm --net=host loadimpact/loadgentest-wrk -c 100 -t 100 -d 5m http://127.0.0.1:8080 Running 5m test @ http://127.0.0.1:8080
[/simterm]
Тут:
- открываем 100 подключений, используя 600 потоков
- выполняем тест 5 минут
Проверяем под:
[simterm]
$ kubectl top pod deployment-example-86c47f5897-2mzjd NAME CPU(cores) MEMORY(bytes) deployment-example-86c47f5897-2mzjd 49m 2Mi
[/simterm]
CPU уже 49mi, в реквестах мы задавали лимит в 10 milicpu — проверяем HPA:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 49%/10% 1 5 4 42m
[/simterm]
TARGETS
49% из лимита в 10% — и НРА запустил новые поды — REPLICAS
4:
[simterm]
$ kubectl get pod | grep example deployment-example-86c47f5897-2mzjd 1/1 Running 0 31m deployment-example-86c47f5897-4ntd4 1/1 Running 0 24s deployment-example-86c47f5897-p7tc7 1/1 Running 0 8s deployment-example-86c47f5897-q49gk 1/1 Running 0 24s deployment-example-86c47f5897-zvdvz 1/1 Running 0 24s
[/simterm]
Multi-metrics scaling
Сейчас наш Deployment скейлится только на основании метрики потребления CPU.
Обновим НРА — добавим возможность скейлится по памяти:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: hpa-example spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: deployment-example minReplicas: 1 maxReplicas: 5 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 10 - type: Resource resource: name: memory targetAverageUtilization: 10
autoscaling API group versions
Давайте ещё раз вернёмся к версиям API.
В первом варианте мы использовали autoscaling/v1, в котором есть поддержка только targetCPUUtilizationPercentage
.
Проверяем autoscaling/v2beta1 — тут уже добавлено поле metrics
, которое явлется массивом MetricSpec, у которого есть уже четыре поля — external
, object
, pods
, resource
.
В свою очередь для resource
имеется объект ResourceMetricSource, у которого имеются поля targetAverageUtilization
и targetAverageValue
, которые мы и используем в mertics
вместо указания targetCPUUtilizationPercentage
.
Применяем изменения в НРА:
[simterm]
$ kubectl apply -f hpa-example.yaml horizontalpodautoscaler.autoscaling/hpa-example configured
[/simterm]
Проверяем:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 3%/10%, 0%/10% 1 5 2 126m
[/simterm]
Теперь в TARGETS
мы видим две метрики — и память, и CPU.
Нагрузить NGINX по памяти будет сложно, потому проверим, сколько он сейчас потребляет памяти:
[simterm]
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/deployment-example-c4d6f96db-jv6nm | jq '.containers[].usage.memory' "2824Ki"
[/simterm]
2 мегабайта.
Изменим наш HPA, и зададим не % от реквеста, а явное ограничение в 1024Ki — 1 мегабайт.
Вместо targetAverageUtilization
используем targetAverageUtilization
:
... metrics: - type: Resource resource: name: cpu targetAverageUtilization: 10 - type: Resource resource: name: memory targetAverageValue: 1024Ki
Обновляем, проверяем:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 2551808/1Mi, 0%/10% 1 5 3 2m8s
[/simterm]
Проверим значение из TARGETS
— переведём в килобайты:
[simterm]
$ echo 2551808/1024 | bc 2492
[/simterm]
И реальное потребление в поде:
[simterm]
$ kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/default/pods/deployment-example-c4d6f96db-fldl2 | jq '.containers[].usage.memory' "2496Ki"
[/simterm]
2492 ~= 2496Ki, окей, с этим разобрались, скейлинг тоже работает.
Custom Metrics
Memory metrics scaling
Помимо использование метрик, которые нам дают API-сервер и cAdvisor
, мы можем использовать любые другие метрики, например — собираемые Prometheus.
Это могут быть метрики из Cloudwatch-експортёра, node_exporter
, или метрики непосредственно из приложения.
Документация тут>>>.
Так как у нас используется Prometehus (см. Kubernetes: мониторинг с Prometheus и Kubernetes: мониторинг кластера с Prometheus Operator) — то используем его адаптер, вместе всё будет выглядеть примерно так:
Если попробовать обратиться к external и custom API-ендпоинтам сейчас — то получим ошибку:
[simterm]
$ kubectl get --raw /apis/custom.metrics.k8s.io/ Error from server (NotFound): the server could not find the requested resource $ kubectl get --raw /apis/external.metrics.k8s.io/ Error from server (NotFound): the server could not find the requested resource
[/simterm]
Устанавливаем адаптер из Helm-чарта:
[simterm]
$ helm install prometheus-adapter stable/prometheus-adapter NAME: prometheus-adapter LAST DEPLOYED: Sat Aug 8 13:27:36 2020 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: prometheus-adapter has been deployed. In a few minutes you should be able to list metrics using the following command(s): kubectl get --raw /apis/custom.metrics.k8s.io/v1beta
[/simterm]
Ждём пару минут, и проверяем API ещё раз:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1" | jq . { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "custom.metrics.k8s.io/v1beta1", "resources": [] }
[/simterm]
Но почему в "resources":[]
пусто?
Посмотрим логи:
[simterm]
$ kubectl logs -f prometheus-adapter-b8945f4d8-q5t6x I0808 10:45:47.706771 1 adapter.go:94] successfully using in-cluster auth E0808 10:45:47.752737 1 provider.go:209] unable to update list of all metrics: unable to fetch metrics for query "{__name__=~\"^container_.*\",container!=\"POD\",namespace!=\"\",pod!=\"\"}": Get http://prometheus.default.svc:9090/api/v1/series?match%5B%5D=%7B__name__%3D~%22%5Econtainer_.%2A%22%2Ccontainer%21%3D%22POD%22%2Cnamespace%21%3D%22%22%2Cpod%21%3D%22%22%7D&start=1596882347.736: dial tcp: lookup prometheus.default.svc on 172.20.0.10:53: no such host I0808 10:45:48.032873 1 serving.go:306] Generated self-signed cert (/tmp/cert/apiserver.crt, /tmp/cert/apiserver.key) ...
[/simterm]
Вот наша ошибка:
dial tcp: lookup prometheus.default.svc on 172.20.0.10:53: no such host
Проверим адрес нашего Prometheus:
[simterm]
$ kubectl exec -ti deployment-example-c4d6f96db-fldl2 curl prometheus-prometheus-oper-prometheus.monitoring.svc.cluster.local:9090/metrics | head -5 # HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 2.0078e-05 go_gc_duration_seconds{quantile="0.25"} 3.8669e-05 go_gc_duration_seconds{quantile="0.5"} 6.956e-05
[/simterm]
Окей — Prometheus доступен по адресу prometheus-prometheus-oper-prometheus.monitoring.svc.cluster.local:9090.
Открываем на редактирование деплоймент:
[simterm]
$ kubectl edit deploy prometheus-adapter
[/simterm]
Обновляем prometheus-url
:
... spec: affinity: {} containers: - args: - /adapter - --secure-port=6443 - --cert-dir=/tmp/cert - --logtostderr=true - --prometheus-url=http://prometheus-prometheus-oper-prometheus.monitoring.svc.cluster.local:9090 ...
Обновляем, и через минуту проверяем:
[simterm]
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . |grep "pods/" | head -5 "name": "pods/node_load15", "name": "pods/go_memstats_next_gc_bytes", "name": "pods/coredns_forward_request_duration_seconds_count", "name": "pods/rest_client_requests", "name": "pods/node_ipvs_incoming_bytes",
[/simterm]
Хорошо — метрики пошли, попробуем их применить в НРА.
Проверим наличие и значение memory_usage_bytes
:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/memory_usage_bytes" | jq . { "kind": "MetricValueList", "apiVersion": "custom.metrics.k8s.io/v1beta1", "metadata": { "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/memory_usage_bytes" }, "items": [ { "describedObject": { "kind": "Pod", "namespace": "default", "name": "deployment-example-c4d6f96db-8tfnw", "apiVersion": "/v1" }, "metricName": "memory_usage_bytes", "timestamp": "2020-08-08T11:18:53Z", "value": "11886592", "selector": null }, ...
[/simterm]
Обновляем НРА:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: hpa-example spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: deployment-example minReplicas: 1 maxReplicas: 5 metrics: - type: Pods pods: metricName: memory_usage_bytes targetAverageValue: 1024000
Проверим значения в HPA сейчас:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 4694016/1Mi, 0%/10% 1 5 5 69m
[/simterm]
Применяем:
[simterm]
$ kubectl apply -f hpa-example.yaml horizontalpodautoscaler.autoscaling/hpa-example configured
[/simterm]
Проверяем ещё раз:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 11853824/1024k 1 5 1 16s
[/simterm]
Реплика всё ещё 1, смотрим логи:
[simterm]
... 43s Normal ScalingReplicaSet Deployment Scaled up replica set deployment-example-c4d6f96db to 1 16s Normal ScalingReplicaSet Deployment Scaled up replica set deployment-example-c4d6f96db to 4 1s Normal ScalingReplicaSet Deployment Scaled up replica set deployment-example-c4d6f96db to 5 16s Normal SuccessfulRescale HorizontalPodAutoscaler New size: 4; reason: pods metric memory_usage_bytes above target 1s Normal SuccessfulRescale HorizontalPodAutoscaler New size: 5; reason: pods metric memory_usage_bytes above target ...
[/simterm]
И реплики заскейлились:
[simterm]
$ kubectl get hpa hpa-example NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE hpa-example Deployment/deployment-example 6996787200m/1024k 1 5 5 104s
[/simterm]
И всё хорошо, пока мы используем готовые метрики, которые уже есть в кластере, например memory_usage_bytes
, собираемая cAdvisor со всех контейнеров.
Попробуем использовать более кастомную метрику, например — скейлить Gorush-сервер на основе его метрик, см. Kubernetes: запуск push-сервера Gorush в EKS за AWS LoadBalancer.
Application-based metrics scaling
У нас имеется Gorush-сервер, который выполняет отправку пуш-уведомлений на мобильные, у которого есть встроенный ендпонт /metrics
, по которому доступны стандартные time-series метрики, которые можно использовать в Prometheus.
Используем Service
, ConfigMap
и Deployment
:
apiVersion: v1 kind: Service metadata: name: gorush labels: app: gorush tier: frontend spec: selector: app: gorush tier: frontend type: ClusterIP ports: - name: gorush protocol: TCP port: 80 targetPort: 8088 --- apiVersion: v1 kind: ConfigMap metadata: name: gorush-config data: stat.engine: memory --- apiVersion: apps/v1 kind: Deployment metadata: name: gorush spec: replicas: 1 selector: matchLabels: app: gorush tier: frontend template: metadata: labels: app: gorush tier: frontend spec: containers: - image: appleboy/gorush name: gorush imagePullPolicy: Always ports: - containerPort: 8088 livenessProbe: httpGet: path: /healthz port: 8088 initialDelaySeconds: 3 periodSeconds: 3 env: - name: GORUSH_STAT_ENGINE valueFrom: configMapKeyRef: name: gorush-config key: stat.engine
Создаём отдельный неймспейс:
[simterm]
$ kubectl create ns eks-dev-1-gorush namespace/eks-dev-1-gorush created
[/simterm]
Создаём сервисы:
[simterm]
$ kubectl -n eks-dev-1-gorush apply -f my-gorush.yaml service/gorush created configmap/gorush-config created deployment.apps/gorush created
[/simterm]
Проверяем поды:
[simterm]
$ kubectl -n eks-dev-1-gorush get pod NAME READY STATUS RESTARTS AGE gorush-5c6775748b-6r54h 1/1 Running 0 83s
[/simterm]
Его сервис:
[simterm]
$ kubectl -n eks-dev-1-gorush get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE gorush ClusterIP 172.20.186.251 <none> 80/TCP 103s
[/simterm]
Пробросим порт к поду:
[simterm]
$ kubectl -n eks-dev-1-gorush port-forward gorush-5c6775748b-6r54h 8088:8088 Forwarding from 127.0.0.1:8088 -> 8088 Forwarding from [::1]:8088 -> 8088
[/simterm]
Проверяем метрики:
[simterm]
$ curl -s localhost:8088/metrics | grep gorush | head # HELP gorush_android_fail Number of android fail count # TYPE gorush_android_fail gauge gorush_android_fail 0 # HELP gorush_android_success Number of android success count # TYPE gorush_android_success gauge gorush_android_success 0 # HELP gorush_ios_error Number of iOS fail count # TYPE gorush_ios_error gauge gorush_ios_error 0 # HELP gorush_ios_success Number of iOS success count
[/simterm]
Либо другим образом: зная имя Service
, мы можем обратиться к нему напрямую.
Находим сервис:
[simterm]
$ kubectl -n eks-dev-1-gorush get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE gorush ClusterIP 172.20.186.251 <none> 80/TCP 26m
[/simterm]
Открываем прокси к API-серверу:
[simterm]
$ kubectl proxy --port=8080 Starting to serve on 127.0.0.1:8080
[/simterm]
Обращаемся напрямую к сервису:
[simterm]
$ curl -sL localhost:8080/api/v1/namespaces/eks-dev-1-gorush/services/gorush:gorush/proxy/metrics | head # HELP go_gc_duration_seconds A summary of the GC invocation durations. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile="0"} 9.194e-06 go_gc_duration_seconds{quantile="0.25"} 1.2092e-05 go_gc_duration_seconds{quantile="0.5"} 2.1812e-05 go_gc_duration_seconds{quantile="0.75"} 5.1794e-05 go_gc_duration_seconds{quantile="1"} 0.000145631 go_gc_duration_seconds_sum 0.001080551 go_gc_duration_seconds_count 32 # HELP go_goroutines Number of goroutines that currently exist.
[/simterm]
Kubernetes ServiceMonitor
Далее, нам надо добавить ServiceMonitor
в Kubernetes, см. Создание Kubernetes ServiceMonitor.
Проверим метрики сейчас — пробросим порт к нашему Prometheus:
[simterm]
$ kk -n monitoring port-forward prometheus-prometheus-prometheus-oper-prometheus-0 9090:9090 Forwarding from [::1]:9090 -> 9090 Forwarding from 127.0.0.1:9090 -> 9090
[/simterm]
Пробуем получить их:
[simterm]
$ curl "localhost:9090/api/v1/series?match[]=gorush_total_push_count&start=1597141864" {"status":"success","data":[]}
[/simterm]
"data":[]
пустая — метрики в Prometheus не собираются.
Описываем ServiceMonitor
:
apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: serviceapp: gorush-servicemonitor release: prometheus name: gorush-servicemonitor namespace: monitoring spec: endpoints: - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token interval: 15s port: gorush namespaceSelector: matchNames: - eks-dev-1-gorush selector: matchLabels: app: gorush
Прим: The Prometheus resource includes a field called serviceMonitorSelector
, which defines a selection of ServiceMonitors to be used. By default and before the version v0.19.0
, ServiceMonitors must be installed in the same namespace as the Prometheus instance. With the Prometheus Operator v0.19.0
and above, ServiceMonitors can be selected outside the Prometheus namespace via the serviceMonitorNamespaceSelector
field of the Prometheus resource.
См. Prometheus Operator
Создаём его:
[simterm]
$ kubectl apply -f ../../gorush-service-monitor.yaml servicemonitor.monitoring.coreos.com/gorush-servicemonitor created
[/simterm]
Проверяем его в таргетах:
UP, отлично.
И через пару минут ещё раз метрики:
[simterm]
$ curl "localhost:9090/api/v1/series?match[]=gorush_total_push_count&start=1597141864" {"status":"success","data":[{"__name__":"gorush_total_push_count","endpoint":"gorush","instance":"10.3.35.14:8088","job":"gorush","namespace":"eks-dev-1-gorush","pod":"gorush-5c6775748b-6r54h","service":"gorush"}]}
[/simterm]
Либо так:
[simterm]
$ curl -s localhost:9090/api/v1/label/__name__/values | jq | grep gorush "gorush_android_fail", "gorush_android_success", "gorush_ios_error", "gorush_ios_success", "gorush_queue_usage", "gorush_total_push_count",
[/simterm]
Хорошо — метрики пошли, добавим HorizontalPodAutoscaler
для этого деплоймента.
Проверяем, какие группы метрик доступны через Custom Metrics API:
[simterm]
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . | grep "gorush" "name": "services/gorush_android_success", "name": "pods/gorush_android_fail", "name": "namespaces/gorush_total_push_count", "name": "namespaces/gorush_queue_usage", "name": "pods/gorush_ios_success", "name": "namespaces/gorush_ios_success", "name": "jobs.batch/gorush_ios_error", "name": "services/gorush_total_push_count", "name": "jobs.batch/gorush_queue_usage", "name": "pods/gorush_queue_usage", "name": "jobs.batch/gorush_android_fail", "name": "services/gorush_queue_usage", "name": "services/gorush_ios_success", "name": "jobs.batch/gorush_android_success", "name": "jobs.batch/gorush_total_push_count", "name": "pods/gorush_ios_error", "name": "pods/gorush_total_push_count", "name": "pods/gorush_android_success", "name": "namespaces/gorush_android_success", "name": "namespaces/gorush_android_fail", "name": "namespaces/gorush_ios_error", "name": "jobs.batch/gorush_ios_success", "name": "services/gorush_ios_error", "name": "services/gorush_android_fail",
[/simterm]
Описываем HPA, который использует gorush_queue_usage
из группы Pods
:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: gorush-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: gorush minReplicas: 1 maxReplicas: 5 metrics: - type: Pods pods: metricName: gorush_total_push_count targetAverageValue: 2
С таким НРА поды должны заскейлится, как только gorush_total_push_count
станет больше 2.
Создаём его:
[simterm]
$ kubectl -n eks-dev-1-gorush apply -f my-gorush.yaml service/gorush unchanged configmap/gorush-config unchanged deployment.apps/gorush unchanged horizontalpodautoscaler.autoscaling/gorush-hpa created
[/simterm]
Проверим значение метрики сейчас:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1/namespaces/eks-dev-1-gorush/pods/*/gorush_total_push_count" | jq '.items[].value' "0"
[/simterm]
Проверяем НРА:
[simterm]
$ kubectl -n eks-dev-1-gorush get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE gorush-hpa Deployment/gorush 0/1 1 5 1 17s
[/simterm]
TARGETS
0/1, хорошо.
Шлём пуш:
[simterm]
$ curl -X POST a6095d18859c849889531cf08baa6bcf-531932299.us-east-2.elb.amazonaws.com/api/push -d '{"notifications":[{"tokens":["990004543798742"],"platform":2,"message":"Hello Android"}]}' {"counts":1,"logs":[],"success":"ok"}
[/simterm]
Ещё раз проверяем метрику:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1/namespaces/eks-dev-1-gorush/pods/*/gorush_total_push_count" | jq '.items[].value' "1"
[/simterm]
Один пуш отправлен.
Глянем НРА:
[simterm]
$ kubectl -n eks-dev-1-gorush get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE gorush-hpa Deployment/gorush 1/2 1 5 1 9m42s
[/simterm]
TARGETS
1/2, REPLICAS
всё ещё одна — шлём второй пуш, и проверяем события:
[simterm]
$ kubectl -n eks-dev-1-gorush get events --watch LAST SEEN TYPE REASON KIND MESSAGE 18s Normal Scheduled Pod Successfully assigned eks-dev-1-gorush/gorush-5c6775748b-x8fjs to ip-10-3-49-200.us-east-2.compute.internal 17s Normal Pulling Pod Pulling image "appleboy/gorush" 17s Normal Pulled Pod Successfully pulled image "appleboy/gorush" 17s Normal Created Pod Created container gorush 17s Normal Started Pod Started container gorush 18s Normal SuccessfulCreate ReplicaSet Created pod: gorush-5c6775748b-x8fjs 18s Normal SuccessfulRescale HorizontalPodAutoscaler New size: 2; reason: pods metric gorush_total_push_count above target 18s Normal ScalingReplicaSet Deployment Scaled up replica set gorush-5c6775748b to 2
[/simterm]
И сам НРА теперь:
[simterm]
$ kubectl -n eks-dev-1-gorush get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE gorush-hpa Deployment/gorush 3/2 1 5 2 10m
[/simterm]
Отлично — скейлинг по метрике gorush_total_push_count
работает.
Но есть нюанс: gorush_total_push_count
— метрика куммулятивная, т.е. на графике Production-окружения будет выглядеть так:
В таком случае — наш НРА будет скейлить вечно.
Prometheus Adapter ConfigMap
— seriesQuery
и metricsQuery
Что бы изменить это — создадим свою метрику.
Prometheus Adapter дня настроек использует ConfigMap
:
[simterm]
$ kubectl get cm prometheus-adapter NAME DATA AGE prometheus-adapter 1 46h
[/simterm]
Который содержит файл config.yaml
, пример файла тут>>>.
Сформируем PromQL-запрос, который вернёт нам значение пушей в секунду:
rate(gorush_total_push_count{instance="push.server.com:80",job="push-server"}[5m])
Обновляем ConfigMap
, и добавляем свой запрос:
apiVersion: v1 data: config.yaml: | rules: - seriesQuery: '{__name__=~"gorush_total_push_count"}' seriesFilters: [] resources: overrides: namespace: resource: namespace pod: resource: pod name: matches: "" as: "gorush_push_per_second" metricsQuery: rate(<<.Series>>{<<.LabelMatchers>>}[5m])
Проверяем:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1/namespaces/eks-dev-1-gorush/pods/*/gorush_push_per_second" | jq Error from server (NotFound): the server could not find the metric gorush_push_per_second for pods
[/simterm]
Пересоздаём под, что бы он обновил у себя ConfigMap
(см. Kubernetes: ConfigMap и Secrets — auto-reload данных в подах):
[simterm]
$ kubectl delete pod prometheus-adapter-7c56787c5c-kllq6 pod "prometheus-adapter-7c56787c5c-kllq6" deleted
[/simterm]
Проверяем:
[simterm]
$ kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1/namespaces/eks-dev-1-gorush/pods/*/gorush_push_per_second" | jq { "kind": "MetricValueList", "apiVersion": "custom.metrics.k8s.io/v1beta1", "metadata": { "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/eks-dev-1-gorush/pods/%2A/gorush_push_per_second" }, "items": [ { "describedObject": { "kind": "Pod", "namespace": "eks-dev-1-gorush", "name": "gorush-5c6775748b-6r54h", "apiVersion": "/v1" }, "metricName": "gorush_push_per_second", "timestamp": "2020-08-11T12:28:03Z", "value": "0", "selector": null }, ...
[/simterm]
Обновим НРА — используем gorush_push_per_second
:
apiVersion: autoscaling/v2beta1 kind: HorizontalPodAutoscaler metadata: name: gorush-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: gorush minReplicas: 1 maxReplicas: 5 metrics: - type: Pods pods: metricName: gorush_push_per_second targetAverageValue: 1m
Проверяем HPA:
[simterm]
$ kubectl -n eks-dev-1-gorush get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE gorush-hpa Deployment/gorush 0/1m 1 5 1 68m
[/simterm]
Проверяем события:
[simterm]
... 0s Normal SuccessfulRescale HorizontalPodAutoscaler New size: 4; reason: pods metric gorush_push_per_second above target 0s Normal ScalingReplicaSet Deployment Scaled up replica set gorush-5c6775748b to 4 ...
[/simterm]
И НРА теперь:
[simterm]
$ kubectl -n eks-dev-1-gorush get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE gorush-hpa Deployment/gorush 11m/1m 1 5 5 70m
[/simterm]
Готово.
Ссылки по теме
- Kubernetes Autoscaling in Production: Best Practices for Cluster Autoscaler, HPA and VPA
- Ultimate Kubernetes Resource Planning Guide
- Horizontal Autoscaling in Kubernetes #2 – Custom Metrics
- Kubernetes HPA : ExternalMetrics+Prometheus
- Prometheus Custom Metrics Adapter
- Horizontal Pod Autoscaling (HPA) triggered by Kafka event
- Custom and external metrics for autoscaling workloads
- Prometheus Metrics Based Autoscaling in Kubernetes
- Kubernetes best practices: Resource requests and limits
- Знакомство с Kubernetes. Часть 19: HorizontalPodAutoscaler
- How to Use Kubernetes for Autoscaling
- Horizontal Pod Autoscaling by memory
- Autoscaling apps on Kubernetes with the Horizontal Pod Autoscaler
- Horizontally autoscale Kubernetes deployments on custom metrics
- Kubernetes pod autoscaler using custom metrics
- Kubernetes HPA Autoscaling with Custom and External Metrics
- Horizontal pod auto scaling by using custom metrics
- Horizontal Pod Autoscale with Custom Prometheus Metrics
- Kubernetes HPA using Custom Metrics
- Kubernetes HPA Autoscaling with Custom Metrics
- Building a K8s Autoscaler with Custom Metrics