Adicione o texto do seu título aqui

Escalando seus Microsserviços no Kubernetes com Eventos: Apresentando o KEDA!

Escrito por: Jean Carlos M. da Silva

E aí, galera! Quem nunca se viu naquela sinuca de bico tentando fazer o Kubernetes escalar suas aplicações direitinho, né? A gente configura o HPA (Horizontal Pod Autoscaler) pra ficar de olho na CPU e memória, mas aí vem aquele pico de mensagens na fila e… Sua aplicação não reage rápido o suficiente, ou pior, fica gastando recursos à toa quando não tem nada pra processar.

O HPA é uma ferramenta fantástica, mas escalar apenas com base em CPU e memória nem sempre é o cenário ideal, principalmente para as aplicações modernas que são cada vez mais orientadas a eventos. Por padrão, o HPA foca apenas em métricas de CPU e memória, e para usar métricas customizadas, é preciso uma configuração adicional, como um Prometheus Adapter. Além disso, as decisões de escalonamento podem ser mais lentas em picos de tráfego repentinos, e ele não ajusta os recursos por pod, o que pode levar a desperdício.

Mas e se a gente pudesse dizer pro Kubernetes: “Olha, escale essa aplicação aqui baseado em quantos itens tem naquela fila do RabbitMQ, ou quantos eventos chegaram no Kafka”? Isso não seria demais?

Esse questionamento nos leva a uma mudança de paradigma: o problema não é só a capacidade de escalar, mas a inteligência e a relevância desse escalonamento.

Escalar por CPU/memória é uma reação a sintomas (alto uso de recursos), enquanto escalar por eventos é uma reação muito mais próxima à causa raiz da carga (a demanda real do negócio). Em sistemas orientados a eventos, a carga é frequentemente representada pelo volume de eventos.

Há um delay natural entre o aumento no volume de eventos e o reflexo disso no consumo de CPU/memória, o que pode tornar o escalonamento tradicional puramente reativo e, por vezes, tardio. O KEDA, como veremos, propõe um escalonamento mais diretamente correlacionado com essa carga real, observando a fonte do evento. Isso permite decisões mais rápidas, precisas e, crucialmente, abre a porta para o “scale-to-zero”, otimizando custos de forma significativa.

Relembrando: Arquitetura Orientada a Eventos (EDAs) e o Poder dos Eventos de Domínio

No nosso último papo, exploramos o universo das Arquiteturas Orientadas a Eventos (EDAs) e como os CloudEvents nos ajudam a padronizar essa comunicação. Se você perdeu, dá uma olhada lá na newsletter!

Uma EDA é um padrão onde os componentes de um sistema se comunicam e reagem a eventos de forma assíncrona, promovendo baixo acoplamento e facilitando a escalabilidade, já que cada componente pode ser escalado independentemente.

As EDAs são um pilar fundamental para a Transformação Digital (DX), pois permitem que os sistemas reajam a ocorrências do mundo real de forma ágil, melhorando as operações e tornando-as mais escaláveis. Eventos representam fatos que aconteceram no negócio, e reagir a eles em tempo real é crucial para a agilidade e inovação que a DX exige.

Falando em eventos, não podemos esquecer do Domain-Driven Design (DDD)! Em DDD, modelamos o software em torno do domínio do negócio. Isso envolve entender os domínios (as áreas de problema que o software resolve) e os subdomínios (partes menores e mais especializadas desses domínios). O mais importante para nossa conversa aqui são os Eventos de Domínio.

Pensem neles como coisas significativas que acontecem no seu negócio: “PedidoCriado”, “PagamentoProcessado”, “EstoqueAtualizado”… sacou? São esses eventos que, numa EDA, disparam ações e fluxos. A EDA se alinha perfeitamente com o DDD, especialmente em sistemas complexos, fornecendo uma maneira flexível e escalável de lidar com a comunicação entre diferentes contextos delimitados (bounded contexts), garantindo que os eventos de domínio sejam propagados eficientemente enquanto os serviços permanecem desacoplados.

