Voltar para o blog

Karpenter: como reduzir custos de Kubernetes sem sacrificar performance

Introdução

Um dos maiores desafios em ambientes Kubernetes não é apenas escalar aplicações, mas garantir que a infraestrutura cresça e diminua de forma eficiente.

Em um cluster Kubernetes tradicional executado na AWS, é comum encontrar uma arquitetura baseada em Amazon EKS (Elastic Kubernetes Service) com Managed Node Groups ou Auto Scaling Groups (ASGs).

O problema aparece quando a demanda das aplicações varia.

Imagine um cluster com:

  • APIs;
  • workers;
  • consumidores de RabbitMQ ou Kafka;
  • pipelines de CI/CD;
  • aplicações internas;
  • workloads de processamento;
  • componentes de observabilidade.

Durante o horário comercial, o cluster pode precisar de 20 nós. Durante a madrugada, talvez apenas seis.

Se os Node Groups forem dimensionados para suportar o pico, boa parte da infraestrutura ficará ociosa durante várias horas.

Uma abordagem tradicional seria utilizar:

Pods
 ↓
Horizontal Pod Autoscaler
 ↓
Pods Pending
 ↓
Cluster Autoscaler
 ↓
Auto Scaling Group
 ↓
EC2

O Cluster Autoscaler resolve parte do problema, mas trabalha principalmente ajustando grupos de nós previamente definidos.

O Karpenter adota outra abordagem.

Em vez de perguntar:

"Qual Node Group deve crescer?"

ele trabalha mais próximo de:

"Quais recursos esses pods realmente precisam e qual capacidade de infraestrutura atende essa demanda?"

A partir disso, o Karpenter pode selecionar diferentes famílias, tamanhos, zonas e modelos de capacidade EC2.

Essa flexibilidade permite combinar:

  • Spot Instances;
  • On-Demand;
  • múltiplos tipos de EC2;
  • consolidação de nós;
  • escalabilidade orientada aos pods;
  • workloads com requisitos diferentes.

O resultado pode ser uma infraestrutura significativamente mais eficiente sem necessariamente reduzir a performance das aplicações.


O que é Karpenter

Karpenter é um projeto open source de autoscaling de nós Kubernetes criado originalmente pela AWS.

Seu objetivo é provisionar infraestrutura computacional dinamicamente de acordo com os requisitos dos workloads.

Em vez de depender exclusivamente de grupos fixos de instâncias, o Karpenter observa pods que não conseguem ser agendados e determina qual infraestrutura pode executá-los.

Simplificando:

Deployment
      ↓
Pods
      ↓
Kubernetes Scheduler
      ↓
Pods Pending
      ↓
Karpenter
      ↓
Análise dos requisitos
      ↓
EC2 Fleet / AWS APIs
      ↓
Nova EC2
      ↓
Node entra no cluster
      ↓
Pods são agendados

O Karpenter considera fatores como:

  • CPU;
  • memória;
  • arquitetura;
  • zona de disponibilidade;
  • afinidade;
  • anti-affinity;
  • taints;
  • tolerations;
  • topology spread;
  • instance type;
  • capacidade Spot;
  • capacidade On-Demand.

Nos releases atuais, a configuração gira principalmente em torno de objetos como NodePool, NodeClaim e, no provider AWS, EC2NodeClass.


Principais conceitos

NodePool

O NodePool determina quais tipos de nós o Karpenter pode criar.

Ele define restrições como:

arquitetura
sistema operacional
família EC2
tamanho
zona
modelo de capacidade
taints
labels
limites
políticas de disruption

Um cluster pode possuir vários NodePools.

Por exemplo:

NodePool: applications
  → Spot
  → c/m/r
  → amd64

NodePool: databases
  → On-Demand
  → r-family
  → amd64

NodePool: monitoring
  → Spot + On-Demand
  → m/r

NodePool: cicd
  → Spot
  → c/m

Separar os workloads dessa forma permite controlar custo e disponibilidade individualmente.

