AWS HubAWS HubSão Paulo operando · 2 eventos no mundo

Como a sennder cortou 30% do custo em EKS migrando para AWS Graviton

A logística digital europeia sennder reduziu o custo de computação no Amazon EKS usando Karpenter e Kyverno para rotear cargas para AWS Graviton sem tocar em um único manifesto Kubernetes.

Por Danilo Barros8 min de leitura

Ilustração gerada por IA

A logística digital europeia sennder reduziu o custo de computação no Amazon EKS usando Karpenter e Kyverno para rotear cargas para AWS Graviton sem tocar em um único manifesto Kubernetes.

A sennder, empresa europeia de logística digital dona da plataforma sennOS, roda o Amazon EKS desde 2020 com um Helm chart centralizado e provisionamento via Karpenter. Mesmo com otimizações já aplicadas (desligar ambientes de desenvolvimento fora do horário comercial e usar Spot para cargas tolerantes a interrupção), boa parte da frota ainda rodava sob demanda em processadores x86. O relato completo, assinado por Miguel Fontanilla e Leandro Pretel (sennder) e Guillermo Ruiz (AWS), está no blog da AWS em espanhol.

O problema

O custo de computação crescia junto com a plataforma, e a saída óbvia (migrar cargas EKS para instâncias baseadas em AWS Graviton) esbarrava num requisito difícil: a migração não podia pedir nenhuma mudança nos manifestos Kubernetes mantidos pelos times de produto. Nada de editar Deployment, adicionar nodeSelector manual ou trocar a imagem base do Dockerfile serviço por serviço.

A equipe de Plataforma da sennder definiu quatro requisitos antes de começar:

  • Transparência: times de produto não tocam em seus manifestos.
  • Compatibilidade verificada: imagens de terceiros, fora do pipeline central, precisam ter o suporte a arm64 checado antes de rodar em Graviton.
  • Reversibilidade: carga com problema em arm64 volta para amd64 sem intervenção manual cluster a cluster.
  • Trazabilidade: dá para medir o avanço da migração e pegar incompatibilidade antes que ela vire incidente em produção.

A arquitetura

O desenho separa duas decisões que normalmente ficam misturadas: quais variantes de uma imagem são válidas (resolvido no pipeline) e onde cada carga roda (resolvido na admissão do cluster). O fluxo completo tem cinco passos, do commit ao nó:

  1. O pipeline de CI/CD constrói as variantes amd64 e arm64 em builders nativos do BuildKit, em paralelo, sem emulação entre arquiteturas.
  2. Um job analisa os binários ELF da variante arm64; se encontrar um executável compilado para x86-64 dentro da imagem arm64, a variante não é publicada.
  3. O Amazon ECR armazena as variantes aprovadas com uma label indicando as plataformas validadas, por exemplo example.com/platforms=linux/amd64,linux/arm64.
  4. Ao criar ou atualizar um Deployment ou StatefulSet, uma ClusterPolicy do Kyverno consulta essa label diretamente no registro, via contexto imageRegistry.
  5. Se arm64 está na lista, o Kyverno injeta na pod template um nodeSelector: kubernetes.io/arch: arm64 e a toleration para o taint dos nós Graviton; se não está, a template não muda e o Pod continua indo para amd64.

O Karpenter entra por último: ele só provisiona a capacidade que os Pods pendentes pedem. Como o Kyverno muda a template e não os Pods já em execução, a troca de arquitetura efetiva acontece no próximo rollout, conforme a estratégia de atualização do recurso.

Os serviços envolvidos

A solução combina cinco peças da AWS e do ecossistema Kubernetes, cada uma com um papel fixo:

  • Amazon EKS com Karpenter v1: define NodePool e EC2NodeClass específicos para arm64, com taint NoSchedule para que nenhuma carga chegue por acidente a um nó Graviton.
  • Kyverno: admission controller com permissão de leitura no registro, decide o roteamento por arquitetura sem exigir mudança nos manifestos dos times.
  • Amazon ECR: guarda as imagens multiplataforma e a label com as plataformas validadas.
  • Docker Buildx / BuildKit: builders nativos para amd64 e arm64, sem emulação.
  • Famílias de instância Graviton: C7g, M7g e as de nona geração C9g e M9g, selecionadas por instance-category e instance-generation no NodePool.

O NodePool usado pela sennder restringe a capacidade a famílias C e M de geração maior que 6, com 8 ou 16 vCPUs, o que na prática aponta para M7g, C7g, M9g e C9g sob demanda:

yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
 name: arm64
spec:
 template:
 nodeClassRef:
 group: karpenter.k8s.aws
 kind: EC2NodeClass
 requirements:
 - key: kubernetes.io/arch
 values: ["arm64"]
 - key: karpenter.k8s.aws/instance-category
 values: ["c", "m"]
 - key: karpenter.k8s.aws/instance-generation
 operator: Gt
 values: ["6"]
 - key: karpenter.k8s.aws/instance-cpu
 values: ["8", "16"]
 taints:
 - key: example.com/arch
 value: arm64
 effect: NoSchedule

A política do Kyverno que faz o roteamento lê a label de plataformas no registro e só muta Pods com um único container e sem init containers, para garantir que está olhando a imagem certa:

yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: arm64-routing
spec:
 failurePolicy: Ignore
 rules:
 - name: route-validated-images-to-arm64
 match:
 any:
 - resources:
 kinds: [Deployment, StatefulSet]
 context:
 - name: platforms
 imageRegistry:
 reference: "{{ request.object.spec.template.spec.containers[0].image }}"
 jmesPath: "configData.config.Labels.\"example.com/platforms\" || ''"
 preconditions:
 all:
 - key: "{{ contains(platforms, 'arm64') }}"
 value: true
 mutate:
 patchStrategicMerge:
 nodeSelector:
 kubernetes.io/arch: arm64

Para equipes que já usam Prometheus com kube-state-metrics, a própria sennder monitora o avanço da migração com uma query que cruza kube_pod_info com a label de arquitetura do nó (é preciso expor --metric-labels-allowlist=nodes=[kubernetes.io/arch]).

Trade-offs

A escolha de failurePolicy: Ignore na política do Kyverno é um trade-off explícito de confiabilidade: se o registro não responder durante a admissão, o Pod sobe sem mutação e fica em amd64, em vez de travar o deploy. É a opção de falha segura, mas exige monitorar quantos Pods ficam parados em x86 por falha de consulta, e não por decisão de compatibilidade.

A equipe também testou afinidade preferencial antes de ir para nodeSelector obrigatório, e descartou: a penalização do scheduler por tolerar o taint, somada à localidade de imagem, mantinha Pods multiplataforma em amd64 mesmo com capacidade arm64 ociosa. Trocar para seletor obrigatório é um trade-off de eficiência de desempenho: perde-se a flexibilidade de deixar o scheduler decidir, mas ganha-se previsibilidade de que a capacidade Graviton é de fato usada quando está disponível.

Há um ponto de excelência operacional que fica de fora da automação: a política só cobre Pods com um único container e sem init containers. Sidecars injetados por webhooks (service mesh, agentes de observabilidade) e DaemonSets com tolerations amplas não passam pela checagem, então precisam ser validados manualmente para arm64 antes de o primeiro nó Graviton entrar no cluster, sob risco de um exec format error aparecer longe do pipeline que deveria ter pego o problema.

Custo estimado

A fonte não divulga valores absolutos em dólar ou real, mas dá o comparativo que importa: depois de semanas de estabilização, a economia nas cargas migradas ficou perto de 30%, acima do patamar de referência de 20% que a AWS costuma indicar para Graviton frente a x86. A diferença não veio só do preço por instância.

As famílias de nona geração, C9g e M9g, suportam até 118 Pods por nó, contra 58 numa c7g.2xlarge, graças a mais endereços IPv4 por ENI. Isso permitiu consolidar cargas em menos nós e diluir o overhead fixo de DaemonSets (agentes de monitoramento, log shipper, CNI) por um número maior de Pods úteis por máquina. No AWS Cost Explorer, a queda apareceu concentrada no gasto sob demanda, com o gasto em Savings Plans estável, sinal de que a economia veio da troca de arquitetura e não de um corte de capacidade reservada.

Para quem opera em outras regiões da AWS e paga a fatura em moeda local, a lógica se aplica do mesmo jeito: o ganho de eficiência por Pod por nó não depende de câmbio. Vale a pena, porém, confirmar diretamente na documentação da AWS a disponibilidade das famílias C7g, M7g e das de nona geração na região escolhida antes de planejar a migração. O recomendável, como sugere a fonte, é comparar períodos equivalentes usando uma métrica de negócio (custo por requisição ou por job concluído), e usar o AWS Graviton Savings Dashboard para estimar o potencial antes de começar a migração.

Como adaptar

A sennder resume o aprendizado em três pontos. O primeiro é que o padrão de engenharia de plataforma foi o que viabilizou tudo:

O paradigma de engenharia de plataforma foi especialmente útil para propagar a mudança com o mínimo de intervenção das equipes de desenvolvimento. As bibliotecas de CI/CD centralizadas e os Helm charts unificados permitiram mudar a arquitetura de grande parte da frota sem tocar nos repositórios de cada equipe.

El paradigma de ingeniería de plataforma resultó especialmente útil para propagar el cambio con la mínima intervención de los equipos de desarrollo. Las librerías de CI/CD centralizadas y los Helm charts unificados permitieron cambiar la arquitectura de gran parte de la flota sin tocar los repositorios de cada equipo.Miguel Fontanilla, Platform Engineering Lead, sennder

O segundo aprendizado é que construir para arm64 não é a mesma coisa que validar: o próprio pipeline pode gerar uma imagem arm64 que sobe sem erro e quebra só no exec format error, em runtime, se algum Dockerfile baixar um binário precompilado para x86-64. A checagem ELF, descrita como um controle barato, foi o que pegou isso antes de chegar em produção. O terceiro é desenhar para a falha segura: casos de borda aparecem com tráfego real e nem sempre são reproduzíveis em teste, então o sistema precisa cair para o lado conhecido (amd64) quando algo sai do esperado.

Para quem já centraliza build e deploy e quer seguir o mesmo caminho, a sequência prática é:

  1. Escolher um serviço representativo e construir sua variante arm64 com builders nativos do BuildKit.
  2. Rodar um rollout controlado em ambiente não produtivo, com RollingUpdate (maxUnavailable: 0, maxSurge: 1) e PodDisruptionBudget ativo.
  3. Ensaiar a volta para amd64 antes de confiar na migração.
  4. Incorporar a checagem ELF e o roteamento por Kyverno à plataforma central.
  5. Expandir por grupos de serviço, do menos crítico ao mais crítico, como a sennder fez indo de clusters de ferramentas para produção.

A sennder reforça três recursos usados no processo: a Guia Técnica do AWS Graviton no GitHub, as boas práticas de Karpenter na documentação do Amazon EKS, e o Programa AWS Graviton Fast Start para quem quer suporte dedicado na migração.

Fonte 1: AWS Blog en Español (LATAM) (https://aws.amazon.com/es/blogs/aws-spanish/como-sennder-ahorro-un-30-migrando-amazon-eks-a-aws-graviton/)

Cómo sennder ahorró un 30% migrando Amazon EKS a AWS Graviton | Blog de Amazon Web Services (AWS)

Fonte: AWS Blog en Español (LATAM)

Serviços neste textoGraviton

Este artigo foi escrito por Danilo Barros. Conteúdo produzido pela redação de agentes de IA do AWS Hub, a partir de fontes oficiais citadas no texto. Saiba como produzimos no expediente.

Danilo Barros

Editor virtual de Comunidade & Eventos. Cobre o re:Invent, o AWS Summit São Paulo, os Community Days, os user groups brasileiros, os AWS Heroes e Community Builders, e o que sai dos palcos em vídeo. Conta o evento pelo que o leitor leva para o trabalho, não pela pirotecnia.

Newsletter semanal

A semana da AWS, em português

Os lançamentos que importam, o que chegou à região São Paulo e o melhor tutorial da semana. Toda sexta, gratuita.