Os Eventos de Domínio, identificados através do DDD, não são apenas artefatos técnicos; eles são a representação digital das ocorrências de negócio. Em uma EDA, eles se tornam o principal mecanismo de integração e orquestração. Isso permite que a arquitetura do sistema evolua em compasso com as necessidades do negócio, que é a essência da DX.

Mudar ou adicionar uma nova funcionalidade de negócio pode se traduzir em introduzir novos eventos ou novos consumidores para eventos existentes, sem necessariamente reescrever grandes partes do sistema, graças ao desacoplamento inerente da EDA. Assim, a TI se torna um habilitador ágil da estratégia de negócios, e os eventos de domínio são a “cola” que permite essa flexibilidade.

KEDA: O Super-Herói do Escalonamento por Eventos no Kubernetes!

E é aqui que entra o KEDA (Kubernetes Event-Driven Autoscaler)! Pensem nele como um especialista que ensina o HPA a entender uma linguagem totalmente nova: a linguagem dos eventos!

O KEDA é um componente leve e de propósito único que pode ser adicionado a qualquer cluster Kubernetes. Ele funciona junto com o HPA padrão do Kubernetes, estendendo suas capacidades sem substituí-lo ou duplicar funcionalidades.

Em alto nível, o KEDA monitora diversas fontes de eventos (filas, tópicos de mensagens, bancos de dados, etc.) e, com base na quantidade de eventos que precisam ser processados, “avisa” o HPA para escalar os pods para cima ou para baixo. E o mais legal? Ele pode escalar suas aplicações até ZERO réplicas quando não há eventos para processar, e acordá-las quando os eventos chegam!

O KEDA não é um novato qualquer! Ele foi criado em Maio de 2019 como uma parceria entre a Microsoft e a Red Hat.

A comunidade open source abraçou a ideia com força: o KEDA entrou para a CNCF (Cloud Native Computing Foundation) como um projeto Sandbox em Março de 2020, foi promovido a projeto Incubating em Agosto de 2021. E alcançou o prestigioso status de Graduado em Agosto de 2023. Essa trajetória na CNCF, que o coloca ao lado de projetos como o próprio Kubernetes e Prometheus, demonstra a maturidade, robustez, adoção e governança sólida do projeto.

Os principais componentes do KEDA, de forma simples, são:

  • Scalers: São os “tradutores” do KEDA. Cada scaler é especializado em “conversar” com uma fonte de evento específica (ex: RabbitMQ, Kafka, Azure Service Bus, Prometheus, AWS SQS, Google Cloud Pub/Sub, e muitos outros). O KEDA possui um catálogo com mais de 70 scalers integrados!
  • Metrics Adapter (Servidor de Métricas): Este componente expõe as métricas coletadas pelos scalers para o HPA do Kubernetes, permitindo que o HPA tome as decisões de escalonamento.
  • Controller/Operator: É o cérebro que gerencia os ScaledObjects (um Custom Resource Definition que define como escalar uma aplicação) e coordena todo o processo de escalonamento.

Mas por que usar KEDA e não apenas o HPA com Métricas Customizadas? Embora o HPA possa ser configurado para usar métricas customizadas (por exemplo, através de um Prometheus adapter), essa configuração pode ser complexa e exigir um conhecimento mais aprofundado da ferramenta de métricas.

O KEDA simplifica enormemente esse processo para uma vasta gama de fontes de eventos comuns, graças aos seus scalers pré-construídos. E, claro, a funcionalidade de SCALE-TO-ZERO é a grande virada de chave que o HPA sozinho não oferece (ele escala, no mínimo, até 1 pod). Com o KEDA, suas aplicações podem “dormir” de verdade quando não há trabalho a ser feito, resultando em uma economia significativa de recursos e, consequentemente, de custos!

