Voltar para o blog

Quais workloads do seu cluster podem rodar em Spot? Uma ferramenta open source para descobrir

"Use Spot nas cargas que toleram interrupção" é um conselho comum em FinOps. O que quase nunca se diz é como descobrir quais cargas são essas. Na prática, a resposta costuma ser um palpite, e o palpite errado vira incidente.

O spot-readiness é uma ferramenta de linha de comando, open source, que transforma essa pergunta em uma checagem objetiva sobre a configuração dos workloads.

O que ele verifica

Ele lê Deployments, StatefulSets, Argo Rollouts, Jobs, CronJobs, ScaledJobs do KEDA e Pods, a partir de arquivos YAML ou direto do cluster, e aplica dezenove regras. Cada workload recebe uma nota e um veredito: pronto, pronto com ressalvas ou não pronto.

RegraGravidadeO que checa
SR001BloqueiaMenos de 2 réplicas (o mínimo do HPA conta) ou Pod avulso
SR002BloqueiaVolumes hostPath
SR003RessalvaterminationGracePeriodSeconds acima de 120 s
SR004RessalvaVárias réplicas sem topology spread nem anti-afinidade
SR005RessalvaVárias réplicas sem PodDisruptionBudget
SR006RessalvaContainer sem readinessProbe
SR007RessalvaVolumes persistentes
SR008InformativaContainer sem requests de CPU e memória
SR009InformativaNenhum hook preStop
SR010BloqueiaJob com backoffLimit: 0: um pod perdido derruba o job inteiro
SR011InformativaJob com activeDeadlineSeconds: as novas tentativas após uma interrupção contam no prazo
SR012InformativaJob sem podFailurePolicy que ignore DisruptionTarget: uma interrupção pode contar contra o backoffLimit
SR013InformativaO progresso se perde na interrupção, a menos que o job faça checkpoint (a ferramenta não consegue verificar)
SR014BloqueiaPodDisruptionBudget que não permite nenhuma disrupção (por exemplo minAvailable igual às réplicas)
SR015RessalvaPodDisruptionBudget sem unhealthyPodEvictionPolicy: AlwaysAllow: um pod não saudável pode travar o drain
SR016RessalvaRestrito a uma única Zona de Disponibilidade
SR017RessalvaExige um tipo de instância específico, ou fixa em um nó pelo hostname
SR018InformativaLimita a flexibilidade de capacidade Spot: poucos tipos ou famílias de instância, ou GPUs
SR019RessalvanodeSelector e nodeAffinity obrigatórios fixam três ou mais aspectos do posicionamento

As regras SR001, SR004, SR005, SR006, SR009, SR014 e SR015 valem para serviços de longa duração. As regras SR010 a SR013 valem para Jobs, CronJobs e ScaledJobs. As demais valem para todos os workloads. Um ScaledObject do KEDA conta como número mínimo de réplicas, como um HPA.

O limite de 120 segundos da SR003 vem do aviso de interrupção do Spot na AWS, que é de dois minutos. Um desligamento mais longo que isso é cortado no meio.

Um detalhe que costuma confundir: o PodDisruptionBudget não impede que o Spot seja retomado. Ele só limita quantas réplicas um drain remove ao mesmo tempo. É por isso que a SR014 existe: um budget com minAvailable igual às réplicas não permite nenhuma remoção, e um drain, uma consolidação do Karpenter ou uma atualização de nós fica travada nele.

Como usar

# arquivos, Helm ou kustomize
spot-readiness scan ./k8s
helm template minha-release ./chart | spot-readiness scan -

# o cluster atual (somente leitura)
spot-readiness scan --context prod -n loja

# saída para planilha ou CI
spot-readiness scan -f csv ./k8s > workloads.csv
spot-readiness scan --fail-on not-ready ./k8s

# a correção de cada achado e a ordem para mover as cargas
spot-readiness scan --suggest ./k8s
spot-readiness plan ./k8s

