Implementando aplicações altamente escaláveis com Amazon EKS e Karpenter utilizando Terraform.
Tags: eks, devops, awscommunitybuilders, terraform
Published: 2024-03-25 21:16:08


##**Introdução**

A automação e escalabilidade são elementos cruciais na gestão de infraestrutura em ambientes de nuvem, especialmente quando se trata de clusters Kubernetes. Neste artigo, exploraremos o processo técnico de implantação do Karpenter, uma ferramenta de escalonamento automático para Kubernetes, usando Terraform em um cluster Amazon EKS na AWS.

##**O papel do Karpenter na escalabilidade**

O Karpenter utiliza métricas de uso de recursos e políticas de escalonamento para provisionar automaticamente e de forma otimizada novos nós de computação quando necessário, garantindo que as aplicações tenham recursos suficientes para lidar com as demandas.


##**Desafios e Soluções**

Implementar manualmente políticas de escalonamento automático pode ser complexo e propenso a erros. O uso do Terraform simplifica esse processo, permitindo a definição da infraestrutura como código (IaC) e garantindo a consistência e repetibilidade nas implantações.

##**Benefícios desta Abordagem**

Ao adotar essa abordagem, você automatiza a implantação e o gerenciamento do Karpenter em um ambiente Kubernetes na AWS, garantindo uma infraestrutura escalável, confiável e consistente.


##**O que é Karpenter**

Karpenter é um sistema de autoescalonamento ele foi projetado para automatizar o dimensionamento de recursos em ambientes baseados em Kubernetes.

O objetivo principal é otimizar o uso de recursos, garantindo que os aplicativos tenham a capacidade necessária para atender à demanda, ao mesmo tempo em que evita o desperdício de recursos quando a demanda diminui.

O Karpenter é uma ferramenta útil para equipes que gerenciam clusters em Kubernetes, pois elimina a necessidade de ajustes manuais no dimensionamento dos recursos. 

Ele monitora a utilização dos recursos e toma decisões automatizadas com base nas métricas e políticas configuradas pelos usuários. 

Isso ajuda a garantir um desempenho eficiente e econômico dos aplicativos implantados em ambientes Kubernetes.

Mais informações sobre o Karpenter acesse a pagina oficial clicando [aqui](https://karpenter.sh/docs/getting-started/).

##**Como funciona**

O Karpenter funciona como um controlador personalizado para Kubernetes. Ele estende as capacidades padrão do Kubernetes, permitindo um autoescalonamento mais inteligente e eficiente. 

Abaixo uma visão geral de como o Karpenter opera:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pm6q7u914necjwyxln7e.png)


**Monitoramento de métricas** 
Monitora constantemente métricas como uso de CPU, memória, e outras métricas relevantes dos pods e dos nós do cluster Kubernetes. Pode se integrar a sistemas de monitoramento como Prometheus para coletar essas métricas.

**Análise de métricas e demanda**
Com base nas métricas coletadas, o Karpenter analisa a demanda atual dos recursos pelo cluster, identificando padrões de uso e tendências.

**Políticas de autoescalonamento**
Os usuários podem definir políticas de autoescalonamento que especificam como o Karpenter deve ajustar a quantidade de recursos disponíveis no cluster. Isso inclui especificar limites mínimos e máximos para o número de pods e nós, bem como configurar estratégias de alocação de recursos.

**Decisões de escalonamento**
Com base nas métricas coletadas e nas políticas definidas, o Karpenter toma decisões automatizadas sobre a criação, destruição ou dimensionamento de pods e nós no cluster Kubernetes. Ele pode escalar para cima (aumentando o número de pods ou nós) ou para baixo (reduzindo o número de pods ou nós) conforme necessário para atender à demanda.

**Alocação inteligente de recursos**
Ele realiza uma alocação inteligente de recursos, distribuindo os pods de maneira eficiente nos nós disponíveis, levando em consideração as políticas de balanceamento de carga e os requisitos de recursos de cada pod.