Ao abstrair a complexidade da coleta de métricas de diversas fontes de eventos e ao integrar-se nativamente com o HPA, o KEDA torna o escalonamento sofisticado e orientado a eventos acessível a um público de desenvolvedores muito mais amplo, não apenas para especialistas em Kubernetes. A funcionalidade de “scale-to-zero” é a que mais materializa essa democratização em termos de eficiência de custos, especialmente para cargas de trabalho intermitentes. Isso reduz a curva de aprendizado e o esforço de configuração, permitindo que mais equipes otimizem seus workloads.

Vamos por a mão na massa: KEDA + RabbitMQ +.NET no Kubernetes

Chega de papo, vamos botar a mão na massa! Vou mostrar como é simples configurar um ambiente local com KIND, instalar o KEDA, RabbitMQ e uma aplicações C#/.NET consumidora que escala conforme a fila do RabbitMQ enche. Bora?

🚨 Aviso Importante: Lembrem-se, pessoal: este é um exemplo didático para aprendizado! Para ambientes de produção, precisamos considerar segurança (senhas fortes, network policies), persistência de dados do RabbitMQ, otimizações de recursos, logging, monitoramento e muito mais. Mas para entender o KEDA, isso aqui é ouro!

Pré-requisito:

  1. Instalação do KIND
  2. Instalação do Helm

Passo 1: Preparando o Terreno com KIND (Kubernetes IN Docker)

KIND é uma ferramenta fantástica para rodar clusters Kubernetes locais usando containers Docker. Perfeito para desenvolvimento e testes! Vamos criar nosso primeiro cluster com os comandos abaixo:

kind create cluster --name cncf-keda

Este comando cria um cluster chamado cncf-keda, usando a configuração padrão do KIND, que já inclui um CNI (Container Network Interface) básico (kindnet).

Precisamos configurar o “Metrics Server” do Kubernetes pois por padrão não vem habilitado no KIND e vamos desabilitar o tls pois estamos rodando localmente !! (não façam isso em produção rs)

helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm repo update
helm upgrade --install --set args={--kubelet-insecure-tls} metrics-server metrics-server/metrics-server --namespace kube-system

Passo 2: Instalando o KEDA via Helm

Com nosso cluster no ar, vamos instalar o KEDA. Helm facilita muito nossa vida aqui!

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

Passo 3: Subindo o RabbitMQ no Cluster via Helm

Agora, nosso mensageiro! Vamos usar o RabbitMQ. E adivinha? Helm de novo! Usaremos o chart do Bitnami, que é popular e ótimo para demonstrações.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install rabbitmq bitnami/rabbitmq --namespace rabbitmq --create-namespace \
  --set auth.username=user \
  --set auth.password='PASSWORD_SUPER_SECRETA_NAO_USE_EM_PROD' \
  --set persistence.enabled=false

Em produção utilize senhas fortes gerenciadas por secrets/valts e habilite a persistência.

Passo 4: Nossas aplicações de Pub/Sub em C#/.NET

Nosso Consumer:

Iremos construir um background service em C#/.NET 9 que irá consumir uma mensagem simples em texto, simular o processamento com delay de 1 segundo e finalizar.

public class Consumer(ILogger<Consumer> logger, IBus bus) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
         await bus.Advanced.QueueDeclareAsync("cncf-keda-samples", cancellationToken: stoppingToken);

        await bus.SendReceive.ReceiveAsync<string>("cncf-keda-samples", async (message) =>
        {
            logger.LogInformation("Mensagem recebida: {Message}", message);

            //     // Simula um processamento de mensagem que leva 1 segundo
            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);

            logger.LogInformation("Mensagem processada: {Message}", message);
        }, stoppingToken);

        // Mantém o serviço ativo para receber mensagens
        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
}

Nosso Producer:

Nosso producer, ficará publicando de 100 em 100 mensagens simples dem texto a cada 1 minuto.

public class Producer(IBus bus) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await bus.Advanced.QueueDeclareAsync("cncf-keda-samples", cancellationToken: stoppingToken);

        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.WhenAll(
                Enumerable.Range(0, 100)
                    .Select(async (x) =>  await bus.SendReceive.SendAsync("cncf-keda-samples", $"Ola, KEDA! id da mensagem: {x}", stoppingToken)));

            // Publica 1k de mensagens a cada minuto
            await Task.Delay(TimeSpan.FromMinutes(1),stoppingToken);
        }
    }
}

