Voltar para o blog

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.

RegraGravidadeO que checa
GR001BloqueiaPreso a amd64 por kubernetes.io/arch em um nodeSelector ou em uma afinidade obrigatória
GR002BloqueiaSó aceita tipos de instância x86 (seletor de tipo de instância ou família do Karpenter)
GR003BloqueiaA referência da imagem diz amd64 (app:1.4-amd64, amd64/ubuntu)
GR004BloqueiaA imagem não tem manifest linux/arm64 (exige --check-images)
GR005InformativaNão foi possível verificar a arquitetura da imagem (exige --check-images)
GR006RessalvaComando, argumentos ou variáveis de ambiente citam amd64 ou x86_64
GR007RessalvaPede GPU
GR008InformativaDaemonSet: roda em todo nó, então as imagens precisam ser multi-arch antes
GR009BloqueiaMira nós Windows
GR010BloqueiaEscolhe um NodePool do Karpenter que só provisiona amd64
GR011InformativaEscolhe 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 ./karpenter

Um 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:arm64

O 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 ./k8s

Com 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/unknown ao 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 fica unverified. 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 ./k8s

O --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-images

Os 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-ready

Como 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 amd64 ou x86_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

  1. Rode graviton-readiness scan --check-images e olhe primeiro o que bloqueia.
  2. Corrija o que for barato: tirar o nodeSelector amd64, apontar o workload para um NodePool arm64, trocar a tag -amd64 pela multi-arch, publicar a imagem com buildx.
  3. Resolva os unverified: faça login no registry e rode de novo.
  4. Mova primeiro workloads prontos e sem estado, em homologação, e observe antes de ampliar.
  5. 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.

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.