A documentação recomenda que NodePools sejam, quando possível, mutuamente exclusivos, reduzindo ambiguidades sobre qual pool deve atender determinado workload.


EC2NodeClass

O EC2NodeClass contém configurações específicas da AWS.

Por exemplo:

  • AMI;
  • subnets;
  • Security Groups;
  • IAM Role;
  • discos EBS;
  • configuração do kubelet;
  • tags EC2.

Enquanto o NodePool responde:

"Que tipo de capacidade posso utilizar?"

o EC2NodeClass responde:

"Como essa instância deve ser criada na AWS?"

Um mesmo EC2NodeClass pode ser utilizado por múltiplos NodePools.


NodeClaim

O NodeClaim representa uma solicitação concreta de capacidade criada pelo Karpenter.

Podemos visualizar assim:

NodePool
   ↓
template permitido
   ↓
Karpenter identifica demanda
   ↓
NodeClaim
   ↓
EC2
   ↓
Kubernetes Node

O NodeClaim contém os requisitos resultantes da combinação entre:

  • NodePool;
  • requisitos dos pods;
  • recursos necessários;
  • disponibilidade do cloud provider.

Normalmente não é um objeto que o operador cria manualmente.


Como o Karpenter funciona

Imagine três pods pendentes:

api-1      500m CPU   512Mi
api-2      500m CPU   512Mi
worker-1   2000m CPU  2Gi

O Karpenter observa que não existe capacidade suficiente no cluster.

Ele analisa:

CPU total
Memória total
nodeSelector
affinity
topologySpreadConstraints
taints/tolerations
architecture
zones
capacity-type

Depois procura ofertas EC2 capazes de executar esses pods.

Ao contrário de uma estratégia que determina antecipadamente apenas:

t3.xlarge

é possível fornecer flexibilidade para utilizar:

c6a.large
c6a.xlarge
c7a.large
m6a.large
m6a.xlarge
m7a.large
t3a.xlarge
...

Desde que essas instâncias satisfaçam os requisitos definidos.

Essa flexibilidade é particularmente importante para Spot.

Quanto mais restrições desnecessárias forem aplicadas, menor será o conjunto de capacidade disponível. A AWS recomenda evitar restringir excessivamente tipos de instância para workloads que podem ser flexíveis.


Onde realmente está a economia

Karpenter sozinho não torna um cluster barato.

A redução de custo normalmente vem da combinação de várias estratégias:

Karpenter
    +
Spot
    +
instâncias flexíveis
    +
requests corretos
    +
consolidação
    +
autoscaling de pods
    +
remoção de capacidade ociosa

Antes

Imagine cinco nós:

5 × m6i.xlarge

Cada um possui aproximadamente:

4 vCPU
16 GiB RAM

Capacidade:

20 vCPU
80 GiB RAM

Mas os workloads estão utilizando aproximadamente:

8 vCPU
32 GiB RAM

Existe bastante infraestrutura ociosa.

Depois

Com Karpenter, pods podem ser reorganizados e nós subutilizados podem se tornar candidatos à consolidação.

O cluster pode chegar, por exemplo, a:

2 × m6a.xlarge
1 × c6a.xlarge

ou outra combinação que satisfaça os requisitos de scheduling.

A escolha real dependerá de:

  • preço;
  • disponibilidade;
  • requests;
  • arquitetura;
  • constraints;
  • modelo de capacidade.

O objetivo não deveria ser escolher manualmente a EC2 mais barata.

O objetivo é oferecer flexibilidade suficiente para o Karpenter escolher capacidade eficiente.


Exemplo completo: EC2NodeClass

Um exemplo próximo de produção:

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: production
spec:
  amiSelectorTerms:
    - alias: al2023@v20250808

  role: KarpenterNodeRole-production

  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: production

  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: production

  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 80Gi
        volumeType: gp3
        encrypted: true
        deleteOnTermination: true

  tags:
    Environment: production
    ManagedBy: karpenter

O que essa configuração faz

O EC2NodeClass define como os nós serão criados na AWS.

amiSelectorTerms:

determina a AMI utilizada.