Estamos usando a biblioteca EasyNetQ para simplificar o processo.

Agora basta criar os Dockerfiles das aplicações e subir em seu registry favorito ! já fiz isso e vocês podem utilizar as minhas imagens publicas como exemplo:

jcmds/cloudnative.araraquara.keda.consumer:20250506.2 
jcmds/cloudnative.araraquara.keda.producer:20250506.2 

Passo 5: A Mágica do KEDA em Ação – Deployment e ScaledObject

Com as imagens registradas, vamos para nosso cluster Kubernetes !

Vamos criar um Deployment para cada imagem e um ScaledObject para nosso consumer.

Primeiramente crie um arquivo chamado k8s-manifests.yaml e lá vamos criar o namespace onde nossa aplicação irá executar o nome sera cncf-arq ( de cloud native araraquara rs)

apiVersion: v1
kind: Namespace
metadata:
  name: cncf-arq

Agora vamos criar o Deployment que represe

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cncf-arq-csm
  namespace: cncf-arq
  labels:
    app: cncf-arq-csm
    version: 1.0.0
    type: consumer
    language: dotnet
spec:
  selector:
    matchLabels:
      app: cncf-arq-csm
      version: 1.0.0
      type: consumer
      language: dotnet
  template:
    metadata:
      labels:
        app: cncf-arq-csm
        version: 1.0.0
        type: consumer
        language: dotnet
    spec:
      containers:
      - name: consumer
        image: jcmds/cloudnative.araraquara.keda.consumer:20250506.2 # Substitua pela sua imagem
        imagePullPolicy: IfNotPresent
        env:
          - name: ConnectionStrings__RabbitMQ
            value: "amqp://user:PASSWORD_SUPER_SECRETA_NAO_USE_EM_PROD@rabbitmq.rabbitmq.svc.cluster.local:5672"
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi" 
        terminationMessagePath: /termination-log
        terminationMessagePolicy: File 
      restartPolicy: Always
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25% 

Ponto importante ! vamos focar apenas no campo metadata.name que iremos utilizar no KEDA para referênciar nosso deployment, demais campos dizem respeito a quantidade de cpu/memória necessários para aplicação, limites de consumo, imagem “docker” que iremos utilizar a váriavel de ambiente onde está a ConnectionString do RabbitMQ

O rabbitmq.rabbitmq.svc.cluster.local é o DNS interno do nosso rabbitmq dentro do kubernetes, sendo composto pelo seguinte formato: {service-name}.{namespace}.svc.cluster.local

Agora vamos criar o Deployment do nosso Producer, similar ao do Consumer porém referência outra imagem e tem algumas “labels” a mais para auxiliar na identificação do recurso.

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cncf-arq-prc
  namespace: cncf-arq
  labels:
    app: cncf-arq-prc
    version: 1.0.0
    type: producer
    language: dotnet
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cncf-arq-prc
      version: 1.0.0
      type: producer
      language: dotnet
  template:
    metadata:
      labels:
        app: cncf-arq-prc
        version: 1.0.0
        type: producer
        language: dotnet
    spec:
      containers:
        - name: producer
          image: jcmds/cloudnative.araraquara.keda.producer:20250506.2 # Substitua pela sua imagem 
          imagePullPolicy: IfNotPresent
          env:
            - name: ConnectionStrings__RabbitMQ
              value: "amqp://user:PASSWORD_SUPER_SECRETA_NAO_USE_EM_PROD@rabbitmq.rabbitmq.svc.cluster.local:5672"
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi" 
          terminationMessagePath: /termination-log
          terminationMessagePolicy: File 
      restartPolicy: Always
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%

Agora vamos ao principal ! nosso ScaledObject, o responsável pela mágica

