Pular para o conteúdo

Autoscaling no Kubernetes em produção com métricas customizadas

Kubernetes Autoscaling | | Cheesecake Labs
Resumo
  • O autoscaling padrão do Kubernetes, baseado em CPU e memória, falha em workloads assíncronos (Celery, Kafka, ETL), pois essas métricas não refletem a pressão real, como profundidade da fila e utilização dos workers.
  • A estratégia proposta combina métricas customizadas da aplicação, Prometheus para scraping, Prometheus Adapter para expor as métricas via External Metrics API e HPA para escalar pods pela carga real.
  • Configurar o behavior do HPA, com janelas de estabilização e políticas de escala, evita o 'flapping' causado por picos curtos de métrica e torna as decisões mais suaves e previsíveis.
  • O autoscaling de nodes (via Cluster Autoscaler, Karpenter ou solução gerenciada, habilitado no exemplo com Terraform) garante capacidade de compute para os pods, evita que o HPA fique travado e reduz custos em períodos ociosos.

Autoscaling é uma das promessas mais poderosas do Kubernetes e também uma das mais mal compreendidas. Muitos times ficam no autoscaling padrão, baseado em CPU ou memória, e só depois descobrem que o cluster não escala quando deveria, principalmente em workloads assíncronos (workers Celery, consumidores Kafka, serviços de ETL e afins).

Neste guia, você vai percorrer uma estratégia de autoscaling validada em produção que vai muito além das métricas básicas de recurso. Você vai aprender a combinar:

  • Métricas customizadas da aplicação;
  • Prometheus para o scraping;
  • Prometheus Adapter para expor as métricas ao Kubernetes;
  • Horizontal Pod Autoscalers (HPA) para escalar no nível dos pods;
  • Node Autoscaler do AKS (ou de qualquer cloud) para escalar no nível da infraestrutura;

Essa é exatamente a arquitetura que usamos para escalar workloads baseados em Celery sob alta demanda, e ela se mostrou resiliente, previsível e eficiente em custo.

Se você constrói pipelines de busca, processadores em background, sistemas orientados a eventos ou serviços de inferência de IA, este tutorial entrega um blueprint definitivo de escala para Kubernetes.

Por que o autoscaling tradicional não dá conta

O autoscaling horizontal padrão do Kubernetes se apoia em CPU e memória. Em sistemas assíncronos ou orientados a fila, essas métricas não refletem a pressão real.

Um exemplo:

  • Sua fila do Celery cresce de 200 → 6.000 tarefas;
  • Os workers ficam totalmente ociosos entre uma tarefa e outra;
  • A CPU fica entre 10% e 30%;
  • O Kubernetes acha que está tudo bem.

Enquanto isso, os usuários esperam cada vez mais.

A verdade é esta: para fazer autoscaling do jeito certo, você precisa de métricas semânticas que descrevam a carga real, como profundidade da fila, utilização dos workers e lag de eventos. Neste tutorial, a escala é decidida pelo que realmente importa, não pelo que o kernel reporta.

Leia também: Migração cross-platform: por que ela funciona

Visão geral da arquitetura

O autoscaling vai seguir este fluxo:

Visão geral da arquitetura de autoscaling no Kubernetes com métricas customizadas

Vamos implementar cada camada, passo a passo.

1. Expondo métricas da aplicação, usando Celery como exemplo

Primeiro, exponha as métricas que o autoscaler vai usar.
Em Celery, normalmente publicamos:

  • celery_queue_depth
  • celery_workers_busy_ratio

Exemplo de estrutura do endpoint (no estilo Python/Flask):

@app.route("/metrics/celery")

def celery_metrics():

    queue_depth = get_queue_depth()

    busy_ratio = get_busy_workers_ratio()

    return f"""

    celery_queue_depth {queue_depth}

    celery_workers_busy_ratio {busy_ratio}

    """, 200, {"Content-Type": "text/plain"}Code language: PHP (php)

Exponha esse endpoint dentro do Kubernetes por meio de um Service:

