Kubernetes - Автоскейлинг приложений при помощи Prometheus и KEDA

Thank you for reading this post, don't forget to subscribe!

1. Общие сведения
2. Уста­нов­ка Keda
3. При­мер работы

Kubernetes поз­во­ля­ет авто­ма­ти­че­ски мас­шта­би­ро­вать при­ло­же­ния (то есть Pod в раз­вер­ты­ва­нии или ReplicaSet) декла­ра­тив­ным обра­зом с исполь­зо­ва­ни­ем спе­ци­фи­ка­ции Horizontal Pod Autoscaler.

По умол­ча­нию кри­те­рий для авто­ма­ти­че­ско­го мас­шта­би­ро­ва­ния — мет­ри­ки исполь­зо­ва­ния CPU (мет­ри­ки ресур­сов), но мож­но инте­гри­ро­вать поль­зо­ва­тель­ские мет­ри­ки и мет­ри­ки, предо­став­ля­е­мые извне.

Вме­сто гори­зон­таль­но­го авто­мас­шта­би­ро­ва­ния подов, при­ме­ня­ет­ся Kubernetes Event Driven Autoscaling (KEDA) — опе­ра­тор Kubernetes с откры­тым исход­ным кодом. Он изна­чаль­но инте­гри­ру­ет­ся с Horizontal Pod Autoscaler, что­бы обес­пе­чить плав­ное авто­мас­шта­би­ро­ва­ние (в том чис­ле до/от нуля) для управ­ля­е­мых собы­ти­я­ми рабо­чих нагру­зок. Код досту­пен на GitHub.

Краткий обзор работы системы

На схе­ме — крат­кое опи­са­ние того, как все работает:

  1. При­ло­же­ние предо­став­ля­ет мет­ри­ки коли­че­ства обра­ще­ний к HTTP в фор­ма­те Prometheus.
  2. Prometheus настро­ен на сбор этих показателей.
  3. Скей­лер Prometheus в KEDA настро­ен на авто­ма­ти­че­ское мас­шта­би­ро­ва­ние при­ло­же­ния на осно­ве коли­че­ства обра­ще­ний к HTTP.

Теперь подроб­но рас­ска­жу о каж­дом элементе.

KEDA и Prometheus

Prometheus — набор инстру­мен­тов для мони­то­рин­га и опо­ве­ще­ния систем с откры­тым исход­ным кодом, часть Cloud Native Computing Foundation. Соби­ра­ет мет­ри­ки из раз­ных источ­ни­ков и сохра­ня­ет в виде дан­ных вре­мен­ных рядов. Для визу­а­ли­за­ции дан­ных мож­но исполь­зо­вать Grafana или дру­гие инстру­мен­ты визу­а­ли­за­ции, рабо­та­ю­щие с API Kubernetes.

KEDA под­дер­жи­ва­ет кон­цеп­цию скей­ле­ра — он дей­ству­ет как мост меж­ду KEDA и внеш­ней систе­мой. Реа­ли­за­ция скей­ле­ра спе­ци­фич­на для каж­дой целе­вой систе­мы и извле­ка­ет из нее дан­ные. Затем KEDA исполь­зу­ет их для управ­ле­ния авто­ма­ти­че­ским масштабированием.

Скей­ле­ры под­дер­жи­ва­ют несколь­ких источ­ни­ков дан­ных, напри­мер, Kafka, Redis, Prometheus. То есть KEDA мож­но при­ме­нять для авто­ма­ти­че­ско­го мас­шта­би­ро­ва­ния раз­вер­ты­ва­ний Kubernetes, исполь­зуя в каче­стве кри­те­ри­ев мет­ри­ки Prometheus.

KEDA Prometheus ScaledObject

Скей­лер дей­ству­ет как мост меж­ду KEDA и внеш­ней систе­мой, из кото­рой нуж­но полу­чать мет­ри­ки. ScaledObject — настра­и­ва­е­мый ресурс, его необ­хо­ди­мо раз­вер­нуть для син­хро­ни­за­ции раз­вер­ты­ва­ния с источ­ни­ком собы­тий, в дан­ном слу­чае с Prometheus.

ScaledObject содер­жит инфор­ма­цию о мас­шта­би­ро­ва­нии раз­вер­ты­ва­ния, мета­дан­ные об источ­ни­ке собы­тия (напри­мер, сек­ре­ты для под­клю­че­ния, имя оче­ре­ди), интер­вал опро­са, пери­од вос­ста­нов­ле­ния и дру­гие дан­ные. Он при­во­дит к соот­вет­ству­ю­ще­му ресур­су авто­мас­шта­би­ро­ва­ния (опре­де­ле­ние HPA) для мас­шта­би­ро­ва­ния развертывания.

Когда объ­ект ScaledObject уда­ля­ет­ся, соот­вет­ству­ю­щее ему опре­де­ле­ние HPA очищается.

Вот опре­де­ле­ние ScaledObject для наше­го при­ме­ра, в нем исполь­зу­ет­ся скей­лер Prometheus:

Учти­те сле­ду­ю­щие моменты:

  1. Он ука­зы­ва­ет на Deployment с име­нем go-prom-app.
  2. Тип триг­ге­ра — Prometheus. Адрес сер­ве­ра Prometheus упо­ми­на­ет­ся вме­сте с име­нем мет­ри­ки, поро­го­вым зна­че­ни­ем и запро­сом PromQL, кото­рый будет исполь­зо­вать­ся. Запрос PromQL — sum(rate(http_requests[2m])).
  3. Соглас­но pollingInterval, KEDA запра­ши­ва­ет цель у Prometheus каж­дые пят­на­дцать секунд. Под­дер­жи­ва­ет­ся мини­мум один под (minReplicaCount), а мак­си­маль­ное коли­че­ство подов не пре­вы­ша­ет maxReplicaCount (в дан­ном при­ме­ре — десять).