**Em resumo**
O Karpenter automatiza o processo de escalonamento de recursos em clusters Kubernetes, garantindo que os aplicativos tenham a capacidade necessária para lidar com a demanda atual, ao mesmo tempo em que otimiza a utilização dos recursos disponíveis e evita o desperdício. 

Isso simplifica a operação e a gestão de ambientes Kubernetes, melhorando a eficiência e o desempenho dos aplicativos implantados.


##**Alguns pontos importantes**
- Provisione Worker Nodes com base nos requisitos de Workload.
- Crie diversas configurações de Worker Nodes por tipo de instância, usando opções flexíveis de NodePool. Em vez de gerenciar muitos Worker Nodes Groups personalizados específicos, o Karpenter pode permitir que você gerencie diversas capacidades de carga de trabalho com um NodePool único e flexível.
- Obtenha um melhor agendamento de pods em escala, iniciando rapidamente Worker Nodes e agendando pods.

##**Alguns conceitos importantes**

**AWS NodeTemplate**

No contexto do Karpenter se refere a uma funcionalidade específica dessa ferramenta de autoescalonamento para ambientes Kubernetes na AWS. 

O AWS NodeTemplate permite definir configurações detalhadas para instâncias Worker Nodes no AWS EKS, incluindo o tipo de instância, capacidade de CPU, capacidade de memória, sistema operacional, configurações de rede, volumes anexados e outras opções. Essas configurações são essenciais para a criação de novos Worker Nodes no cluster Kubernetes da AWS de acordo com as necessidades específicas de cada aplicativo.


**SubnetSelector**

É uma funcionalidade que permite aos usuários selecionar sub-redes específicas dentro de uma VPC para implantar instâncias de Worker Nodes quando estão utilizando o Karpenter em conjunto com o Amazon EKS.

**SecurityGroupSelector**

É uma funcionalidade que permite aos usuários selecionar Security Groups específicos da AWS para Worker Nodes que estão sendo implantadas em um cluster Kubernetes gerenciado pelo Karpenter, especialmente quando utilizado em conjunto com o Amazon EKS.

Essa funcionalidade é útil por várias razões:

Segurança: Permite implementar políticas de segurança granulares, garantindo que as instâncias de Worker Nodes sejam associadas aos grupos de segurança adequados com as regras de firewall corretas.

Conformidade: Facilita a conformidade com padrões e regulamentos de segurança, pois você pode garantir que apenas as instâncias de Worker Nodes apropriadas tenham acesso aos recursos de rede necessários.

Isolamento: Ajuda a isolar o tráfego de rede entre diferentes componentes do seu ambiente Kubernetes na AWS, aumentando a segurança e reduzindo riscos.

##**Best practices**
**Exclua tipos de instância que não se adequam à sua carga de trabalho**
Considere excluir tipos de instâncias específicas com a chave node.kubernetes.io/instance-type se elas não forem exigidas pelas cargas de trabalho em execução no seu cluster.

O exemplo a seguir mostra como evitar o provisionamento de grandes instâncias Graviton.


- key: node.kubernetes.io/instance-type
  operator: NotIn
  values:
  - m6g.16xlarge
  - m6gd.16xlarge
  - r6g.16xlarge
  - r6gd.16xlarge
  - c6g.16xlarge


**Habilite o tratamento de interrupções ao usar o Spot**
Karpenter suporta tratamento de interrupção nativo , habilitado através do --interruption-queue-nameargumento CLI com o nome da fila SQS. O tratamento de interrupções monitora eventos futuros de interrupção involuntária que causariam interrupção em suas cargas de trabalho, como:

**Avisos de interrupção pontual**
Eventos de integridade de alteração agendados (eventos de manutenção)
Eventos de encerramento de instância
Eventos de interrupção de instância
Quando o Karpenter detecta que um desses eventos ocorrerá em seus Worker Nodes, ele automaticamente isola, drena e encerra os Worker Nodes antes do evento de interrupção para fornecer o tempo máximo para limpeza da carga de trabalho antes da interrupção

