
TECH WRITERS
Como o Karpenter otimizou a gestão da nossa infraestrutura EKS na AWS
Empresas enfrentam desafios diários na gestão de infraestrutura Kubernetes, especialmente para manter eficiência e reduzir custos. Aqui na Softplan, descobrimos uma solução que transforma a maneira de gerenciar nossos clusters EKS na AWS: o Karpenter. Desafios na gestão de instâncias Antes de falar de Karpenter é preciso dar alguns passos atrás e explicar um pouco do que se trata um auto escalonamento de nodes. Suponha que temos nosso cluster com algumas máquinas (instâncias) disponíveis executando nossos workloads. O que acontece se por acaso houver um pico de uso em nossas aplicações e seja necessário subir mais instâncias (réplicas) de nossos pods? Sem um autoscaling precisaríamos provisionar um node, orientá-lo a juntar-se ao nosso cluster para aí sim nossos pods estarem aptos a serem iniciados nessa nova instância. Lembrando que o provisionamento de uma instância não é instantâneo, há todo um bootstrapping da máquina, configurações de rede e muitas outras coisas antes dela ficar totalmente disponível. Certo, falamos sobre pico de usuários em nossas aplicações, mas e quando houver ociosidade? Queremos mesmo deixar esses nodes em pé com poder computacional subutilizado? Para resolver essa e outras questões, entra em cena o conceito de auto scalers. Auto Scalers As implementações de auto scalers são responsáveis basicamente pelo provisionamento e consolidação de nodes. Aqui estamos falando de escalonamento horizontal, ou seja, adicionando mais máquinas em nosso cluster. Há diversas implementações de node autoscaling, mas neste artigo o foco será na implementação da AWS e por que decidimos migrar para uma outra solução. Abaixo uma figura exemplificando o funcionamento do node autoscaling: Figura 01: autoscaling AWS - Auto Scaling Groups Ao definir um grupo de escalonamento na AWS precisamos definir diversas propriedades, como o número mínimo/máximo de instâncias de nodes permitidas para este grupo, recursos utilizados, tipo de disco, configurações de rede (subnets, etc) e muitos outros detalhes. Por exemplo, para um determinado tipo de aplicação que utilize mais CPU vamos configurar um grupo que contenha tipos de instância com mais CPU do que memória. No fim possivelmente teremos alguns grupos distintos para certos tipos de aplicações. Juntando as peças – Cluster Auto Scaler Para que meu cluster consiga “conversar” com meu cloud provider (neste exemplo AWS), precisamos de um componente chamado Cluster Auto Scaler, ou CAS.Este componente foi criado pela própria comunidade que mantém o Kubernetes, e está disponível aqui. Uma configuração padrão do CAS pode ser vista abaixo, utilizando o helm para instalação: nameOverride: cluster-autoscaler awsRegion: us-east-1 autoDiscovery: clusterName: meu-cluster image: repository: registry.k8s.io/autoscaling/cluster-autoscaler tag: v1.30.1 tolerations: - key: infra operator: Exists effect: NoSchedule nodeSelector: environment: "infra" rbac: create: true serviceAccount: name: cluster-autoscaler annotations: eks.amazonaws.com/role-arn: "role-aws" extraArgs: v: 1 stderrthreshold: info Com isso configurado e instalado e nossos autoscaling groups criados acabamos de habilitar o gerenciamento automático de nossos nodes! Por que decidimos migrar para o Karpenter Nosso caso de uso aqui na Projuris é o seguinte: temos um cluster de desenvolvimento e outro de produção. Depois da migração para o Gitlab SaaS tínhamos um desafio de como provisionar os runners para a execução de nossas pipelines. Ficou decidido que usaríamos o cluster de desenvolvimento para provisionamento desses runners. Na “primeira versão” optamos pelo cluster auto scaler por ser uma configuração mais simples e que já atendia nosso setup em produção. Mas aí começamos a enfrentar alguns problemas com esta escolha: Tempo de provisionamento: ao iniciar uma pipeline o tempo de provisionamento da máquina era um pouco lento. O grande ponto é que o cluster auto scaler paga um “pedágio” no cloud provider para provisionamento de um novo node. Dificuldade na configuração de grupos: como temos alguns “perfis” de pipeline essa gestão ficou um pouco complicada, porque para cada novo perfil um novo node group precisa ser criado. Custo: para mitigar o problema de lentidão no startup de um novo node tínhamos um perfil de máquina “online” que ficava o tempo todo de pé, mesmo sem executar nenhuma pipeline. O que é o Karpenter? É uma solução de cluster autoscaling criada pela AWS, que promete o provisionamento e consolidação de nodes sempre com o menor custo possível. Ele é inteligente o suficiente para saber que por exemplo, ao comprar uma máquina na AWS do tipo on-demand, dependendo da situação, é mais em conta do que se fosse uma máquina spot. E essa é apenas uma das características dessa ferramenta. O Karpenter também trabalha com a ideia de “grupos” de máquinas (que no mundo do Karpenter chamamos de NodePools), só que a diferença é que fazemos isso através de CRDs (custom resource definitions) do próprio Karpenter, ou seja, temos manifestos dentro de nosso cluster com todas essas configurações, eliminando a necessidade de qualquer node group criado na AWS. Exemplo de um NodePool no Karpenter: apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: karpenter-gitlab-runner-small-online spec: template: metadata: labels: workload: gitlab-runners environment: karpenter-nodes-gitlab-runner-build-small-online spec: requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["spot", “on-demand”] - key: "node.kubernetes.io/instance-type" operator: In values: ["m5d.large", "m5n.large", "m6id.large", "m6in.large"] nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: my-node-class taints: - key: "gitlab-runner-karpenter" value: "true" effect: NoSchedule expireAfter: Never disruption: consolidationPolicy: WhenEmpty consolidateAfter: 5m budgets: - nodes: "20%" limits: cpu: "500" memory: 500Gi Além do NodePool precisamos criar um NodeClass para definir detalhes específicos de instâncias AWS: apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: my-node-class spec: amiFamily: AL2 role: "aws-role" tags: Name: nodes-k8s-nodes-gitlab-runner-small-online subnetSelectorTerms: - tags: karpenter.sh/subnet: "my-subnet" securityGroupSelectorTerms: - id: "sg-123" - id: "sg-456" - id: "sg-789" amiSelectorTerms: - id: "imagem ami" kubelet: clusterDNS: ["111.222.333.44"] blockDeviceMappings: - deviceName: /dev/xvda ebs: volumeSize: 40Gi volumeType: gp3 encrypted: true OBS: perceba que o nome “my-node-class” precisa bater com o node class configurado no node pool. Como o Karpenter nos ajudou a superar os desafios apresentados? Tempo de provisionamento: como o Karpenter conversa diretamente com as APIs do cloud provider não é necessário pagar o pedágio do cluster auto scaler. Tínhamos muitos problemas de timeout no provisionamento de novos nodes, após a troca pelo Karpenter esse problema simplesmente desapareceu justamente porque o provisionamento é mais eficiente. Dificuldade na configuração de grupos: com a solução de NodePools e NodeClass do Karpenter essa configuração ficou trivial, e o mais importante, versionada em nosso controle de versões no Gitlab. Ou seja, precisa incluir um perfil de máquina novo no NodePool? Sem problemas, basta um commit e o Karpenter já irá considerar isso nos novos provisionamentos. Custo: Conseguimos utilizar a utilização de máquinas, pois agora runners com características semelhantes são alocados em nodes que suportem os requisitos de memória e CPU exigidos. Ou seja, estamos realmente usando todo o poder computacional que aquele node proporciona. Isso vale também para a consolidação de nodes. Com o cluster auto scaler haviam scripts complexos para fazer o drain dos nodes antes da consolidação. Com o Karpenter isso é configurado no NodePool de maneira muito simplificada. Um ótimo argumento para a gestão que justifique o investimento nesse tipo de mudança é custo. Abaixo temos um comparativo do custo utilizando o Cluster AutoScaler e o Karpenter em Janeiro/25, onde conseguimos uma economia de 16% no total: Figura 02: Período de 01/01 à 15/01 com ClusterAutoScaler Figura 03: Período de 16/01 à 31/01 com o Karpenter Considerações finais A migração para o Karpenter foi uma escolha acertada. Conseguimos simplificar a gestão de nossos nodes com diferentes perfis de forma bastante simplificada. Ainda há espaço para algumas melhorias, como por exemplo a utilização de um único NodePool para simplificar ainda mais, e deixar que os runners configurem labels específicas para o perfil de máquina que deve ser provisionado para o runner (mais em https://kubernetes.io/docs/reference/labels-annotations-taints/).