Мож­но уста­но­вить minReplicaCount рав­ным нулю. В этом слу­чае KEDA акти­ви­ру­ет раз­вер­ты­ва­ние с нуля до еди­ни­цы, а затем предо­став­ля­ет HPA для даль­ней­ше­го авто­ма­ти­че­ско­го мас­шта­би­ро­ва­ния. Воз­мо­жен и обрат­ный поря­док, то есть мас­шта­би­ро­ва­ние от еди­ни­цы до нуля. В при­ме­ре мы не выбра­ли ноль, посколь­ку это HTTP-сер­вис, а не систе­ма по запросу.

Магия внутри автомасштабирования

Поро­го­вое зна­че­ние исполь­зу­ют в каче­стве триг­ге­ра для мас­шта­би­ро­ва­ния раз­вер­ты­ва­ния. В нашем при­ме­ре запрос PromQL sum(rate (http_requests [2m])) воз­вра­ща­ет агре­ги­ро­ван­ное зна­че­ние ско­ро­сти HTTP-запро­сов (коли­че­ство запро­сов в секун­ду), ее изме­ря­ют за послед­ние две минуты.

Посколь­ку поро­го­вое зна­че­ние рав­но трем, зна­чит, будет один под, пока зна­че­ние sum(rate (http_requests [2m])) мень­ше трех. Если же зна­че­ние воз­рас­та­ет, добав­ля­ет­ся допол­ни­тель­ный под каж­дый раз, когда sum(rate (http_requests [2m])) уве­ли­чи­ва­ет­ся на три. Напри­мер, если зна­че­ние от 12 до 14, то коли­че­ство подов — четыре.

Теперь давай­те попро­бу­ем настроить!

 

Установка KEDA

Вы може­те раз­вер­нуть KEDA несколь­ки­ми спо­со­ба­ми, они пере­чис­ле­ны в доку­мен­та­ции. Я исполь­зую моно­лит­ный YAML:

[root@kub-master-1 ~]# wget https://github.com/kedacore/keda/releases/download/v2.1.0/keda-2.1.0.yaml
[root@kub-master-1 ~]# kubectl apply -f keda-2.1.0.yaml

ну или мож­но уста­но­вить через helm

helm repo add kedacore https://kedacore.github.io/charts
helm repo update
kubectl create namespace keda
helm install keda kedacore/keda --namespace keda

я ста­вил через моно­лит­ный файл.
про­ве­рим что всё поднялось:

 

3. Пример работы

созда­ём namespace

kubectl create ns my-site

запус­ка­ем обыч­ное при­ло­же­ние напри­мер apache:

[root@kub-master-1 ~]# cat my-site.yaml

[root@kub-master-1 ~]# cat my-site-service.yaml

[root@kub-master-1 ~]# cat my-site-ingress.yaml

при­ме­ня­ем:

[root@kub-master-1 ~]# kubectl apply -f my-site.yaml -f my-site-service.yaml -f my-site-ingress.yaml

про­ве­ря­ем:

[root@kub-worker-1 ~]# curl test.ru
<html><body><h1>It works!</h1></body></html>

[root@kub-master-1 ~]# kubectl get all -n my-site

 

будем автос­кей­лить - для при­ме­ра по мет­ри­ке nginx nginx_ingress_controller_requests

запрос в prometheus будет следующий:

sum(irate( nginx_ingress_controller_requests{namespace="my-site"}[3m] )) by (ingress)*10

т.е. счи­та­ем общее коли­че­ство запро­сов в нейм­с­пейс my-site за 3 минуты

созда­ём keda сущность:

[root@kub-master-1 ~]# cat hpa-keda.yaml

тут мы ука­зы­ва­ем в каком namespace нам запускаться:
namespace: my-site

ука­зы­ва­ем цель, т.е. наш deployment:
name: my-deployment-apache

зада­ём мини­маль­ное и мак­си­маль­ное коли­че­ство реплик
minReplicaCount: 1 # зна­че­ние по умол­ча­нию: 0
maxReplicaCount: 8 # зна­че­ние по умол­ча­нию: 100

есть ещё 2 стан­дарт­ные пере­мен­ные отве­ча­ю­щие за то когда поды будут поды­мать­ся и убиваться:
pollingInterval: 30 # Optional. Default: 30 seconds
cooldownPeriod: 300 # Optional. Default: 300 seconds

ука­зы­ва­ем адрес наше­го prometheus

serverAddress: http://prometheus-kube-prometheus-prometheus.monitoring.svc.cluster.local:9090
адрес идёт в виде сервис.неймспейс.svc.имя_кластера

ука­зы­ва­ем нашу метрику:
metricName: nginx_ingress_controller_requests

ука­зы­ва­ем поро­го­вое зна­че­ние при кото­ром нач­нёт­ся автоскейлинг:
threshold: '100'

и соот­вет­ствен­но наш запрос в prometheus:
query:

всё мож­но применять:

[root@kub-master-1 ~]# kubectl apply -f hpa-keda.yaml

про­ве­ря­ем:

а теперь накру­тим запросов:

[root@kub-worker-1 ~]# for i in {1..5000}; do curl test.ru; done

про­ве­ря­ем:

как видим коли­че­ство запро­сов пре­вы­си­ло наш лимит и ста­ли созда­вать­ся новые поды:

пре­кра­ща­ем запро­сы и спу­стя 5 минут, ука­зан­ное в пере­мен­ной cooldownPeriod ненуж­ные поды будут убиты: