Escalando sua aplicação utilizando K8S e Prometheus

Hoje em dia é comum termos a necessidade de trabalhar com várias instâncias de um serviço para atender cargas de trabalho que em determinados momentos uma única pode não ser o suficiente.

O Kubernetes possui o componente HorizontalPodAutoscaler(HPA) que nos permite de forma automática controlar a quantidade de replicas do serviço baseado em métricas, ou seja permite definir o momento em que deve ser adicionado replicas ao seu "pool" de instâncias do seu serviço, evitando assim desperdício de recurso que seria o caso de um gerência estática.

E como podemos escalar baseado em métricas de aplicação?

Por padrão só conseguimos utilizar o HPA com métricas de CPU ou memoria, porém com o Prometheus podemos obter métricas da aplicação que por sua vez serão enviadas para a API Kubernetes.

O que iremos utilizar?

Para seguir artigo será necessário ter instalado o Helm e o nosso cluster que sugiro utilizar opções como Kind, Minikube e afin.

Instalação Prometheus Stack e Adapter

Prometheus Stack

Este é uma coleção de documentos para configuração do seu Prometheus para o Kubernetes, além de já instalar o Grafana que também sera utilizado aqui.

Primeiramente vamos criar uma namespace para nossos serviços de monitoramento

kubectl create namespace monitoring

E em seguida já podemos instalar o Prometheus Stack através do Helm

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring  --set grafana.service.type=NodePort --set prometheus.service.type=NodePort

Pode ser observado que foi adicionado argumentos definindo os tipos de serviço como NodePort tendo em vista facilitar nosso acesso as interfaces fora do cluster, porém em um ambiente de produção o recomendado é utilizar Ingress.

Apos a execução dos comandos é esperado esse resultado ao listar nossos deployments

~$ kubectl get deployments -n monitoring
NAME                                  READY   UP-TO-DATE   AVAILABLE   AGE
prometheus-grafana                    1/1     1            1           19h
prometheus-kube-prometheus-operator   1/1     1            1           19h
prometheus-kube-state-metrics         1/1     1            1           19h

Prometheus Adapter

Esse é o resposável por ser conectar ao nosso Prometheus e obter as metricas coletadas pelo mesmo e envia-las para a API Metricas do Kubernetes disponibilizando o uso das mesmas no nosso HPA.

Crie o arquivo customValues.yaml, pois nele iremos configurar a url para nosso prometheus e configuração adicional para obter métricas requisições por segundo que iremos utilizar logo mais a frente.

prometheus:
  url: "http://prometheus-kube-prometheus-prometheus"
rules:
  custom:
  - seriesQuery: '{container!="POD",namespace!="",pod!=""}'
    resources:
      template: <<.Resource>>
    name:
      matches: "^(.*)_seconds_count"
      as: "${1}_per_second"
    metricsQuery: (sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>))

_A url do prometheus definida é baseada no DNS interno do serviço visto que o mesmo foi também na namespace monitoring _

helm install prometheus-adapter prometheus-community/prometheus-adapter -n monitoring -f customValues.yaml

Instalando nossa aplicação no cluster

Para este exemplo utilizei de uma aplicação SpringBoot+Kotlin que está disponível em GitHub.

Toda a especificação do chart dessa aplicação se encontra em /chart, que segue a seguinte estrutura:

chart/  
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── servicemonitor.yaml
│   └── service.yaml
└── values.yaml

Dentre os yaml's acima o servicemonitor.yaml é responsável por definir a captura de metricas do serviço, que no nosso caso é no path /actuator/prometheus.

Gerando imagem aplicação

Para gerar a imagem da aplicação iremos usar o seguinte comando gradle:

gradle bootBuildImage

Após isso já teremos disponível em docker local a imagem spring-auto-scaling:0.0.1, feito isso poderemos agora intalar nosso chart no cluster.

Instalando via Helm

helm install article chart/

Verificando estado atual do HPA

Podemos fazer isso com kubectl:

~$ kubectl get hpa 
NAME                          REFERENCE                                TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
article-spring-auto-scaling   Deployment/article-spring-auto-scaling   418m/1    1         5         1          65m

A Coluna TARGETS representa a métrica alvo, onde temos 418m(O K8's utiliza 'm' para unidades fracionarias da métrica que nesse caso seria 0,4/1) de 1 como media entre as quantidade de replicas que também se encontra em 1. A carga atual se deve ao fato de termos o Prometheus obtendo as métricas do nosso serviço através do ServiceMonitor.

Incrementando carga

Iremos executar um pod com busybox para fazer chamadas á nossa aplicação em loop, irá tentar gerando uma carga de 10 requisições por segundo.

kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.1; do wget -q -O- http://article-spring-auto-scaling/users/latest; done"

Devemos levar em conta o tempo de resposta da aplicação, pois as chamadas são sequencias e não paralelas, porém já é o suficiente para vermos funcionar.

Eventos HPA

kubectl describe hpa  article-spring-auto-scaling

Como podemos para que para tentar atingir a media máxima de 1 req/s enviou eventos para aumentar o numero replicas para nosso deployment e chegou ao número de 4 replicas sendo 5 o número máximo de replicas permitidas.

Uma excelente boa prática é definição de valores limites, pois não queremos que um serviço escale infinitamente dentro nosso cluster.

Conclusão

Podemos ver que o escalonamento de automático de aplicações é grande aliado, juntamente com a possibilidade de usar métricas customizadas que podem estar bem alinhadas a necessidade do negócio.As configurações e definições de escalonamento também devem ser constantemente monitoradas e refinidas, pois os requisitos sempre mudam e afinal não existe bala de prata.

20