Em produção, é recomendável fixar uma versão testada da AMI em vez de utilizar automaticamente @latest, evitando que uma nova imagem seja introduzida no cluster sem validação prévia. Essa recomendação é explicitamente feita pela documentação de boas práticas do EKS para Karpenter.

role:

determina a IAM Role utilizada pelas instâncias.

subnetSelectorTerms:

permite descobrir as subnets através de tags.

securityGroupSelectorTerms:

seleciona os Security Groups utilizados pelos nós.

volumeType: gp3

utiliza volumes EBS GP3.

E:

encrypted: true

garante criptografia do volume.

Os nomes da role, versão da AMI, tamanho do disco, tags, subnets e Security Groups devem ser adaptados para o ambiente.


NodePool Spot para aplicações

Agora podemos criar um NodePool.

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: prod-app
spec:
  template:
    metadata:
      labels:
        workload-type: application

    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: production

      taints:
        - key: prod-app
          value: "true"
          effect: NoSchedule

      requirements:
        - key: kubernetes.io/arch
          operator: In
          values:
            - amd64

        - key: kubernetes.io/os
          operator: In
          values:
            - linux

        - key: karpenter.sh/capacity-type
          operator: In
          values:
            - spot

        - key: karpenter.k8s.aws/instance-category
          operator: In
          values:
            - c
            - m

        - key: karpenter.k8s.aws/instance-generation
          operator: Gte
          values:
            - "6"

      expireAfter: 720h
      terminationGracePeriod: 2m

  limits:
    cpu: "200"
    memory: 400Gi

  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 5m

    budgets:
      - nodes: "20%"

Esse NodePool permite que o Karpenter escolha instâncias:

c*
m*

a partir da geração seis, desde que sejam compatíveis com os requisitos.

Em vez de definir:

values:
  - m6a.xlarge

oferecemos várias possibilidades.

Isso melhora as chances de encontrar capacidade Spot disponível.

O Karpenter suporta restrições por arquitetura, sistema operacional, tipo de capacidade, categoria, geração e outras características das instâncias.

Taint

O NodePool cria:

prod-app=true:NoSchedule

Consequentemente, somente workloads com a toleration correspondente poderão ser executados nesses nós.

Isso evita que componentes aleatórios do cluster sejam enviados para capacidade destinada às aplicações.

Limits

limits:
  cpu: "200"
  memory: 400Gi

estabelece um limite de crescimento do pool.

Sem spec.limits, o crescimento máximo pode acabar sendo limitado basicamente pelas quotas do cloud provider, portanto definir limites é uma proteção operacional e financeira importante.


Deployment utilizando o NodePool

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: production
spec:
  replicas: 4

  selector:
    matchLabels:
      app: orders-api

  template:
    metadata:
      labels:
        app: orders-api

    spec:
      terminationGracePeriodSeconds: 30

      tolerations:
        - key: prod-app
          operator: Equal
          value: "true"
          effect: NoSchedule

      nodeSelector:
        workload-type: application

      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: orders-api

      containers:
        - name: api
          image: registry.example.com/orders-api:2.8.1

          ports:
            - containerPort: 8080

          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi

          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20

Aqui existe um ponto fundamental:

resources:
  requests:
    cpu: 500m
    memory: 512Mi

O Karpenter utiliza os requests para entender quanto recurso os pods precisam.

Se os requests estiverem completamente superdimensionados, o Karpenter concluirá que precisa de mais infraestrutura do que realmente é necessário.

Por exemplo:

Uso real CPU:      150m
CPU request:       2000m

Para o Kubernetes, aquele pod reserva:

2 CPUs

mesmo utilizando apenas uma pequena fração disso.

Consequentemente, FinOps em Kubernetes começa pelo correto dimensionamento dos workloads.


PodDisruptionBudget

Se utilizarmos consolidação, precisamos considerar disponibilidade.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: orders-api
  namespace: production
spec:
  minAvailable: 3

  selector:
    matchLabels:
      app: orders-api

Com quatro réplicas:

replicas = 4
minAvailable = 3

