Escalando aplicações automaticamente no Kubernetes usando KEDA
Tags: kubernetes, keda
Published: 2021-06-23 13:18:01

Para quem ainda não conhece o [KEDA (Kubernetes Event-Driven Autoscaling)](https://keda.sh/), é:
> Um componente leve e de finalidade única que pode ser adicionado a qualquer cluster do Kubernetes. Funciona junto com componentes Kubernetes padrão, como o Horizontal Pod Autoscaler (HPA) e pode estender a funcionalidade sem sobrescrever ou duplicação.
Retirado da documentação oficial

Foi lançado no fim de 2019 [(anúncio oficial)](https://cloudblogs.microsoft.com/opensource/2019/11/19/keda-1-0-release-kubernetes-based-event-driven-autoscaling/) e é fruto de uma parceria entre Microsoft & Red Hat.

E ele cumpre bem o lema, que é: **“Application autoscaling made simple”**.

Nativamente o Kubernetes só permite configurar [HPA](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) com as métricas de CPU e memória.
Se quiser escalar as aplicações utilizando outro tipo de métrica, por exemplo, lags de eventos ou filas, você precisa primeiro criar um adaptador de métricas (custom metrics) para extrair as métricas da fonte desejada. Entretanto se  precisar obter métricas de várias fontes usando vários adaptadores, você está sem sorte porque apenas um por vez é compatível (a menos que tenha mudado recentemente).

Já o KEDA extrai de uma [variedade de fontes](https://keda.sh/docs/2.3/scalers/) e dimensiona automaticamente suas implantações de `0` a `N-instâncias` com base em sua configuração no ScaledObject.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sav8cxq4lajkb3tfo7bk.jpg)

Outro ponto interessante é que o KEDA não “reinventa a roda” e não construiu seu próprio mecanismo de escalonamento, se aproveitando de HPAs do Kubernetes e dos secrets (**TriggerAuthentication**) já usados pelas aplicações.


## INSTALANDO O KEDA

As instruções para implantar o KEDA são muito simples e podem ser encontradas [aqui](https://keda.sh/docs/2.3/deploy/).

Existem três maneiras de implantar KEDA em seu cluster Kubernetes:
* Helm charts
* Operator Hub
* Implantar YAMLs

Vamos usar a primeira opção.
```
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

kubectl create ns keda
helm install keda kedacore/keda --namespace keda
```

Após a instalação teremos 2 deployments (KEDA Operator e KEDA Metrics API) rodando no cluster…
```
kubectl get deployment -n keda
NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
keda-operator                     1/1     1            1           1h
keda-operator-metrics-apiserver   1/1     1            1           1h
```
e mais alguns CRDs disponíveis:
```
kubectl api-resources --api-group=keda.sh
NAME                     SHORTNAMES       APIGROUP   NAMESPACED   KIND
scaledjobs               sj               keda.sh    true         ScaledJob
scaledobjects            so               keda.sh    true         ScaledObject
triggerauthentications   ta,triggerauth   keda.sh    true         TriggerAuthentication
```

**ScaledJobs*/ScaledObject**: Os ScaledJobs/ScaledObjects mapeiam uma fonte de evento para a jobs/deployments que você deseja dimensionar.
**TriggerAuthentication**: Se necessário, este recurso contém a configuração de autenticação necessária para monitorar a origem do evento.

O "ScaledObject" também cria o HPA para você.

> * O KEDA não apenas dimensiona deployments, mas também pode dimensionar seus jobs do Kubernetes. Em vez de ter muitos eventos processados ​​em sua implantação e aumentar ou diminuir com base no número de mensagens que precisam ser consumidas, o KEDA pode ativar um trabalho para cada mensagem na origem do evento.


## EXEMPLO COM KAFKA

Vamos dar uma olhada mais de perto no [ScaledObject](https://keda.sh/docs/2.3/concepts/scaling-deployments/) e [Kafka trigger](https://keda.sh/docs/2.3/scalers/apache-kafka/).

```
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: kafka-consumer-scaler
  labels:
    deploymentName: my-kafka-consumer-service
  namespace: sample
spec:
  scaleTargetRef:
    deploymentName: my-kafka-consumer-service
  pollingInterval: 1		# Optional. Default: 30 seconds
  cooldownPeriod:  30		# Optional. Default: 300 seconds
  minReplicaCount: 0		# Optional. Default: 0
  maxReplicaCount: 10	# Optional. Default: 100
  triggers:
    - type: kafka
      metadata:
        topic: test-topic-1
        # brokerList: my-cluster-kafka-bootstrap.kafka:9092 - deprecated
        bootstrapServers: my-cluster-kafka-bootstrap.kafka:9092    
        consumerGroup: my-kafka-consumerGroup
        lagThreshold: '5'		# Default: 10
        offsetResetPolicy: latest
        allowIdleConsumers: false
      authenticationRef:
        name: keda-trigger-auth-kafka-credential
    ## Optional: list of topics to trigger
    #- type: kafka
    #   metadata:
    #     topic: test-topic-2
    #     bootstrapServers: my-cluster-kafka-bootstrap.kafka:9092    
    #     consumerGroup: my-kafka-consumerGroup
    #     lagThreshold: '5'		# Default: 10
    #     offsetResetPolicy: latest
    #     allowIdleConsumers: false
    #   authenticationRef:
    #     name: keda-trigger-auth-kafka-credential
```

> O ScaledObject, TriggerAuthentication e a implantação referenciada em deploymentName precisam estar no mesmo namespace.

* Apesar de serem valores opcionais, é importante definir valores dentro dos padrões para seu negócio nos parâmetros `minReplicaCount` e `maxReplicaCount`. Para evitar rebalanceamento de partições no Kafka E/OU evitar que muitos pods sejam iniciados - consumindo todos os recursos do cluster :boom:.
* O parâmetro `offsetResetPolicy` pode ser earliest ou latest. Como o KEDA vai percorrer todos os tópicos, vale a pena entender como o código (negócio) se comporta com duplicidade de eventos. 

> Por padrão, o número de réplicas não excederá o número de partições em um tópico. Ou seja, se maxReplicaCount for definido mais do que o número de partições, o escalonador não vai atingir o valor definido. Caso queira mudar este comportamento, ajuste o parâmetro `allowIdleConsumers` para `true`. Porém, se houver mais número de consumidores do que número de partições em um tópico, o consumidor extra terá que ficar ocioso.

Para facilitar criamos um usuário no Kafka com permissão de somente leitura (`list` e `describe`) em todos os grupos e tópicos e referenciamos no TriggerAuthentication o __secret__ com este usuário:

```
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: keda-trigger-auth-kafka-credential
  namespace: sample
spec:
  secretTargetRef:
  - key: sasl
    name: keda-credentials
    parameter: sasl
  - key: username
    name: keda-credentials
    parameter: username
  - key: password
    name: keda-credentials
    parameter: password
```

## REFERÊNCIAS:
https://keda.sh/docs/2.3/
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
https://cloudblogs.microsoft.com/opensource/2020/04/06/kubernetes-event-driven-autoscaling-keda-cncf-sandbox-project/
https://keda.sh/resources/

## AGRADECIMENTOS

Obrigado à [todos os envolvidos](https://twitter.com/lipekis/status/1407064351010398208?s=20) que me incentivaram a escrever este artigo e revisaram o texto:
* Felipe Lamarão Silva (@lipekis)
* Willian Itiho (@Willian_Itiho)
* Rafael Gomes (@gomex)