**Criando NodePools**
As práticas recomendadas a seguir abrangem tópicos relacionados à criação de NodePools.

Crie vários NodePools quando...
Quando diferentes equipes estão compartilhando um cluster e precisam executar suas cargas de trabalho em diferentes Worker Nodes de trabalho ou têm diferentes requisitos de sistema operacional ou de tipo de instância, crie vários NodePools. Por exemplo, uma equipe pode querer usar o Bottlerocket, enquanto outra pode querer usar o Amazon Linux. Da mesma forma, uma equipe pode ter acesso a hardware de GPU caro que não seria necessário para outra equipe. O uso de vários NodePools garante que os ativos mais apropriados estejam disponíveis para cada equipe.

**Crie NodePools que sejam mutuamente exclusivos ou ponderados**
Recomenda-se criar NodePools que sejam mutuamente exclusivos ou ponderados para fornecer um comportamento de agendamento consistente. Se não forem e vários NodePools corresponderem, o Karpenter escolherá aleatoriamente qual usar, causando resultados inesperados.

**Use temporizadores (TTL) para excluir automaticamente Worker Nodes do cluster**
É possível usar temporizadores em Worker Nodes provisionados para definir quando excluir Worker Nodes que estão desprovidos de pods de carga de trabalho ou que atingiram um tempo de expiração. A expiração do nó pode ser usada como meio de atualização, para que os Worker Nodes sejam retirados e substituídos por versões atualizadas. Consulte Expiração na documentação do Karpenter para obter informações sobre como spec.disruption.expireAfterconfigurar a expiração do nó.


## **Criando o ambiente do cluster EKS**
Para criar um ambiente com um cluster EKS para nossos testes, iremos utilizar o repositório que está disponivel nesse [link](https://github.com/rodrigofrs13/workshop-aws-eks-karpenter).

**EKS Blueprint**
O código fonte do repositório acima é baseado nos Blueprints que são forneceidos pela AWS.
A AWS disponibiliza diversos templates prontos, os chamados Blueprints. Eles abstraem as complexidades da infraestrutura permitindo que seja implantado cargas de trabalhado de forma simples e replicavel.
Segue o link dos vários modelos de Blueprint que pode ser acessado clicando [aqui](https://github.com/aws-ia/terraform-aws-eks-blueprints?source=post_page-----5294088425e2--------------------------------).



**Clone do repositório**

> `git clone https://github.com/rodrigofrs13/workshop-aws-eks-karpenter`

Acesse o diretório /workshop-aws-eks-karpenter/enviroment


**Editando as variaveis**
Edite o arquivo arquivo terraform.tfvars com as variaveis que podem ser ajustadas antes de iniciar a instalação, como por exemplo:

_- Região AWS_
_- Nome do cluster EKS_
_- Versão do cluster EKS_
_- Versão dos Addons_
_- Nome da Role Admin_

**Setup do ambiente**
Com o comando abaixo vamos iniciar o Terraform, criar o Plan e efeturar o setup.

> `terraform init && terraform plan && terraform apply --auto-approve`

Acesse o diretório /workshop-aws-eks-karpenter/eks-blue

**Editando as variaveis**
Edite o arquivo arquivo terraform.tfvars com as variaveis que podem ser ajustadas antes de iniciar a instalação, como por exemplo:

_- Região AWS_
_- Nome do cluster EKS_
_- Versão do cluster EKS_
_- Versão dos Addons_
_- Nome da Role Admin_

**Setup do ambiente**
Com o comando abaixo vamos iniciar o Terraform, criar o Plan e efeturar o setup.

> `terraform init && terraform plan && terraform apply --auto-approve`


## **Acessando o Cluster**
Vamos acessar o cluster EKS com seguinte comando para configurar o kubectl para apontar para o cluster desejado:

> `$ aws eks --region <region> update-kubeconfig --name <cluster-name>`

Caso tenha perdido o output, utilize o comando `terraform output`.

Vamos fazer alguns testes iniciais para verificar se está tudo certo com nosso cluster.

**Coletando algumas informações.**

> `kubectl cluster-info`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bb8swcqsvvfcp811scrg.png)



**Verificando os Worker Nodes**

> `kubectl get nodes -o wide`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9t3c2cm2un81x4592u6z.png)