uma interrupção voluntária deve preservar pelo menos três pods disponíveis.

O PodDisruptionBudget (PDB) ajuda a proteger a aplicação durante operações como drain e consolidação.

Porém PDBs excessivamente restritivos também podem impedir o Karpenter de consolidar nós. A própria documentação mostra PDBs como uma possível causa de nós marcados como Unconsolidatable.

Portanto:

PDB fraco demais
→ risco de indisponibilidade

PDB restritivo demais
→ infraestrutura difícil de consolidar

PDB correto
→ equilíbrio entre disponibilidade e eficiência

Exemplo do dia a dia

Considere uma plataforma SaaS com:

EKS

├── APIs Node.js
├── APIs Go
├── workers
├── RabbitMQ
├── Redis
├── PostgreSQL
├── Prometheus
├── Grafana
└── pipelines CI/CD

Durante horário comercial:

CPU solicitada: ~45 vCPU

À noite:

CPU solicitada: ~15 vCPU

Antes do Karpenter, a empresa mantém:

12 × m6a.xlarge

24 horas por dia.

O cluster precisa dessa capacidade durante picos, mas mantém boa parte dela ociosa durante várias horas.


Aplicando Karpenter

A arquitetura passa para:

                    Kubernetes
                         │
             ┌───────────┴───────────┐
             │                       │
         APIs/Workers          Infra crítica
             │                       │
             ▼                       ▼
      NodePool prod-app       NodePool critical
             │                       │
           Spot                  On-Demand
             │                       │
        c/m families              m/r family

Assim:

APIs e workers

Utilizam:

Spot

porque são:

  • replicados;
  • stateless;
  • tolerantes a interrupções;
  • inicializam rapidamente.

Bancos ou componentes extremamente sensíveis

Podem permanecer:

On-Demand

ou em Managed Node Groups dedicados.

O objetivo não é executar 100% do cluster em Spot.

É utilizar Spot onde a arquitetura suporta interrupções.


Spot não significa simplesmente infraestrutura barata

Spot possui uma característica importante:

a capacidade pode ser interrompida.

Na AWS, determinadas interrupções Spot fornecem aproximadamente dois minutos de antecedência. O Karpenter possui integração para tratamento desses eventos e pode iniciar substituição enquanto executa cordon/drain do nó afetado.

Para isso, é importante configurar o mecanismo de interruption handling, incluindo a fila SQS utilizada pelo Karpenter.

Arquiteturalmente:

AWS interruption event
         ↓
EventBridge
         ↓
SQS
         ↓
Karpenter
         ↓
cordon / taint / drain
         ↓
novo NodeClaim
         ↓
nova EC2

Spot deve ser utilizado principalmente em workloads preparados para desaparecer e retornar.


Estratégia Spot + On-Demand

Uma alternativa é permitir os dois modelos:

- key: karpenter.sh/capacity-type
  operator: In
  values:
    - spot
    - on-demand

Nos NodePools compatíveis com múltiplos modelos de capacidade, o Karpenter pode recorrer a alternativas conforme disponibilidade. A documentação atual também contempla capacidade reserved para reservas de capacidade específicas; isso não deve ser confundido com Reserved Instances tradicionais.

Outra estratégia, mais controlada, é possuir NodePools independentes:

prod-app-spot
prod-app-ondemand

e utilizar:

  • labels;
  • weights;
  • affinities;
  • topology spread.

A escolha depende da política de confiabilidade da organização.


Consolidação

Uma das funcionalidades mais importantes do Karpenter para FinOps é a consolidação.

Imagine:

Node A → 20% utilizado
Node B → 25% utilizado
Node C → 20% utilizado

Talvez seja possível mover os workloads para:

Node D → 70% utilizado

e remover os três anteriores.

O Karpenter pode avaliar:

Node A ──┐
Node B ──┼──► Node mais eficiente
Node C ──┘

A documentação descreve consolidação através de operações de remoção ou substituição e avalia se workloads podem ser executados em capacidade existente ou em uma alternativa mais eficiente.