apiVersion: v1

kind: Service

metadata:

  name: worker-metrics

spec:

  selector:

    app: my-worker

  ports:

    - name: metrics

      port: 8000

      targetPort: 8000Code language: HTTP (http)

2. Coletando métricas com o Prometheus

O Prometheus precisa fazer scraping do endpoint de métricas.

Adicione o seguinte ao values.yaml do seu Prometheus:

extraScrapeConfigs: |

  - job_name: 'worker-celery-metrics'

    metrics_path: /metrics/celery

    scrape_interval: 15s

    static_configs:

      - targets:

        - 'worker-metrics.default.svc.cluster.local:8000'Code language: JavaScript (javascript)

Assim o Prometheus coleta celery_queue_depth e celery_workers_busy_ratio a cada 15 segundos.

3. Expondo as métricas pelo Prometheus Adapter

Os Horizontal Pod Autoscalers do Kubernetes não conseguem consumir métricas do Prometheus diretamente. Para preencher essa lacuna, o Prometheus Adapter é instalado como um componente separado no cluster, normalmente pelo próprio Helm chart e por um arquivo de configuração, quase sempre em um namespace compartilhado como o monitoring.

O adapter se conecta ao Prometheus, executa queries predefinidas e expõe os resultados pela External Metrics API do Kubernetes (external.metrics.k8s.io). Essas regras de mapeamento de métricas ficam exclusivamente na configuração do Prometheus Adapter, e são elas que tornam as métricas customizadas da aplicação, como profundidade da fila do Celery ou utilização dos workers, disponíveis para o autoscaling.

Exemplo de configuração do adapter:

rules:

  external:

    - seriesQuery: 'celery_queue_depth{job="worker-celery-metrics"}'

      name:

        as: 'celery_queue_depth'

      metricsQuery: 'avg(celery_queue_depth)'

    - seriesQuery: 'celery_workers_busy_ratio{job="worker-celery-metrics"}'

      name:

        as: 'celery_workers_busy_ratio'

      metricsQuery: 'avg(celery_workers_busy_ratio)'Code language: JavaScript (javascript)

Depois do deploy, o Kubernetes consegue consultar essas métricas e os Horizontal Pod Autoscalers podem referenciá-las diretamente.

Você pode inspecioná-las com:

kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq

You should see:

celery_queue_depth

celery_workers_busy_ratioCode language: JavaScript (javascript)

4. HPA do Kubernetes: escalando pods pela carga real

Com as métricas customizadas expostas pelo Prometheus Adapter, o Kubernetes passa a usá-las para decidir a escala. Isso acontece pelo Horizontal Pod Autoscaler (HPA), que ajusta o número de réplicas de pods pela pressão real da carga, e não apenas pelo uso de CPU ou memória.

Nesta configuração, o HPA escala os pods de worker usando métricas externas, como profundidade da fila e utilização dos workers. Essas métricas refletem quanto trabalho o sistema está de fato processando, o que deixa as decisões de escala mais precisas e mais responsivas.

Exemplo de configuração do HPA (usando Helm):

autoscaling:

  enabled: true

  minReplicas: 2

  maxReplicas: 10

  metrics:

    - type: External

      external:

        metric:

          name: celery_queue_depth

        target:

          type: AverageValue

          averageValue: "10"

    - type: External

      external:

        metric:

          name: celery_workers_busy_ratio

        target:

          type: AverageValue

          averageValue: "1"Code language: JavaScript (javascript)

Com essa configuração:

  • Os pods escalam para cima quando a fila passa da profundidade alvo;
  • Os pods escalam para cima quando os workers chegam perto da utilização total;
  • Os pods escalam para baixo quando o sistema fica ocioso.

O ajuste do behavior do HPA e por que ele importa

Quando a escala é guiada por métricas externas ou pela carga, é importante configurar também os parâmetros de behavior do HPA. Sem eles, o autoscaler reage de forma agressiva demais a picos curtos de métrica, o que gera ciclos rápidos de subida e descida de réplicas (o chamado “flapping”).