**Analisando todos os recursos criados**

> `kubectl get all -A`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j97npdbu5o55a8mc1k6t.png)



Com isso podemos concluir que nosso cluster está funcionando corretamente e estamos prontos para a próxima etapa, o Karpenter.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eehrmaarfs1wrf4m7pli.png)

----------------------------------------------------
**Validando o Karpenter**

Como utilizamos um Blueprint da AWS, o Karpenter já está instalado e configurado em nosso cluster. 

Vamos fazer algumas validações para confirmar se está tudo certo com o Karpenter.

Validando os pods.

> `kubectl get pods -n karpenter`


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/krj26eusecqx0h7qhcof.png)


Validando o Provisioner

> `kubectl get provisioner`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cv3tyrpbv18u2exz2k38.png)




Com isso o Karpenter está instalado e executando com sucesso.

Vamos criar um NodePool com algumas regras para começarmos a utilizar o Karpenter e escalar de forma inteligente o nosso ambiente.

Como estamos utilizando os Blueprints já temos a parte GitOps e CICD configurados. Em nosso estudo de caso vamos utilizar uma parte dessas ferramentas.

O repositório que pode ser acessado por esse [link ](https://github.com/rodrigofrs13/eks-blueprints-workloads) é um Fork de um blueprint que iremos utilizar.
Faça o clone do repositório com o comando `git clone https://github.com/rodrigofrs13/eks-blueprints-workloads.git`

No deployment da app temos o NodeSelector que indica que todos os pods daquele deployment serão deployados nos Worker Nodes do Karpenter.

Iremos utilizar o Team Riker para nossos testes, acesse arquivo do Karpenter que está nesse caminho: teams/team-riker/dev/templates/karpenter.yaml.

Vamos configurar o nosso AWSNodeTemplate e Provisioner para definir algumas configurações para o Karpenter.

Segue abaixo um exemplo do nosso NodePool

```
{{ if .Values.spec.karpenterInstanceProfile }}
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: karpenter-default
  labels:
    {{- toYaml .Values.labels | nindent 4 }}  
spec:
  instanceProfile: '{{ .Values.spec.karpenterInstanceProfile }}'
  subnetSelector:
    kubernetes.io/cluster/{{ .Values.spec.clusterName }}: '*'
    kubernetes.io/role/internal-elb: '1' # to select only private subnets
  securityGroupSelector:
    aws:eks:cluster-name: '{{ .Values.spec.clusterName }}' # Choose only security groups of nodes
  tags:
    karpenter.sh/cluster_name: {{.Values.spec.clusterName}}
    karpenter.sh/provisioner: default
  metadataOptions:
    httpEndpoint: enabled
    httpProtocolIPv6: disabled
    httpPutResponseHopLimit: 2
    httpTokens: required
---
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
  labels:
    {{- toYaml .Values.labels | nindent 4 }}
spec:
  consolidation:
    enabled: true
  #ttlSecondsAfterEmpty: 60 # mutual exclusive with consolitation
  requirements:
    - key: "karpenter.k8s.aws/instance-category"
      operator: In
      values: ["c", "m"]
    - key: karpenter.k8s.aws/instance-cpu
      operator: Lt
      values:
        - '33'    
    - key: 'kubernetes.io/arch'
      operator: In
      values: ['amd64']
    - key: karpenter.sh/capacity-type
      operator: In
      values: ['on-demand']
    - key: kubernetes.io/os
      operator: In
      values:
        - linux
  providerRef:
    name: karpenter-default

  ttlSecondsUntilExpired: 2592000 # 30 Days = 60 * 60 * 24 * 30 Seconds;
  
  # Priority given to the provisioner when the scheduler considers which provisioner
  # to select. Higher weights indicate higher priority when comparing provisioners.
  # Specifying no weight is equivalent to specifying a weight of 0.
  weight: 1
  limits:
    resources:
      cpu: '2k'
  labels:
    billing-team: default
    team: default
    type: karpenter
    
  # Do we want to apply some taints on the nodes ?  
  # taints:
  #   - key: karpenter
  #     value: 'true'
  #     effect: NoSchedule

  # Karpenter provides the ability to specify a few additional Kubelet args.
  # These are all optional and provide support for additional customization and use cases.
  kubeletConfiguration:
    containerRuntime: containerd
    maxPods: 110     
    systemReserved:
      cpu: '1'
      memory: 5Gi
      ephemeral-storage: 2Gi
{{ end }}
```
 Alguns itens que foram defidos

**Labels**
Definimos labels dedicados que podem ser usados ​​por pods como nodeSelectors.

**Taints**
Podemos adicionar taints aos Worker Nodes para que as cargas de trabalho precisem tolerar que esses taints sejam agendados nos Worker Nodes do Karpenter.

Especificamos alguns requisitos em torno de tipos de instâncias, capacidade e arquitetura; cada provisionador é altamente personalizável.



Após os ajustes vamos fazer o commit e o push

> `git add teams/team-riker/dev/templates/karpenter.yaml
> git commit -m "Add Karpenter provisioner"
> git push`

Podemos verificar com o eks-node-view(para saber mais clique aqui) abaixo e já podemos ver que alguns Worker Nodes do Karpenter já foram provisionados, cada um em uma AZ por conta das configurações que fizemos.




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phn347s31w0lgtlnlnec.png)




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fif1qq79xn5ndgr8l0uv.png)