consolidateAfter

Não é necessário consolidar imediatamente.

Por exemplo:

consolidateAfter: 5m

significa que o Karpenter espera um período de estabilidade antes de considerar determinadas consolidações.

Isso é útil para workloads com comportamento variável.

Imagine:

10:00 → escala
10:02 → reduz
10:04 → escala
10:06 → reduz

Consolidação muito agressiva pode gerar:

cria node
↓
remove node
↓
cria node
↓
remove node

Esse comportamento é conhecido informalmente como node churn.

Um consolidateAfter adequado reduz movimentações desnecessárias.


Disruption Budgets

Uma proteção importante para produção é controlar quantos nodes o Karpenter pode interromper simultaneamente.

Exemplo:

disruption:
  consolidationPolicy: WhenEmptyOrUnderutilized
  consolidateAfter: 5m

  budgets:
    - nodes: "10%"

Assim, o Karpenter não executa indiscriminadamente grandes quantidades de interrupções voluntárias simultâneas.

NodePool Disruption Budgets limitam ações voluntárias relacionadas a mecanismos como consolidação e drift.

Essa proteção é especialmente importante quando existem dezenas ou centenas de nodes.


Produção: separando workloads

Uma arquitetura madura pode possuir:

                    EKS
                     │
     ┌───────────────┼────────────────┐
     │               │                │
prod-app         monitoring         database
     │               │                │
Spot            Spot/OnDemand      On-Demand
     │               │                │
c/m               m/r                r

E ainda:

ci-cd
  │
Spot
  │
c/m

prod-app

Aplicações:

Node.js
Go
PHP
Java

Podem utilizar Spot quando possuem:

  • múltiplas réplicas;
  • readinessProbe;
  • PDB;
  • shutdown adequado;
  • distribuição entre AZs.

CI/CD

Agents de build são ótimos candidatos para Spot quando jobs podem ser reexecutados.

Observabilidade

Pode utilizar estratégia híbrida.

Bancos

Stateful workloads exigem análise mais cuidadosa.

Karpenter pode executar workloads stateful, mas isso não significa automaticamente que um banco crítico deve ser colocado em capacidade Spot.


FinOps: requests são fundamentais

Considere:

resources:
  requests:
    cpu: "4"
    memory: 8Gi

Mas observabilidade mostra:

CPU P95: 700m
RAM P95: 2.5Gi

Se existirem dez pods:

Kubernetes enxerga:

40 vCPU
80 GiB

mesmo que o consumo real esteja muito abaixo.

Karpenter provisionará capacidade considerando o que o scheduler precisa reservar.

Por isso, a sequência correta de otimização normalmente é:

Métricas
   ↓
Rightsizing
   ↓
Requests adequados
   ↓
Karpenter
   ↓
Consolidation
   ↓
Spot

Não:

requests errados
   ↓
Karpenter
   ↓
"por que não economizou?"

Observabilidade

Karpenter precisa ser monitorado como qualquer componente crítico da plataforma.

Ele disponibiliza métricas em formato Prometheus relacionadas a provisioning, NodeClaims, cloud provider, reconciliações e consolidação.

Uma stack típica:

Karpenter
    │
    ├── metrics ──► Prometheus ──► Grafana
    │
    └── logs ─────► Loki ────────► Grafana

Entre os pontos importantes para acompanhar estão:

tempo para criação do node
NodeClaims pendentes
erros do cloud provider
falhas de provisioning
nodes NotReady
pods Pending
quantidade Spot x On-Demand
consolidações
interrupções
custo por NodePool
utilização CPU/memória
requests x utilização

Algumas métricas disponíveis incluem dados sobre erros e duração das operações do cloud provider, além de métricas relacionadas à consolidação.


Logs

Logs do controller podem ser analisados com:

kubectl logs \
  -n kube-system \
  -l app.kubernetes.io/name=karpenter \
  --tail=200

Para acompanhar em tempo real:

kubectl logs \
  -n kube-system \
  -l app.kubernetes.io/name=karpenter \
  -f

