Karpenter versus Cluster Autoscaler: o que de fato controla o custo de nós no EKS
Um guia de Michael Zion, CEO da MeteorOps, compara as duas formas de escalar nós no EKS e mostra que o problema raramente é a ferramenta escolhida: é o teto que ninguém configurou.
Por Danilo Barros6 min de leitura
A decisão
Quem opera EKS cedo ou tarde esbarra nessa escolha: deixar o cluster crescer pela lógica de grupos de nós fixos, com o Cluster Autoscaler, ou deixar uma ferramenta decidir, pod a pod, qual instância comprar, com o Karpenter. Um guia publicado por Michael Zion, CEO da consultoria MeteorOps, no DEV Community, resume esse trade-off e detalha a configuração que impede qualquer um dos dois de virar motivo de susto na fatura.
A diferença estrutural é simples de enunciar. O cluster-autoscaler aumenta o desired size de grupos de nós que você já definiu antes, como um Auto Scaling Group ou managed node group. O Karpenter ignora grupos de nós: ele cria instâncias direto a partir dos pods pendentes, escolhendo entre vários tipos de instância, e depois consolida nós subutilizados.
No AWS, segundo o guia, o Karpenter costuma empacotar as cargas de forma mais apertada e lidar melhor com Spot. O Cluster Autoscaler é a opção portátil, que funciona em praticamente todas as nuvens grandes. A escolha entre os dois importa menos do que uma coisa que vale para qualquer um deles: sem um teto explícito de capacidade, o autoscaler de nós vai crescer enquanto houver pod pendente, e o teto é o que separa escalabilidade de bill shock.
Lado a lado
O guia organiza a comparação em oito critérios, todos aplicáveis ao mesmo cluster EKS:
| Critério | Cluster Autoscaler | Karpenter |
|---|---|---|
| Como adiciona capacidade | Aumenta o desired size de um node group já existente (ASG, managed node group) | Cria instâncias direto a partir dos requisitos dos pods pendentes |
| Escolha de instância | Fixa por node group; é preciso pré-criar um grupo por formato | Qualquer tipo permitido pelos requirements do NodePool (família, tamanho, arquitetura, zona, Spot ou On-Demand) |
| Scale-down | Remove nós abaixo de um limiar de utilização (padrão 50%) após ficarem ociosos (padrão 10 min) | Consolidação apaga nós vazios e troca nós subutilizados por opções mais baratas |
| Spot | Node groups com instâncias mistas mais um handler de interrupção separado | Seleção nativa de Spot/On-Demand, com interrupção tratada via fila SQS |
| Controles de disrupção | PDBs e a anotação cluster-autoscaler.kubernetes.io/safe-to-evict: "false" | PDBs, disruption budgets do NodePool e a anotação karpenter.sh/do-not-disrupt: "true" |
| Teto de custo | Tamanho máximo do node group e a flag --max-nodes-total | spec.limits (CPU e memória) em cada NodePool |
| Nuvens suportadas | AWS, GCP, Azure e outras | AWS (v1 estável desde 2024); no Azure via AKS Node Auto Provisioning, construído sobre o próprio Karpenter |
| Risco principal | Muitos node groups para manter; escala mais lenta e grosseira | NodePool sem limite; churn por consolidação agressiva |
Os dois reagem ao mesmo sinal, o pod pendente. O que muda é o grão da decisão e o que impede o crescimento de continuar.
Quando escolher cada um
O guia é direto sobre os cenários em que cada ferramenta compensa:
- Karpenter no EKS: cargas com formato variável, necessidade de Spot com fallback automático para On-Demand, ou um cluster que hoje paga por nós meio vazios que o Cluster Autoscaler não consolida.
- Cluster Autoscaler: operação multi-cloud, GKE (que tem seu próprio node auto-provisioning), poucos formatos de node group que já atendem bem a carga, ou um time que prefere formatos de nó fixos e previsíveis.
- AKS: usar o autoscaler nativo ou o Node Auto Provisioning em vez de instalar qualquer um dos dois por conta própria, já que o Azure embute o Karpenter por baixo.
Um detalhe prático do guia: se o cluster já precisa de muitos tipos de instância só para perseguir capacidade de Spot, esse é geralmente o ponto em que o Karpenter fica mais simples de operar do que manter dezenas de node groups.
Sobre migração, o texto é categórico:
Não rode as duas ferramentas contra os mesmos nós.
Do not run both against the same nodes.Michael Zion, CEO da MeteorOps
O caminho recomendado é criar o NodePool, escalar o Cluster Autoscaler para zero réplicas e então fazer cordon/drain gradual dos node groups antigos, com PDBs protegendo as cargas críticas durante a transição.
Custo
No Karpenter, o teto vem do spec.limits de cada NodePool:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
values: ["c", "m", "r"]
- key: kubernetes.io/arch
values: ["amd64", "arm64"]
expireAfter: 720h
limits:
cpu: "200"
memory: 400Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 5m
budgets:
- nodes: "10%"
- nodes: "0"
schedule: "0 9 * * mon-fri"
duration: 8hO limits.cpu e limits.memory são o teto duro do NodePool: nenhum pod pendente consegue criar nó além desse ponto. O segundo budget, com nodes: "0" agendado em horário comercial, impede que a consolidação derrube nós durante o pico, mesmo que ela continue economizando à noite.
No Cluster Autoscaler, o teto é configurado nas flags de inicialização:
--cloud-provider=aws
--node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-cluster
--expander=least-waste
--balance-similar-node-groups
--max-nodes-total=60
--scale-down-utilization-threshold=0.5
--scale-down-unneeded-time=10m
--skip-nodes-with-system-pods=trueO --max-nodes-total=60 é o equivalente ao spec.limits do Karpenter: um teto de cluster inteiro, não por node group.
O ponto mais importante do guia, porém, é que o autoscaler de nós é só uma camada entre várias que decidem o gasto. O HPA costuma calcular CPU como porcentagem do request do pod, não da capacidade do nó: um container com request de 500m rodando a 400m já está em 80%, mesmo que o nó tenha folga. Request errado distorce toda decisão de escala, em qualquer direção:
Requestsbaixos demais: o scheduler empacota pods demais no nó, e a carga sofrethrottlingantes do HPA reagir.Requestsaltos demais: cada réplica reserva mais nó do que precisa, então escalar cria nós mais cedo e maiores do que o necessário.Requestsausentes: a métrica de utilização de CPU fica inútil ou indisponível, e o agendamento vira imprevisível.
O guia recomenda também um ResourceQuota por namespace, para impedir que um time sozinho consuma toda a capacidade aprovada:
apiVersion: v1
kind: ResourceQuota
metadata:
name: application-quota
namespace: production
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "50"Uma quota é uma guarda de agendamento, não uma garantia de teto de fatura: overhead de nó, DaemonSets, armazenamento e load balancers continuam consumindo recursos fora dela.
Veredito
Se o cluster roda só na AWS, tem cargas de formato variável e já paga por nós meio vazios que o Cluster Autoscaler nunca consolida, o Karpenter tende a economizar mais, principalmente combinado com Spot e fallback automático para On-Demand. Se a operação é multi-cloud, roda em GKE, ou já tem poucos formatos de node group que atendem bem a carga atual, o Cluster Autoscaler continua sendo a opção mais simples de operar e portar entre nuvens. No AKS a escolha nem é sua: o Node Auto Provisioning já usa Karpenter por baixo, de forma gerenciada.
Mas a decisão entre as duas ferramentas resolve só metade do problema de custo. Sem spec.limits no NodePool ou --max-nodes-total no Cluster Autoscaler, sem requests calibrados por uso real e sem maxReplicas definido no HPA, qualquer uma das duas vai crescer exatamente até onde o pod pendente mandar, e isso raramente coincide com o orçamento aprovado.
Fonte: DEV Community — #aws
Serviços neste textoEKS
Este artigo foi escrito por Danilo Barros. Conteúdo produzido pela redação de agentes de IA do AWS Hub, a partir de fontes oficiais citadas no texto. Saiba como produzimos no expediente.