No fim, a ferramenta mostra a parcela da CPU solicitada que está em workloads prontos ou com ressalvas, com um link para a calculadora de economia em Kubernetes já preenchido com esse número.

O workload consegue mudar de pool de capacidade?

Sobreviver a um restart não basta. O Spot funciona quando o Karpenter pode escolher entre muitos pools de capacidade, e a AWS recomenda ter flexibilidade entre tipos de instância e zonas. As regras SR016 a SR019 leem o nodeSelector e o nodeAffinity obrigatório e apontam o workload preso a uma zona, a um tipo de instância ou a poucas famílias. Cada workload também ganha uma nota de flexibilidade, em uma tabela própria:

SPOT FLEXIBILITY

WORKLOAD                   SCORE  AZS  FAMILIES  CONSTRAINTS
ml/deployment/ml-training  10     1    g5        high

O que o cluster está fazendo agora?

No modo cluster (spot-readiness scan sem caminhos) a ferramenta também lê os pods e os nós em execução, e responde a outra pergunta: se este nó Spot sumisse agora, o que aconteceria?

  • Risco em runtime segue cada pod até o nó, a zona, o tipo de capacidade e o tipo de instância. Ele avisa quando todas as réplicas estão no mesmo nó ou na mesma zona, e mostra o raio de impacto de perder um nó.
  • Tratamento de interrupção verifica se o Karpenter roda com uma interruption queue, se o AWS Node Termination Handler roda, e quantos workloads têm problema de PodDisruptionBudget ou levam mais de 120 s para terminar. O resultado é uma nota do cluster de 0 a 100.
  • Com --karpenter, ele lê NodePools, NodeClaims e EC2NodeClasses e aplica sete regras KR: uma zona só, poucas famílias ou tipos de instância, requisitos restritivos demais, disruption budgets, um pool só Spot sem alternativa on-demand e a falta de minValues.
INTERRUPTION HANDLING

Karpenter detected             ✓
Interruption queue configured  ✓
Node Termination Handler       -
PDB issues                     2
Workloads >120s termination    1

Cluster interruption readiness: 86/100

Os valores acima são ilustrativos. A visão de runtime é um retrato do momento e não muda a nota do workload.

Dos achados a um plano

O spot-readiness plan transforma os achados em uma ordem de trabalho, em quatro grupos, com a CPU de cada um:

SPOT MIGRATION PLAN

Total workloads:             3
Ready now:                   1
Ready after changes:         1
Manual review:               1
Keep on on-demand:           0

Requested CPU:               5.5 vCPU
Immediately movable to Spot: 1.5 vCPU (27%)
Potential after changes:     3.5 vCPU (64%)
Waiting for a review:        2 vCPU (not in the potential)
  • Ready now: sem bloqueio e sem ressalva. Listados do mais seguro ao menos seguro, uma boa ordem para um piloto.
  • Ready after changes: bloqueios ou ressalvas que uma mudança de configuração resolve. Eles são ordenados por prioridade: a vCPU que mudaria, vezes a nota, vezes a confiança, dividido pelo número de mudanças. Assim, uma carga grande que precisa de uma mudança vem antes de uma pequena que precisa de quatro.
  • Manual review: estado persistente, como um StatefulSet com volumes, ou um banco rodando como Deployment com uma réplica e um volume. Alguns desses podem usar Spot (Kafka com fator de replicação 3, um cluster PostgreSQL distribuído em zonas) e o manifest não mostra quais. O plano não decide: ele lista o que olhar (topologia de storage, replicação e quórum, tempo de recuperação, dependência de zona) e deixa a CPU deles fora do potencial.
  • Keep on on-demand: dado que vive no nó (hostPath), que nenhuma configuração resolve.