No Loki, pode-se centralizar esses logs e criar painéis ou alertas para eventos como:

failed scheduling
insufficient capacity
NodeClaim failed
cloud provider error
unconsolidatable

Troubleshooting

Pods permanecem Pending

Primeiro:

kubectl get pods -A --field-selector=status.phase=Pending

Depois:

kubectl describe pod <pod>

Observe a seção:

Events

Erros comuns:

Insufficient cpu
Insufficient memory
untolerated taint
node affinity conflict
topology constraints

Verificar NodePools

kubectl get nodepool

Detalhes:

kubectl describe nodepool prod-app

Verifique:

requirements
limits
conditions
disruption

Verificar NodeClaims

kubectl get nodeclaims

Depois:

kubectl describe nodeclaim <nodeclaim>

Isso ajuda a identificar problemas entre:

Pod
↓
NodePool
↓
NodeClaim
↓
EC2

Verificar EC2NodeClass

kubectl get ec2nodeclass

E:

kubectl describe ec2nodeclass production

Problemas podem incluir:

subnet não encontrada
security group incorreto
AMI inválida
IAM insuficiente
capacity indisponível

Eventos Kubernetes

Um dos comandos mais úteis:

kubectl get events -A \
  --sort-by=.metadata.creationTimestamp

Também:

kubectl get nodes \
  -L karpenter.sh/nodepool,karpenter.sh/capacity-type,node.kubernetes.io/instance-type

Isso permite visualizar algo semelhante a:

NODE       NODEPOOL    CAPACITY     INSTANCE
node-01    prod-app    spot         c6a.xlarge
node-02    prod-app    spot         m6a.large
node-03    critical    on-demand    m6i.xlarge

Segurança

IAM

O Karpenter precisa criar e remover infraestrutura AWS.

Portanto, permissões IAM precisam seguir o princípio do menor privilégio.

Evite políticas como:

