O blog da AWS

Como Grover economiza custos com 80% de Spot na produção usando o Karpenter com o Amazon EKS

Este post foi escrito em parceria com Suraj Nair, engenheiro sênior de DevOps da Grover.

Introdução

A Grover é líder global em aluguel de tecnologia com sede em Berlim, permitindo que pessoas e empresas assinem produtos de tecnologia mensalmente em vez de comprá-los. Como pioneira na economia circular, o modelo de negócios da Grover de alugar e reformar produtos de tecnologia resulta no uso de dispositivos com mais frequência e por um período mais longo.

Até o momento, a Grover distribuiu mais de 1,2 milhão de dispositivos e é uma das empresas que mais crescem na Europa, atendendo milhões de clientes na Alemanha, EUA, Áustria, Holanda e Espanha. A Grover está executando e gerenciando seus microsserviços usando o Amazon Elastic Kubernetes Service (Amazon EKS) e esta postagem discute como adotamos o Karpenter para otimizar nossos custos da AWS e reduzir a sobrecarga do gerenciamento de nós do Kubernetes.

Desafios

Inicialmente, estávamos usando o Kubernetes Cluster Autoscaler com grupos de nós gerenciados do Amazon EKS para gerenciar nós dentro do cluster EKS. À medida que Grover cresceu em tamanho para ter várias equipes de engenharia, enfrentamos alguns desafios no gerenciamento dessa infraestrutura. Inicialmente, havia apenas dois grandes grupos de nós gerenciados que hospedavam todos os aplicativos em execução em nosso cluster executando principalmente instâncias spot e um pequeno número de nós sob demanda. Isso começou a se tornar um gargalo para as equipes de engenharia de dados e risco da Grover, que precisavam de um conjunto diversificado de tipos e tamanhos de instância para executar serviços com diferentes requisitos de computação sem interrupções. Para atender à crescente demanda das equipes de engenharia por um conjunto diversificado de tipos e tamanhos de instâncias e opções de compra, tivemos que aumentar o número de grupos de nós no cluster. No entanto, tivemos sobrecargas de configuração e gerenciamento quando se tratava de gerenciar o tipo e a quantidade de instâncias no grupo de nós. Muitas vezes observamos nós subutilizados durante o horário comercial normal e tivemos que provisionar em excesso durante o horário comercial de pico e em campanhas como Black Friday e Cyber Monday. Quanto à forma como o Cluster Autoscaler funciona, incluindo a natureza assíncrona dos grupos de escalonamento automático associados aos grupos de nós gerenciados pelo Amazon EKS, o tempo de expansão não se adequaria às nossas demandas de desempenho de aplicativos. Além disso, precisávamos atualizar os grupos de nós regularmente para usar as imagens de máquina da Amazon (AMIs) otimizadas mais recentes para o Amazon EKS e acompanhar as diferentes versões.

Como o Karpenter trabalha na Grover

Devido aos desafios descritos anteriormente, decidimos usar o Karpenter. Começamos a usar o Karpenter NodePools para atender a diferentes requisitos de departamentos/equipes individuais, adicionando manchas e rótulos para que cada serviço possa usar esses rótulos de nós em seus manifestos do Kubernetes. Acabamos tendo menos NodePools do que com nossa configuração anterior. No entanto, como Grover continuou a crescer no tamanho e nos requisitos das equipes de engenharia, adicionamos mais NodePools dedicados à pilha de monitoramento e um NodePool de GPUs.

Em nosso cluster Kubernetes, o KEDA aciona o Horizontal Pod Autoscaler (HPA) para expandir os pods. O Karpenter monitora pods não programáveis e adiciona a capacidade computacional necessária aos nossos clusters. A maior parte da nossa carga de trabalho não tem estado, é tolerante a falhas e é adequada para instâncias spot. Também temos cargas de trabalho com estado, como os notebooks Jupyter, que usam o Amazon Elastic Block Store (Amazon EBS) para armazenar dados e o driver CSI do Amazon EBS para montar os volumes. Para algumas das cargas de trabalho críticas, usamos Pod Disruption Budgets (PDBs) junto com restrições de dispersão de topologia que são respeitadas pela Karpenter.

Nosso cluster EKS agora tem apenas um único grupo de nós gerenciados pela AWS que executa o Karpenter e serviços sempre em execução, como Argocd ou Kyverno. O resto dos nós são gerenciados pelo Karpenter usando NodePools. Adotamos o Karpenter bem cedo (v0.16.1) e estamos acompanhando o projeto com entusiasmo. O arquivo a seguir é uma configuração do Karpenter do tipo NodePool, na qual o Karpenter pode ser configurado. Ele mostra algumas das configurações importantes que usamos com o Karpenter.

Para economizar custos, queríamos usar as instâncias spot do Amazon Elastic Compute Cloud (Amazon EC2) para a maioria das nossas cargas de trabalho. Portanto, especificamos as instâncias sob demanda e spot em nosso Karpenter NodePool (consulte o tipo de capacidade), que não foi possível misturar e combinar em um único local em nossa configuração anterior. Isso resultou em ter menos NodePools do que antes. O Karpenter prioriza as instâncias spot porque só tenta provisionar capacidade computacional sob demanda se não houver nenhum spot disponível. O Karpenter dimensiona corretamente as instâncias por meio de seu recurso de consolidação integrado para economizar custos (consulte Política de consolidação: quando subutilizado). Preferimos não usar instâncias muito grandes para reduzir a densidade de pods em uma única máquina, o que também facilita a remoção rápida e simples de pods antes que um nó spot seja recuperado. Testamos exaustivamente nossa carga de trabalho executando o Karpenter inicialmente sem restrições de tamanho ou tipo de instância por um tempo, e acabamos executando apenas os tipos e tamanhos específicos de instâncias adequados às nossas cargas de trabalho.

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h # refresh nodes every 30 days
  template:
    metadata: {}
    spec:
      nodeClassRef: default
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values:
        - spot
        - on-demand
      - key: kubernetes.io/arch
        operator: In
        values:
        - amd64
      - key: karpenter.k8s.aws/instance-family
        operator: NotIn
        values:
        - t3
        - t3a
        - t4
        - t2
        - i3
        - i4i
        - i3en
        - c6id
        - m6id
        - r6id
        - d3
        - d3en
      - key: karpenter.k8s.aws/instance-hypervisor
        operator: In
        values:
        - nitro
      - key: karpenter.k8s.aws/instance-size
        operator: In
        values:
        - large
        - xlarge
        - 2xlarge
        - 4xlarge
      - key: kubernetes.io/os
        operator: In
        values:
        - linux
Apache Configuration

Para garantir a confiabilidade de nossos serviços, também usamos o Tratamento de Interrupções integrado da Karpenter. Em nosso gráfico do Helm, especificamos o nome da fila do Amazon Simple Queue Service (Amazon SQS) que é necessário para que o Karpenter receba mensagens de notificação, como interrupções de instâncias spot do Amazon EC2. Com isso, o Karpenter drena proativamente um nó, resultando na recriação de pods pelo kube-scheduler em novos nós. Quando a fila precisa ser provisionada com antecedência, usamos o AWS CloudFormation. Há um exemplo de gráfico do Helm no repositório GitHub do Karpenter mostrando o parâmetro InterruptionQueue.

settings:
  clusterName: *clusterName
  clusterEndpoint: https://xxxxxxxxxxxxxxxxxxx.eu-central-1.eks.amazonaws.com
  interruptionQueue: node-termination-handler
Apache Configuration

Para reciclar periodicamente os nós para ter as AMIs mais recentes no cluster Kubernetes, forçamos o encerramento de todos os nós após um tempo de vida (TTL) especificado no NodePool:

disruption:
  expireAfter: 720h # refresh nodes every 30 days
Apache Configuration

Algumas de nossas cargas de trabalho não podem ser reduzidas devido ao longo processamento e, portanto, precisávamos de uma forma de dizer ao Karpenter que evitasse qualquer interrupção nos nós nos quais elas estão sendo executadas. Para isso, o Karpenter fornece a capacidade de especificar um rótulo de não interromper como uma anotação no pod, o que remove o nó em que o pod está sendo executado de qualquer processo de consolidação:

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      annotations:
        karpenter.sh/do-not-disrupt: "true"
Apache Configuration

Para reduzir qualquer perímetro de interrupção quando os nós estão sendo ampliados e reduzidos, também estamos usando PodTopologySpreadConstraints no manifesto do Pod para distribuir o número de pods em diferentes nós de uma topologia específica. Um exemplo dessa configuração pode ser visto a seguir:

spec:
    topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: "topology.kubernetes.io/zone"
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app.kubernetes.io/name: {{ include "servicename" . | lower | quote }}
      - maxSkew: 1
        topologyKey: "kubernetes.io/hostname"
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app.kubernetes.io/name: {{ include "servicename" . | lower | quote }}
Apache Configuration

Migração do Cluster Autoscaler para nós gerenciados pelo Karpenter

A migração dos serviços Kubernetes de nós provisionados por meio do Cluster Autoscaler para nós gerenciados pelo Karpenter foi relativamente livre de estresse e sem tempo de inatividade. Começamos implantando o Karpenter junto com nosso autoescalador de cluster existente e executando ambos em paralelo sem problemas, sem nenhum Karpenter NodePools. Criamos o mesmo número de NodePools com o Karpenter com os mesmos Taints e Node Labels que tínhamos nos grupos de nós gerenciados com o Cluster Autoscaler. Isso garantiu que os aplicativos pudessem tentar agendar em um nó gerenciado do Karpenter ou no Cluster Autoscaler, o que estiver no status Pronto sem nenhum problema. Começamos a reduzir o número de nós nos grupos de nós gerenciados do Amazon EKS, o que daria início à remoção do pod. Durante esse período, quando um nó não estava disponível, os nós gerenciados pelo Karpenter ficavam prontos para programar pods mais rapidamente do que o novo nó gerado pelo Cluster Autoscaler. Devido às várias réplicas em execução para cada serviço combinadas com as restrições de dispersão de topologia, não houve interrupção no desempenho geral dos serviços. Ao repetir isso em pequenas etapas, alteramos o tamanho de contagem desejado de todos os grupos de nós gerenciados do Amazon EKS para 0 e, eventualmente, todos os nós em execução no cluster eram gerenciados pelo Karpenter. Por fim, removemos o Cluster Autoscaler e as definições de grupos de nós gerenciados pelo Amazon EKS do cluster e ajustamos os limites de vCPU e memória para cada NodePool conforme necessário. Agora temos um único grupo de nós gerenciados executando o Karpenter e outras ferramentas de operações, conforme mencionado anteriormente. Testamos e adotamos o Karpenter em nosso cluster de desenvolvimento em aproximadamente 10 dias úteis. Com nosso conhecimento adquirido, a migração do Cluster Autoscaler para o Karpenter levou um único dia para nosso cluster de produção.

Resultados

Usando o Karpenter, conseguimos reduzir significativamente nossa sobrecarga de gerenciamento de clusters do Amazon EKS, juntamente com os seguintes benefícios.

Experimentamos um uso 25% maior do Spot em todos os nossos clusters do Amazon EKS desde a adoção do Karpenter, sem afetar o desempenho da plataforma. O uso do spot altera cerca de 70-80% no máximo. A imagem a seguir é um painel do Grafana em um dia útil normal.

This image shows the percentage of Spot instances in our EKS cluster which alters around 70-80%.

Spot Node percentage alters around 70-80% in our EKS cluster.

A porcentagem do Spot Node se altera em torno de 70-80% em nosso cluster EKS.

Apoiando nossas metas de otimização de custos, uma vez que adotamos o Karpenter em todos os nossos clusters Kubernetes, experimentamos uma maior proporção de instâncias spot em comparação com instâncias sob demanda nos custos gerais de gastos do Amazon EC2. A flexibilidade da Karpenter de especificar as duas opções de compra no NodePools , o recurso de consolidação e a estratégia de alocação spot do Amazon EC2 ajudam a reduzir custos. A Karpenter usa o pool de preços mais baixos para uma estratégia de alocação otimizada sob demanda e de preço-capacidade para instâncias spot.

This image shows the ratio of Spot vs. On-demand instances in our total EC2 costs.

A proporção de instâncias spot versus sob demanda em nossos custos totais do EC2.

Especificamos as instâncias Amazon EC2 On-demand e Amazon EC2 Spot, e nos beneficiamos da priorização spot da Karpenter. Veja a seguir a taxa de distribuição de pods para instâncias sob demanda e spot. Como pode ser visto no diagrama, os pods em nós sob demanda são mais estáveis e têm menos interrupções quando comparados aos pods em instâncias spot.

This image shows the distribution of Pod disruptions on Spot vs. On-demand instances.

Distribuição de interrupções de pod em instâncias spot versus sob demanda.

Conclusão

Usando o Karpenter na Grover, conseguimos reduzir significativamente nossa sobrecarga de gerenciamento de clusters do Amazon EKS combinada com a economia de custos do Amazon EC2 a longo prazo. Com o Karpenter, aumentamos nosso uso Spot em 25% e agora usamos cerca de 80% de instâncias Spot na produção, e nossos clientes têm uma melhor experiência com a alta demanda sazonal, como Black Fridays. Também reduziu a sobrecarga operacional que tínhamos com o gerenciamento de grupos de nós e do autoescalador de cluster anteriormente, e foi comprovado que era mais rápido adicionar novos nós ao cluster. Isso permitiu que nossa operação de aumento de escala fosse mais suave do que nunca. Recentemente, conduzimos uma avaliação e agora estamos usando o Amazon Managed Service for Prometheus, um serviço gerenciado da Prometheus. Em seguida, queremos fortalecer nosso processo de AppSec para melhorar nossa postura de segurança.

Grover Team

Grover Team

Equipe Grover

Este blog é uma tradução do blog original em inglês (link aqui).

Biografia dos autores

Suraj Hair, Grover

Suraj Nair é engenheiro sênior de DevOps na Grover Group GmbH e é apaixonado por trabalhar em soluções que permitam que as equipes trabalhem com eficiência e em grande escala. Quando não está trabalhando, ele está interessado em viajar e explorar lugares.

Mehdi Yosofie

Mehdi Yosofie é arquiteto de soluções na Amazon Web Services e apoia startups a escalarem e crescerem na AWS. Ele fornece orientação aos clientes da AWS sobre suas cargas de trabalho em uma variedade de tecnologias da AWS. Ele faz parte da comunidade de campo técnico de contêineres da AWS.


Biografia do tradutor

Daniel Abib é arquiteto de soluções sênior na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem.

https://www.linkedin.com/in/danielabib/

Biografia do Revisor

Joao Melo é Arquiteto de Soluções atendendo clientes Enterprise com foco em mercado financeiro. Com mais de 10 anos de experiência, João iniciou sua carreira como desenvolvedor Java e .NET, e posteriormente se especializou em infraestrutura como Engenheiro de Sistemas na Cisco Systems. Formado pela FEI, é entusiasta de Containers, DeFi, Sistemas de Pagamento e apaixonado por cinema e jogos.