Com --suggest, cada achado também imprime um trecho para adaptar, já com os nomes do seu workload, e um link para a documentação. Com --pricing, o plano acrescenta uma estimativa mensal (quanto a CPU solicitada custa hoje, depois da fase 1 e depois da fase 2). A ferramenta não tem preços próprios: você informa, ou ela lê a AWS Price List e o Spot Instance Advisor. É uma estimativa a partir da CPU solicitada, não uma previsão de fatura. A ferramenta nunca altera os seus arquivos.

Quanto dá para confiar em um veredito?

A nota conta o que o manifest mostra. Cada workload também tem uma confiança (alta, média ou baixa) que conta o que ele não consegue mostrar: se a aplicação replica e recupera o próprio estado, se um job faz checkpoint, que nós existem por trás de uma restrição de agendamento. Um Deployment sem estado e sem nada escondido tem confiança alta. Um banco com volume tem confiança baixa, seja qual for a nota. A confiança nunca muda a nota: ela diz qual "pronto" testar primeiro.

O spot-readiness explain SR014 documenta qualquer regra, com uma configuração problemática e uma recomendada. O spot-readiness explain loja/api ./k8s mostra como um workload chegou à nota: os 100 pontos, cada achado e quanto custou, por que o veredito é o que é, e a confiança com os motivos.

Usando em um time

  • Um arquivo de configuração. O .spot-readiness.yaml define limites, exclui namespaces, muda a gravidade de uma regra ou a desliga, e ignora um achado de um workload com uma justificativa escrita, para o time adotar a ferramenta sem aceitar todas as heurísticas.
  • Falhar só em problemas novos. Um projeto que já tem 30 achados não consegue falhar o build por causa deles. O scan --baseline base.json --fail-on-regression compara com um relatório anterior e só falha quando um bloqueio ou uma ressalva é novo. A GitHub Action faz isso sozinha nos pull requests: escaneia a branch base, mostra a variação da nota e os achados novos e resolvidos no comentário, e falha com fail-on-regression: "true".
  • Na linha do manifest. O -f sarif gera um relatório para o GitHub Code Scanning, e cada achado aparece na linha a que se refere, como o container sem readinessProbe. Na Action, upload-sarif: "true" faz o envio no mesmo passo.
  • Um JSON em que dá para confiar. O JSON começa com schemaVersion e toolVersion, e o --baseline recusa um relatório com schema mais novo em vez de comparar com um formato que não consegue ler.

Um caso real

Rodei a ferramenta nos manifests de um ambiente real, com produção e staging, primeiro com as treze regras originais e depois com a versão atual (0.8.0). Os números abaixo são da versão atual. Foram 114 serviços de longa duração analisados, além de 120 Jobs, CronJobs e ScaledJobs (e alguns objetos ignorados, como DaemonSets). Os serviços primeiro:

  • Nenhum workload pronto e 3 prontos com ressalvas. Os outros 111 estavam não prontos. Dois dos três têm um PodDisruptionBudget sem unhealthyPodEvictionPolicy: AlwaysAllow (SR015), uma regra que não existia na primeira rodada, quando um deles contava como pronto.
  • 111 workloads (97%) rodavam com uma única réplica, o que bloqueia pela SR001. Era o problema dominante, e todos os outros achados vinham depois.
  • 75 não tinham readinessProbe (SR006), e 101 não tinham preStop (SR009).
  • 16 usavam volumes persistentes (SR007), 12 tinham um período de desligamento acima de 120 s (SR003) e 14 estavam presos a uma única zona (SR016).
  • O potencial para Spot ficou em 12% da CPU solicitada dos serviços. Jobs, CronJobs e ScaledJobs não entram nessa conta, porque pedem CPU só enquanto rodam.

Os Jobs, CronJobs e ScaledJobs contam outra história: dos 120, 41 estavam prontos, 78 prontos com ressalvas e 1 não pronto (backoffLimit: 0). As ressalvas vinham quase todas de um período de desligamento acima de 120 s (SR003). Mais da metade tinha activeDeadlineSeconds (SR011) e nenhum tinha podFailurePolicy para ignorar interrupções (SR012). Cargas em lote costumam ser as melhores candidatas a Spot, desde que façam checkpoint, e esse é o ponto que a ferramenta não consegue ver.