{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

As permissões do controller e das instâncias devem possuir responsabilidades claramente separadas.


ServiceAccounts

Em EKS, prefira mecanismos de identidade específicos para workloads, como EKS Pod Identity ou IRSA conforme a arquitetura e versão adotadas.

Evite credenciais AWS hard coded em:

env:
  AWS_ACCESS_KEY_ID: ...

ou:

Secret:
  access-key: ...

quando identidade federada puder ser utilizada.


Isolamento de workloads

Use:

taints
tolerations
labels
nodeAffinity
NetworkPolicies
namespaces
RBAC

para impedir que workloads sejam executados onde não deveriam.

Por exemplo:

database → nodes database
monitoring → nodes monitoring
applications → nodes prod-app
CI/CD → nodes ci-cd

O Karpenter não substitui políticas de isolamento Kubernetes.


Disponibilidade

Economia não pode destruir disponibilidade.

Uma aplicação em Spot deveria considerar:

Deployment
   │
   ├── replica 1 → AZ A
   ├── replica 2 → AZ B
   └── replica 3 → AZ A/B

Utilize:

topologySpreadConstraints:

ou mecanismos equivalentes para evitar concentração desnecessária das réplicas.

Também considere:

PDB
readinessProbe
livenessProbe
startupProbe
terminationGracePeriodSeconds
graceful shutdown

O que evitar

Fixar apenas um instance type

Evite:

- key: node.kubernetes.io/instance-type
  operator: In
  values:
    - m6a.xlarge

sem uma necessidade real.

Isso reduz consideravelmente a flexibilidade.

Melhor trabalhar com requisitos:

- key: karpenter.k8s.aws/instance-category
  operator: In
  values:
    - c
    - m

quando o workload permitir.


Colocar tudo em Spot

Não faça:

Kubernetes inteiro
      ↓
100% Spot

apenas para maximizar a economia.

Classifique workloads por tolerância a interrupção.

Por exemplo:

WorkloadEstratégia possível
API statelessSpot
Worker idempotenteSpot
CI/CD AgentSpot
Processamento batchSpot
Observabilidadehíbrido
Banco críticoOn-Demand
Componentes essenciaisOn-Demand ou estratégia híbrida

Essas são decisões arquiteturais, não regras absolutas.


Não ignorar requests

Evite:

resources: {}

Em clusters com autoscaling, isso prejudica:

  • scheduling;
  • capacidade;
  • bin packing;
  • previsibilidade;
  • FinOps.

Não utilizar AMI @latest indiscriminadamente em produção

Exemplo:

amiSelectorTerms:
  - alias: al2023@latest

é conveniente em ambientes de teste.

Em produção, uma atualização inesperada pode introduzir alterações em:

kernel
container runtime
kubelet
drivers
configuração

A recomendação da AWS é utilizar AMIs conhecidas e testadas em produção e validar novas versões em ambientes não produtivos antes da atualização.


Desenvolvimento x produção

Desenvolvimento

Pode ser aceitável:

100% Spot
consolidação agressiva
poucas réplicas
NodePools genéricos
expireAfter menor

O principal objetivo pode ser custo.


Produção

Uma estratégia adequada tende a incluir:

Spot + On-Demand
múltiplas AZs
PDB
topology spread
interruption handling
AMIs fixadas
observabilidade
NodePool limits
disruption budgets
requests revisados
isolamento por workload

Produção deve priorizar equilíbrio entre:

custo
+
resiliência
+
performance
+
previsibilidade

Exemplo de arquitetura de produção

                          AWS
                           │
                          EKS
                           │
              ┌────────────┼────────────┐
              │            │            │
             API        Workers      Database
              │            │            │
            HPA           KEDA        Stateful
              │            │            │
              └──────┬─────┘            │
                     │                  │
                     ▼                  ▼
               Pending Pods      Critical Pods
                     │                  │
                     ▼                  ▼
                 Karpenter          NodePool
                     │              On-Demand
          ┌──────────┴──────────┐
          │                     │
        Spot                 On-Demand
          │                     │
      c/m families           m/r families

Nesse modelo:

HPA

Escala APIs por métricas.

CPU
memory
custom metrics

KEDA

Pode escalar workers através de:

RabbitMQ
Kafka
SQS
Prometheus

Karpenter

Escala a infraestrutura quando os pods deixam de caber nos nodes existentes.

Portanto:

HPA/KEDA
    ↓
mais Pods
    ↓
Pods Pending
    ↓
Karpenter
    ↓
mais Nodes

E na redução:

demanda cai
    ↓
HPA/KEDA reduz Pods
    ↓
nodes ficam subutilizados
    ↓
Karpenter consolida
    ↓
EC2 removida

Essa interação é uma das formas mais eficientes de elasticidade Kubernetes.


Estratégia FinOps completa

Karpenter deve fazer parte de uma estratégia maior.

Uma arquitetura madura pode utilizar:

Prometheus
   ↓
análise utilização
   ↓
VPA recommendations / rightsizing
   ↓
requests corretos
   ↓
HPA/KEDA
   ↓
Karpenter
   ↓
Spot
   ↓
consolidation
   ↓
OpenCost

O OpenCost pode complementar o ambiente mostrando custos por:

namespace
deployment
pod
label
Node

Assim é possível descobrir situações como:

Namespace A
requests CPU: 30 cores
CPU real: 6 cores

Nesse caso, trocar On-Demand por Spot pode reduzir custo, mas o problema estrutural continua existindo:

requests superdimensionados.

FinOps eficiente começa pela demanda, não apenas pelo preço da infraestrutura.


Quando utilizar Karpenter

Karpenter é especialmente interessante quando existem:

  • clusters EKS com demanda variável;
  • muitos workloads;
  • aplicações stateless;
  • autoscaling frequente;
  • utilização de Spot;
  • necessidade de múltiplos tipos de EC2;
  • workloads com requisitos diferentes;
  • preocupação significativa com custos;
  • crescimento imprevisível;
  • ambientes com HPA ou KEDA.

Quanto maior a variação da demanda, maior tende a ser o benefício potencial.


Quando Karpenter pode não ser necessário

Nem todo cluster precisa de Karpenter.

Imagine:

3 nodes
3 aplicações
carga praticamente constante

Se o ambiente funciona continuamente com:

3 × m6i.large

e raramente muda, um Managed Node Group pode ser suficiente.

Adicionar:

Karpenter
NodePools
NodeClaims
EC2NodeClasses
interruption handling
políticas de disruption
monitoramento

aumenta a complexidade operacional.

Nesse cenário, a economia talvez não justifique essa complexidade.

Karpenter tende a trazer maior valor para ambientes dinâmicos.


Checklist antes de utilizar Karpenter em produção

Antes da migração, valide:

  • requests e limits revisados;
  • aplicações replicadas;
  • readiness e liveness probes configuradas;
  • graceful shutdown;
  • PodDisruptionBudgets;
  • distribuição entre AZs;
  • NodePools separados por responsabilidade;
  • limites de CPU e memória;
  • estratégia Spot/On-Demand;
  • interruption handling;
  • IAM seguindo menor privilégio;
  • discos criptografados;
  • AMIs controladas;
  • métricas Prometheus;
  • dashboards Grafana;
  • logs centralizados;
  • alertas para falha de provisioning;
  • testes reais de interrupção de nodes.

Resumo prático

O Karpenter muda a lógica tradicional de autoscaling de infraestrutura Kubernetes.

Em vez de pensar apenas:

preciso aumentar meu grupo de t3.xlarge

podemos pensar:

tenho estes pods
com estes requisitos
e preciso da capacidade mais adequada disponível

Essa diferença permite aproveitar melhor:

Spot
+
múltiplos instance types
+
múltiplas famílias
+
consolidação
+
autoscaling dinâmico

O fluxo final pode ser representado como:

Demanda aumenta
     ↓
HPA / KEDA
     ↓
Novos Pods
     ↓
Pods Pending
     ↓
Karpenter
     ↓
NodeClaim
     ↓
EC2 Spot / On-Demand
     ↓
Node Kubernetes
     ↓
Pods executando

Quando a demanda diminui:

menos Pods
     ↓
nodes subutilizados
     ↓
consolidação
     ↓
drain
     ↓
terminação EC2
     ↓
redução de custo

Conclusão

Karpenter não deve ser tratado apenas como uma ferramenta para criar EC2 automaticamente.

Seu maior benefício aparece quando ele faz parte de uma arquitetura Kubernetes projetada para elasticidade.

A redução de custo surge da combinação entre:

rightsizing
+
autoscaling de aplicações
+
provisionamento dinâmico
+
Spot
+
flexibilidade de instance types
+
consolidação
+
observabilidade

Um cluster que possui requests superdimensionados, aplicações incapazes de tolerar interrupções e NodePools excessivamente restritivos continuará sendo ineficiente mesmo utilizando Karpenter.

Por outro lado, quando aplicações são corretamente dimensionadas e arquitetadas para resiliência, o Karpenter permite que a infraestrutura acompanhe muito mais de perto a demanda real.

Para Platform Engineering, isso significa disponibilizar uma camada de infraestrutura capaz de oferecer diferentes perfis de capacidade:

applications → Spot
critical     → On-Demand
batch        → Spot
ci-cd        → Spot
database     → capacidade estável

Para SRE, significa combinar elasticidade com controles de disponibilidade, observabilidade e disruption.

Para FinOps, significa reduzir o intervalo entre capacidade provisionada e capacidade realmente necessária.

E esse é o ponto central do Karpenter: não reduzir custos simplesmente escolhendo máquinas mais baratas, mas permitir que o Kubernetes utilize a quantidade e o tipo de infraestrutura adequados para cada momento, mantendo performance, disponibilidade e confiabilidade como requisitos da arquitetura.

Quer aplicar isso no seu ambiente?

Agende uma conversa de 30 minutos sem compromisso. Eu olho o seu cenário e digo por onde começar.

Agendar uma conversa

Receba os novos artigos por e-mail

Um e-mail quando sair artigo novo sobre infraestrutura, backend e IA. Sem spam, cancele quando quiser.