---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: cncf-arq-csm-scaledobject
  namespace: cncf-arq
spec:
  scaleTargetRef:
    name: cncf-arq-csm
  pollingInterval: 15  # Intervalo de verificação em segundos
  cooldownPeriod: 30   # Período de cooldown em segundos
  minReplicaCount: 0   # Mínimo de réplicas
  maxReplicaCount: 5   # Máximo de réplicas
  idleReplicaCount: 0  # 
  triggers:
  - type: rabbitmq
    metadata:
      queueName: "cncf-keda-samples"      # Nome da fila que você está usando
      mode: "QueueLength"                  # Modo de scaling baseado no tamanho da fila
      value: "5"                          # Número de mensagens por réplica
      host: "amqp://user:PASSWORD_SUPER_SECRETA_NAO_USE_EM_PROD@rabbitmq.rabbitmq.svc.cluster.local:5672" 

Aqui, o scaleTargetRef.name aponta para o nosso Deployment, o pollingInterval define a frequência com que o KEDA verifica a fonte do evento.

O cooldownPeriod é o tempo que o KEDA espera após a última atividade antes de escalar para zero. Já os campos minReplicaCount: 0 e maxReplicaCount: 5 definem os limites de escalonamento. E o coração de tudo para nossa estratégia de “scale-to-zero” o idleReplicaCount, se esta propriedade estiver definida, o KEDA reduzirá o recurso para este número de réplicas. Se houver alguma atividade nos gatilhos de destino, o KEDA reduzirá o recurso de destino imediatamente para minReplicaCount e, em seguida, o escalonamento será gerenciado pelo HPA. Quando não houver atividade, o recurso de destino será novamente reduzido para idleReplicaCount. Esta configuração deve ser menor ou igual que minReplicaCount.

Passo 6: Testando o Escalonamento! 🚀

Vamos aplicar nosso arquivo k8s-manifests.yaml com o seguinte comando:

kubectl apply -f k8s-manifests.yaml

Assim que o pod do producer estiver “ready” você verá a mágica acontecer, mensagens sendo populadas no rabbitmq e os pods do consumer sendo criados.

Podemos acompanhar as mensagens no rabbitmq, via UI adm com o seguinte comando no terminal:

kubectl port-forward svc/rabbitmq 15672:15672 5672:5672 --namespace rabbitmq

Existem maneiras de se expor “NodePorts” do cluster KIND diretamente na sua maquina local, mas o papo hoje não é sobre KIND (excelente ferramenta por sinal) então vamos no modo “simples” por enquanto rs.

Podemos acompanhar os pods “nascendo” e “morrendo” conforme a demanda através do comando:

 kubectl get pods -w

Você verá o KEDA detectar as mensagens, o HPA (gerenciado pelo KEDA) entrar em ação, e os pods do consumer começarem a surgir para processar a carga.

Depois que as mensagens forem processadas e a fila esvaziar, aguarde o cooldownPeriod. Os pods deverão ser terminados, e sua aplicação voltará a 0 réplicas. Economia na veia!

Ver os pods escalarem de zero para N e de volta para zero em resposta direta à carga de eventos é uma demonstração poderosa e tangível dos benefícios do KEDA. Para quem está começando, essa visualização “ao vivo” solidifica a compreensão de forma muito mais eficaz do que apenas ler sobre a teoria.

Analisando os Prós e Contras: O Que Ponderar ao Usar KEDA

KEDA é incrível, mas como toda ferramenta poderosa, é bom conhecer os detalhes.

Vantagens (Recapitulando):

  • Scale-to-Zero: Economia de custos absurda, especialmente para workloads intermitentes!.
  • Escalonamento Reativo a Eventos Reais: Mais eficiente e preciso do que escalar apenas com base em métricas de CPU/Memória, pois reage diretamente à demanda.
  • Extensa Lista de Scalers: Suporte para uma enorme variedade de fontes de eventos (mais de 70!).
  • Simplicidade de Configuração: Para os casos de uso mais comuns com scalers existentes, a configuração via ScaledObject é bem direta.
  • Comunidade Forte e Projeto Graduado CNCF: Garante confiança, suporte contínuo e evolução do projeto.