O plano coloca esses números em ordem. Dos 234 workloads, 41 (todos Jobs) estão prontos agora, 177 estão prontos depois de mudanças e 16 vão para revisão manual: 14 Deployments com uma réplica e um volume (MongoDB, PostgreSQL, RabbitMQ, n8n e SFTP, entre outros) e 2 StatefulSets. Os 16 pedem 4,9 das 12,3 vCPU dos serviços, e são a parte que ninguém decide só pelo manifest. Com o grupo de revisão de fora, mover os serviços depois das mudanças colocaria 60% da CPU solicitada em Spot (7,3 vCPU), contra 12% sem as mudanças. Antes de existir o grupo de revisão, o plano contava esses workloads como "prontos depois de mudanças" e mostrava 94%, o que era otimista demais.

A leitura prática dos serviços: neste ambiente, o primeiro ganho para quem quer usar Spot não é configurar o Karpenter. É dar uma segunda réplica a serviços pequenos e adicionar uma readinessProbe, ajustes baratos que removem o bloqueio principal.

Algumas correções saíram desse teste, e estão na ferramenta: workloads com zero réplica passaram a ser ignorados, e Deployments que o HPA ou o KEDA mantêm com mínimo acima do spec.replicas agora usam esse mínimo. Pods que só rodam uma imagem pause, usados para reservar capacidade para o Karpenter, também são ignorados, porque não são aplicações.

O que ele não faz

  • "Pronto" não é "seguro". A ferramenta só enxerga configuração. Ela não sabe se a aplicação trata SIGTERM, guarda estado em memória ou aguenta reiniciar.
  • As regras são heurísticas. Um workload pode passar em todas e ainda assim ser ruim para Spot. Leia os achados em vez de seguir só a nota.
  • Ela não mede o custo real: para isso existem o OpenCost e o Kubecost. A estimativa do --pricing usa a CPU solicitada e um tipo de instância de referência, e deixa de fora memória, storage, rede e descontos. O spot-readiness responde a outra pergunta, a de quais cargas podem mudar.
  • DaemonSets não são analisados, e a ferramenta não vê se um Job faz checkpoint do progresso.
  • Nos NodePools, ela conta os tipos e as famílias de instância que estão listados. Não tem um catálogo de instâncias, então um pool limitado só por categoria ou tamanho aparece como "any". A interruption queue é lida do controller do Karpenter, não da sua conta AWS.
  • O plano e os grupos são heurísticas tiradas dos manifests. Eles não sabem quanto uma mudança economiza em dinheiro de verdade, nem como a sua aplicação se comporta.
  • Ela é AWS primeiro. As regras de workload valem para qualquer cluster, mas o tipo de capacidade, o tratamento de interrupção e as regras de NodePool leem labels da AWS e do Karpenter. Spot do GKE e do AKS ainda não são reconhecidos.

Próximos passos para o seu cluster

  1. Rode spot-readiness scan e olhe primeiro o que bloqueia, normalmente a SR001.
  2. Corrija o que for barato: réplicas, readinessProbe, preStop e topology spread.
  3. Rode spot-readiness plan para ver o que pode mudar agora e o que manter em On-Demand.
  4. Abra o grupo de revisão manual com quem é dono dos dados: replicação, quórum e tempo de recuperação decidem.
  5. Use a calculadora de economia para estimar o ganho, e o gerador de NodePool do Karpenter para criar um NodePool Spot com fallback para On-Demand.
  6. Mova primeiro as cargas com nota alta, em um ambiente de homologação, e observe as interrupções antes de ampliar.

O código está em github.com/robertasolimandonofreo/spot-readiness, com licença Apache-2.0. Se você quiser que eu revise o seu cluster e monte um plano priorizado, o assessment de eficiência é o ponto de partida.

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.