Ao definir janelas de estabilização e políticas de escala, você garante decisões mais suaves, mais previsíveis e alinhadas a tendências sustentadas de carga, em vez de ruído passageiro.

Exemplo de configuração de behavior:

behavior:

  scaleUp:

    stabilizationWindowSeconds: 300

    policies:

      - type: Pods

        value: 1

        periodSeconds: 60

  scaleDown:

    stabilizationWindowSeconds: 300

    policies:

      - type: Pods

        value: 1

        periodSeconds: 60

Essa configuração limita a frequência com que réplicas podem ser adicionadas ou removidas e dá tempo para o sistema absorver as mudanças de demanda antes de novos ajustes. Em ambientes de produção, principalmente quando o autoscaling vem de métricas de fila, ajustar o behavior do HPA é essencial para evitar instabilidade e troca desnecessária de recursos.

Isso é autoscaling semântico: escala guiada pela lógica de negócio.

Leia também: Nearshore staff augmentation: um guia para o seu negócio

5. Autoscaling no nível dos nodes (AKS ou qualquer cloud provider)

O autoscaling de pods só funciona quando o cluster tem capacidade de compute suficiente. Para adicionar ou remover nodes automaticamente, é preciso habilitar um cluster autoscaler. Isso pode ser feito com a solução preferida da plataforma (o Cluster Autoscaler nativo do Kubernetes, o Karpenter ou um autoscaler gerenciado pelo cloud provider) e configurado por ferramentas de infraestrutura como código ou direto pelo console da cloud. Neste guia, o autoscaling de nodes é habilitado com Terraform, mas os mesmos conceitos valem para qualquer ferramenta ou implementação de autoscaler.

Exemplo genérico em Terraform:

resource "azurerm_kubernetes_cluster" "example" {

  name                = "autoscaling-cluster"

  location            = "eastus"

  resource_group_name = azurerm_resource_group.example.name

  dns_prefix          = "example"

  default_node_pool {

    name                = "default"

    vm_size             = "Standard_D4s_v3"

    node_count          = 2

    enable_auto_scaling = true

    min_count           = 1

    max_count           = 5

    mode                = "System"

  }

  identity {

    type = "SystemAssigned"

  }

}Code language: JavaScript (javascript)

Comportamento:

  • Se os pods não puderem ser agendados → um novo node é adicionado;
  • Se os nodes ficarem subutilizados → nodes são removidos;

Isso garante que:

  • Seu HPA nunca fica travado;
  • Você paga só pelo que usa;

Isso é essencial em qualquer sistema Kubernetes escalável em produção.

6. Validando a configuração

Verifique as decisões do HPA:

kubectl describe hpa -n defaultCode language: JavaScript (javascript)

Verifique as métricas externas:

kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/celery_queue_depth"Code language: JavaScript (javascript)

Verifique os pods pendentes:

kubectl get pods -A | grep PendingCode language: JavaScript (javascript)

Verifique as ações do autoscaler de nodes:

kubectl get nodes

kubectl describe node <name>Code language: HTML, XML (xml)

Para fechar: uma estratégia de autoscaling validada em produção

Fazer autoscaling no Kubernetes não é só ligar HPAs. Autoscaling de verdade exige métricas que entendem a aplicação, decisões bem calibradas e elasticidade de infraestrutura.

Ao combinar:

  • Métricas customizadas
  • Prometheus
  • Prometheus Adapter
  • HPA do Kubernetes
  • Autoscaling de nodes

Você constrói um cluster que reage à demanda real, escala de forma suave sob pressão e reduz custo nos períodos ociosos.

Se suas aplicações dependem de filas, processamento em background ou qualquer workload assíncrono, essa estratégia de escala não é apenas desejável. Ela é essencial.

Este é o jeito definitivo de fazer autoscaling no Kubernetes.

Se o seu time de engenharia quiser ajuda para implementar esse padrão ou precisar escalar workloads mais avançados (IA, pipelines de busca, ETL e afins), entre em contato!