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.
| Regra | Gravidade | O que checa |
|---|---|---|
| SR001 | Bloqueia | Menos de 2 réplicas (o mínimo do HPA conta) ou Pod avulso |
| SR002 | Bloqueia | Volumes hostPath |
| SR003 | Ressalva | terminationGracePeriodSeconds acima de 120 s |
| SR004 | Ressalva | Várias réplicas sem topology spread nem anti-afinidade |
| SR005 | Ressalva | Várias réplicas sem PodDisruptionBudget |
| SR006 | Ressalva | Container sem readinessProbe |
| SR007 | Ressalva | Volumes persistentes |
| SR008 | Informativa | Container sem requests de CPU e memória |
| SR009 | Informativa | Nenhum hook preStop |
| SR010 | Bloqueia | Job com backoffLimit: 0: um pod perdido derruba o job inteiro |
| SR011 | Informativa | Job com activeDeadlineSeconds: as novas tentativas após uma interrupção contam no prazo |
| SR012 | Informativa | Job sem podFailurePolicy que ignore DisruptionTarget: uma interrupção pode contar contra o backoffLimit |
| SR013 | Informativa | O progresso se perde na interrupção, a menos que o job faça checkpoint (a ferramenta não consegue verificar) |
| SR014 | Bloqueia | PodDisruptionBudget que não permite nenhuma disrupção (por exemplo minAvailable igual às réplicas) |
| SR015 | Ressalva | PodDisruptionBudget sem unhealthyPodEvictionPolicy: AlwaysAllow: um pod não saudável pode travar o drain |
| SR016 | Ressalva | Restrito a uma única Zona de Disponibilidade |
| SR017 | Ressalva | Exige um tipo de instância específico, ou fixa em um nó pelo hostname |
| SR018 | Informativa | Limita a flexibilidade de capacidade Spot: poucos tipos ou famílias de instância, ou GPUs |
| SR019 | Ressalva | nodeSelector 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 ./k8sNo 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 highO 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 regrasKR: 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 deminValues.
INTERRUPTION HANDLING
Karpenter detected ✓
Interruption queue configured ✓
Node Termination Handler -
PDB issues 2
Workloads >120s termination 1
Cluster interruption readiness: 86/100Os 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.yamldefine 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-regressioncompara 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 comfail-on-regression: "true". - Na linha do manifest. O
-f sarifgera um relatório para o GitHub Code Scanning, e cada achado aparece na linha a que se refere, como o container semreadinessProbe. Na Action,upload-sarif: "true"faz o envio no mesmo passo. - Um JSON em que dá para confiar. O JSON começa com
schemaVersionetoolVersion, e o--baselinerecusa 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 tinhampreStop(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
--pricingusa 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
- Rode
spot-readiness scane olhe primeiro o que bloqueia, normalmente a SR001. - Corrija o que for barato: réplicas,
readinessProbe,preStope topology spread. - Rode
spot-readiness planpara ver o que pode mudar agora e o que manter em On-Demand. - Abra o grupo de revisão manual com quem é dono dos dados: replicação, quórum e tempo de recuperação decidem.
- 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.
- 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.
Artigos relacionados
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 conversaReceba os novos artigos por e-mail
Um e-mail quando sair artigo novo sobre infraestrutura, backend e IA. Sem spam, cancele quando quiser.