Quais workloads do seu cluster podem rodar em Graviton? Uma ferramenta open source para descobrir
"Migre para Graviton" é um conselho comum de FinOps na AWS. Os nós arm64 costumam custar menos por unidade de trabalho, e o Karpenter sabe misturar as duas arquiteturas. O que quase nunca se diz é como descobrir quais workloads do seu cluster podem ir, e o palpite errado tem uma cara conhecida: um pod que reinicia em loop com exec format error.
O graviton-readiness é uma ferramenta de linha de comando, open source, que transforma essa pergunta em uma checagem objetiva. Ela olha as duas coisas que decidem se um pod roda em arm64: se a configuração de agendamento deixa o pod chegar a um nó arm64, e se cada imagem foi publicada para linux/arm64.
O que ele verifica
Ele lê Deployments, StatefulSets, DaemonSets, Jobs, CronJobs e Pods, a partir de arquivos YAML ou JSON, e aplica onze regras. Cada workload recebe uma nota e um veredito.
| Regra | Gravidade | O que checa |
|---|---|---|
| GR001 | Bloqueia | Preso a amd64 por kubernetes.io/arch em um nodeSelector ou em uma afinidade obrigatória |
| GR002 | Bloqueia | Só aceita tipos de instância x86 (seletor de tipo de instância ou família do Karpenter) |
| GR003 | Bloqueia | A referência da imagem diz amd64 (app:1.4-amd64, amd64/ubuntu) |
| GR004 | Bloqueia | A imagem não tem manifest linux/arm64 (exige --check-images) |
| GR005 | Informativa | Não foi possível verificar a arquitetura da imagem (exige --check-images) |
| GR006 | Ressalva | Comando, argumentos ou variáveis de ambiente citam amd64 ou x86_64 |
| GR007 | Ressalva | Pede GPU |
| GR008 | Informativa | DaemonSet: roda em todo nó, então as imagens precisam ser multi-arch antes |
| GR009 | Bloqueia | Mira nós Windows |
| GR010 | Bloqueia | Escolhe um NodePool do Karpenter que só provisiona amd64 |
| GR011 | Informativa | Escolhe um NodePool que não está entre os manifests lidos |
Os init containers são checados como os demais. O graviton-readiness explain GR004 documenta qualquer regra, com o problema e a correção.
Duas regras merecem um comentário. A GR001 e a GR002 olham o agendamento: um nodeSelector fixo em kubernetes.io/arch: amd64 impede o scheduler de colocar o pod em um nó arm64, mesmo que a imagem seja multi-arch. Nas afinidades, os termos obrigatórios são combinados com OU, então um workload que lista amd64 em um termo e arm64 em outro pode rodar nos dois. A GR002 reconhece as famílias Graviton pelo nome (m7g, c6gn, x2gd, im4gn, is4gen, g5g, hpc7g e a primeira geração, a1) e só bloqueia quando todas as opções permitidas são x86.
O NodePool também decide
Um pod que escolhe os nós por karpenter.sh/nodepool, ou por um label que o NodePool coloca nos nós (role: web), tem a arquitetura desse pool, e o próprio pod não diz qual é. Por isso a imagem pode ser multi-arch e o workload ainda assim nunca chegar a um nó Graviton.
Quando a pasta inclui os manifests de NodePool do Karpenter, a GR010 liga cada workload aos pools em que ele pode cair (pelo nodeSelector e pela afinidade obrigatória) e acusa quando nenhum deles provisiona arm64. A GR011 avisa quando o workload escolhe um pool que não está entre os manifests lidos. Um pool só conta como amd64 quando o manifest prova: um kubernetes.io/arch In [amd64], ou tipos e famílias de instância que são todos x86.
graviton-readiness scan ./k8s ./karpenterUm pool que não declara arquitetura não é presumido como amd64: a documentação do Karpenter não promete um padrão, então a regra só fala quando há prova.
E se não houver NodePool na análise? A regra não roda, e a ferramenta avisa: o resumo conta os workloads que escolhem nós por um label que nenhum pool lido define. É o caso quando os pools não são do Karpenter (um managed node group da AWS, um pool do GKE ou do AKS) ou ficam fora da pasta. Nesse caso você declara a arquitetura com --node-pool chave=valor:arch, repetível:
graviton-readiness scan ./k8s \
--node-pool eks.amazonaws.com/nodegroup=general:amd64 \
--node-pool cloud.google.com/gke-nodepool=arm-pool:arm64O EC2NodeClass também entra: um NodePool cuja classe usa uma família de AMI Windows (amiFamily: Windows2022, ou um alias como windows2022@latest) só provisiona amd64, qualquer que seja o kubernetes.io/arch do próprio pool, e a GR010 explica o motivo. A arquitetura de uma AMI customizada (Custom, ou escolhida por id ou tags) está na AWS e não no manifest, então não é lida.
As imagens são a parte que decide
A configuração pode estar limpa e o pod ainda assim falhar, porque a imagem só existe para amd64. Por isso a ferramenta tem a flag --check-images.
graviton-readiness scan --check-images ./k8sCom ela, a ferramenta lê o manifest de cada imagem no registry, nunca as camadas, e procura uma entrada linux/arm64. Alguns detalhes que importam:
- Credenciais. Ela usa as do seu Docker config e dos credential helpers, que é como ECR, GCR e registries privados funcionam. Cada imagem distinta é consultada uma vez.
- Atestações. O BuildKit adiciona entradas
unknown/unknownao lado das imagens reais. Elas não são plataformas, e a ferramenta as ignora. - O que o registry não responde. Sem credencial, sem rede ou com uma referência parametrizada (
${REGISTRY}/app:1), a imagem fica como não verificada (GR005), e o workload ficaunverified. A ferramenta nunca assume que está tudo bem.
Sem --check-images, nada vai à rede, e nenhum workload é dado como `ready`. Uma configuração limpa fica unverified, porque nada confirma que as imagens rodam em arm64. É uma escolha de projeto: um "pronto" que ninguém verificou seria pior do que nenhum veredito.
Como usar
# arquivos, Helm ou kustomize
graviton-readiness scan ./k8s
helm template minha-release ./chart | graviton-readiness scan -
# lendo as imagens no registry
graviton-readiness scan --check-images ./k8s
# os workloads e os NodePools do Karpenter juntos
graviton-readiness scan ./k8s ./karpenter
# o plano, o cluster vivo e o SARIF
graviton-readiness plan --check-images ./k8s ./karpenter
graviton-readiness scan --cluster
graviton-readiness scan -f sarif ./k8s > gr.sarif
# saída para CI e pull request
graviton-readiness scan --check-images --fail-on not-ready ./k8s
graviton-readiness scan -f markdown ./k8s
graviton-readiness scan -f json ./k8sO --fail-on aceita not-ready, unverified ou caveats, e cada nível inclui os anteriores. Os códigos de saída são 0 (sucesso), 1 (o nível pedido foi atingido) e 2 (erro de uso ou de execução).
Um exemplo de saída
Este é o resultado real sobre o testdata do repositório, que é um namespace inventado, sem --check-images:
VERDICT SCORE WORKLOAD REPLICAS CPU FINDINGS
unverified 100 kube-system/daemonset/log-agent - - GR008
unverified 100 shop/cronjob/report - - -
not-ready 60 shop/deployment/api 2 1000m GR001
unverified 100 shop/deployment/web 3 500m -
not-ready 50 shop/deployment/worker 2 250m GR003,GR006
5 workloads: 0 ready, 0 with caveats, 2 not ready, 3 unverified
Graviton potential: 0% of requested CPU (workloads ready or with caveats).
Unverified: 37% of requested CPU could move if the images support arm64.
Images were not checked: run with --check-images to read their platforms from the registry.A leitura é direta. O api está preso a amd64 por um nodeSelector (GR001), e o worker tem uma tag -amd64 (GR003) e baixa um binário linux_amd64 nos argumentos (GR006). O web não tem nenhum bloqueio, mas ninguém verificou a imagem, então ele é unverified, e é por isso que o resumo mostra 0% de potencial confirmado e 37% da CPU que ainda depende das imagens.
O plano de migração
O graviton-readiness plan transforma os vereditos em uma ordem de trabalho, com a CPU de cada grupo:
- Pronto agora: sem bloqueio nem ressalva, com todas as imagens verificadas para arm64. O primeiro movimento e um bom piloto.
- Pronto depois de uma conferência: pronto, com uma ressalva que uma pessoa precisa olhar (uma arquitetura escrita em um comando, uma GPU).
- Pronto depois de mudanças: bloqueios que uma mudança no manifest ou no build da imagem remove. Eles são ordenados pela CPU que se moveria dividida pelo número de mudanças: uma carga grande com uma mudança vem antes de uma pequena com quatro. É uma forma de ordenar o trabalho, não uma previsão de economia.
- Verificar antes: nada bloqueia na configuração, mas as imagens não foram checadas, ou o registry não respondeu. Nunca entra no potencial.
- Manter em amd64: precisa de uma arquitetura que o Graviton não tem (Windows).
- Já em Graviton: só pode cair em nós arm64 (seleciona arm64, ou um pool que só tem nós arm64) e não tem bloqueio. Está feito, então fica fora do que falta mover: sem esse grupo, os workloads já migrados apareceriam como "prontos agora".
Cada mudança é a primeira frase da correção da regra, com o que a regra achou. Esta é a saída real sobre o testdata do repositório, sem --check-images:
GRAVITON MIGRATION PLAN
Total workloads: 5
Ready now: 0
Ready after a check: 0
Ready after changes: 2
Verify first: 3
Keep on amd64: 0
Requested CPU: 4.0 vCPU
Movable now: 0.0 vCPU (0%)
Potential after changes: 2.5 vCPU (62%)
To verify first: 1.5 vCPU (37%), not counted in the potential
Phase 1: Ready after changes
blockers that a change in the manifest or in the image build removes. The best return for the effort first.
1. shop/deployment/api (2.0 vCPU)
GR001 Remove the kubernetes.io/arch constraint, or allow both architectures.
2. shop/deployment/worker (0.5 vCPU)
GR003 Use the multi-arch tag, the one without the architecture in it, or the arm64 build, and keep the tag identical across architectures so one manifest serves both.
Phase 2: Verify first
nothing blocks it in the configuration, but its images were not checked, or the registry did not answer.
shop/deployment/web (1.5 vCPU)
Run with --check-images, to read the platforms of its images from the registry.
kube-system/daemonset/log-agent
Run with --check-images, to read the platforms of its images from the registry.
shop/cronjob/report
Run with --check-images, to read the platforms of its images from the registry.
Images were not checked: run with --check-images to read their platforms from the registry.Cluster vivo, só leitura
Com --cluster, a ferramenta lê Deployments, StatefulSets, DaemonSets, CronJobs, Argo Rollouts, NodePools do Karpenter, EC2NodeClasses e os nós direto do cluster, em vez de arquivos. Só chamadas list são enviadas (os testes conferem que nenhum outro verbo é usado), paginadas de 500 em 500 e com poucas requisições por segundo, um recurso por vez. Um recurso que não está instalado é pulado com uma nota.
graviton-readiness scan --cluster
graviton-readiness plan --cluster --check-imagesOs rótulos dos nós resolvem o que os manifests não resolvem: os pools que não são do Karpenter. Um managed node group da AWS, um pool do GKE ou do AKS ganha a arquitetura que os próprios nós informam, e a mensagem diz que foi observado (observed on the cluster: all 2 node(s) labeled eks.amazonaws.com/nodegroup=core are amd64). É um retrato dos nós de hoje: um pool escalado a zero não diz nada, e um pool com as duas arquiteturas não bloqueia.
SARIF e GitHub Action
-f sarif gera um log SARIF 2.1.0 para o code scanning do GitHub. Cada achado aponta a linha do campo que o causou (o image:, o seletor de nó), e a impressão digital ignora a linha, então um alerta sobrevive a uma edição que desloca o manifest. O log foi validado contra o schema oficial do SARIF.
O repositório também é uma GitHub Action. Ela instala a release com verificação de checksum e roda uma varredura que escreve todos os relatórios, então os registries são consultados uma vez. Comenta o relatório e, se você pedir, o plano no pull request: cada comentário é achado por um marcador, e a Action nunca edita o comentário de outra ferramenta. Sobe o SARIF e só falha a pipeline no nível do fail-on depois dos comentários.
- uses: robertasolimandonofreo/graviton-readiness@v0.3.2
with:
path: ./k8s ./karpenter
check-images: "true"
comment: "true"
comment-plan: "true"
fail-on: not-readyComo ler os números
O resumo pondera os vereditos pela CPU que os workloads pedem. Dois pontos para não se enganar:
- Só serviços de longa duração entram. Os números cobrem Deployments, StatefulSets e Pods. Um DaemonSet não tem contagem de réplicas para ponderar, e um Job pede CPU só enquanto roda.
- O não verificado fica separado. O potencial Graviton é a CPU de workloads prontos ou prontos com ressalvas, com as imagens verificadas. A CPU
unverifiedé mostrada à parte e nunca é somada a ele.
Um erro comum em clusters mistos
Quando o cluster tem nós x86 e arm64, um workload sem restrição de arquitetura e com uma imagem só amd64 pode ser agendado em um nó arm64 e falhar. A GR004 avisa quando é esse o caso: o workload não está preso a amd64, então pode cair em um nó arm64. Enquanto a imagem não for multi-arch, fixe kubernetes.io/arch: amd64 no workload.
Para construir uma imagem que sirva às duas arquiteturas:
docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/api:1.4 --push .Mantenha a mesma tag nas duas arquiteturas, para que um único manifest sirva às duas, e remova o -amd64 do nome.
O que ele não faz
- Pronto não é seguro. A ferramenta lê configuração e o registry. Ela não sabe se a aplicação tem dependências nativas, JIT ou assembly que se comportam diferente em arm64. Teste um workload pronto em um nó Graviton antes de mover produção.
- Uma imagem com `linux/arm64` foi publicada para arm64, o que não garante que funcione.
- Não mede custo nem desempenho. Ela diz se o workload pode rodar em Graviton, não se vale a pena para ele.
- Sem values do Helm. Renderize os charts com
helm template. Os NodePools do Karpenter são lidos dos manifests ou do cluster, e os outros pools vêm dos nós (com--cluster) ou são declarados com--node-pool. O EC2NodeClass só prova Windows: a arquitetura de uma AMI customizada está na AWS e não é lida. - As regras são heurísticas. A GR006 procura o texto
amd64oux86_64, então pode deixar passar uma arquitetura escondida ou apontar uma menção inofensiva. - AWS primeiro. As regras de tipo de instância conhecem as famílias Graviton. Ampere no Azure e Axion no Google Cloud não são reconhecidos pela GR002.
A ferramenta foi testada com um registry em memória, que cobre índices multi-arch, imagens de uma plataforma só, atestações e referências inválidas, e também contra imagens públicas reais.
Próximos passos para o seu cluster
- Rode
graviton-readiness scan --check-imagese olhe primeiro o que bloqueia. - Corrija o que for barato: tirar o nodeSelector
amd64, apontar o workload para um NodePool arm64, trocar a tag-amd64pela multi-arch, publicar a imagem combuildx. - Resolva os
unverified: faça login no registry e rode de novo. - Mova primeiro workloads prontos e sem estado, em homologação, e observe antes de ampliar.
- Use a calculadora de economia em Kubernetes, que tem a alavanca de Graviton, para estimar o ganho, e o gerador de NodePool do Karpenter para criar um pool que aceite as duas arquiteturas.
Graviton e Spot se combinam, e o spot-readiness responde à pergunta equivalente para Spot. Antes dos dois, vale reduzir requests inflados: com requests justos, a consolidação do Karpenter tem o que empacotar.
A página da ferramenta, com instalação e exemplo, está em /ferramentas/graviton-readiness, e o código em github.com/robertasolimandonofreo/graviton-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.