O próximo passo é aumentar o nosso Workload para que o Karpenter começe a dimencionar os Worker Nodes.


Vamos escalar a app skiapp-deployment do Team Riker com o comando abaixo:

> `> kubectl scale deployment -n team-riker skiapp-deployment --replicas 30`

Podemos notar que o Karpenter começa a escalar os novos Worker Nodes conforme os requirimentos baseando sempre na menor instância. 

Em um primerio momento o Karpenter adcionou 3 novos Worker Nodes (Atendendo o requisito de Multi-AZ) menores para suportar o aumento do Workload. 



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mugndlzctb0awvptfqjt.png)


No segundo momento o Karpenter adcionou 3 novos Worker Nodes (Atendendo o requisito de Multi-AZ) menores para suportar o aumento do Workload. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4r80xj2ewsou7mlkyn3d.png)

Vamos escalar a app skiapp-deployment do Team Riker com o comando abaixo para simular um consumo alto de WorkLoad

> `kubectl scale deployment -n team-riker skiapp-deployment --replicas 100`

Podemos notar em um primeiro momento que o Karpenter adcionou novas instancias.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oaio31o7fv1tt2nn6hko.png)


Em um segundo momento o Karpenter alterou o tipo de instancia para melhorar a eficiencia do ambiente.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3s9d6ye4s92y7w2tunnt.png)

O Karpenter sempre tenta infinitamente economizar custos em seu cluster.



 

##**Removendo o ambiente**

Vamos dismobilizar a infra reduzindo o Workload para 1

> `kubectl scale deployment -n team-riker skiapp-deployment --replicas 1`

**Removendo o cluster**
Acesse o diretório /workshop-aws-eks-karpenter/enviroment

> `terraform destroy--auto-approve`

Após a conclusão, acesse o diretório /workshop-aws-eks-karpenter/eks-blue e execute o comando abaixo


> `terraform destroy--auto-approve`

##**Conclusão**

Agora temos a capacidade de adicionar mais capacidade ao nosso cluster para dimensionar nosso Workload e o Karpenter garante um equilíbrio entre instâncias e custos de forma inteligente. 

##**Referências**
https://aws.github.io/aws-eks-best-practices/karpenter/