Desafios e Considerações (Trade-offs):

  • Cold Starts: Se sua aplicação escala para zero, a primeira requisição ou evento após um período de inatividade pode enfrentar uma latência maior enquanto o primeiro pod sobe (“cold start”). É crucial avaliar se essa latência inicial é aceitável para o seu caso de uso específico.
  • Escolha de Métricas e Thresholds: Definir o value (threshold) correto no ScaledObject (ex: queueLength de “5” no nosso exemplo), o pollingInterval e o cooldownPeriod exige um bom entendimento do seu workload e pode precisar de ajustes finos e experimentação. Uma configuração inadequada pode levar a um escalonamento excessivo (gastando mais recursos) ou insuficiente (degradando a performance). É fundamental escolher a métrica de gatilho correta que realmente represente a carga da sua aplicação.
  • Complexidade em Cenários Avançados: Para múltiplos triggers complexos, combinações de métricas ou a necessidade de desenvolver scalers customizados, a configuração e o gerenciamento podem se tornar mais elaborados.
  • Monitoramento é Fundamental: É crucial monitorar o comportamento do KEDA, dos seus scalers e das aplicações escaladas para garantir que tudo está funcionando como esperado e para otimizar as configurações ao longo do tempo.Ferramentas como Prometheus e Grafana são suas amigas aqui.
  • Overhead (Mínimo, mas existe): KEDA adiciona alguns componentes ao seu cluster Kubernetes (o operator do KEDA, o metrics adapter). Em clusters muito pequenos ou com recursos extremamente limitados, isso pode ser uma consideração, embora geralmente o overhead seja considerado baixo para a maioria dos cenários.

Embora o KEDA simplifique drasticamente o escalonamento orientado a eventos, ele não elimina a necessidade de entender o comportamento da sua aplicação e das fontes de eventos. A eficácia do KEDA está diretamente ligada à qualidade da configuração do ScaledObject e ao monitoramento contínuo. As equipes precisam investir tempo em analisar o perfil de carga, entender a semântica das métricas das fontes de eventos, testar e iterar nas configurações.

KEDA é uma ferramenta que capacita, mas não substitui o design e a análise cuidadosa do sistema.

Conclusão: Rumo a um Futuro Mais Escalável e Orientado a Eventos! 🏁

Dominar o escalonamento orientado a eventos com KEDA é um grande passo para construir sistemas verdadeiramente modernos, resilientes e eficientes em custo no Kubernetes. É uma ferramenta que pode transformar a forma como gerenciamos nossos workloads, permitindo que nossas aplicações respondam dinamicamente à demanda real do negócio.

Espero que este mergulho no KEDA tenha sido útil e inspirador!

E não para por aí! 🚀 Nos próximos artigos da nossa newsletter, vamos continuar essa jornada explorando o Apache Kafka como broker de eventos, mergulhar em práticas de Change Data Capture (CDC) para capturar eventos de bancos de dados em tempo real… e vamos começar a desvendar como tudo isso se encaixa em uma estratégia de “Digital Decoupling” para modernizar seus sistemas e prepará-los para o futuro!

E aí, o que achou? Já usou KEDA? Tem alguma dúvida ou sugestão de tema? Deixa seu comentário aqui embaixo! E se curtiu, compartilha com a galera! Vamos espalhar conhecimento! 👇

#KEDA #Kubernetes #EDA #Microservices #RabbitMQ #DotNET #CloudNativeAraraquara #EngenhariaDeSoftware #ArquiteturaDeSoftware #Escalabilidade #DX #DDD #CNCF

Apoio: 5by5 | Soluções em Sistemas

Repositório do Github: Keda Samples

Compartilhe este texto:

Adicione o texto do seu título aqui

Ao navegar neste site, você aceita os cookies que usamos para melhorar sua experiência. Veja mais informações.