# LATAM Containers Roadshow - Workshop de Amazon EKS
Neste workshop, exploraremos várias maneiras de configurar VPC, ALB, EC2 Kubernetes DataPlane Nodes e Amazon Elastic Kubernetes Service (EKS).
# Módulos
- Provisionando seu cluster EKS
- Cluster Autoscaler
- Melhor estratégia de escala com Karpenter
- Uso de instâncias Graviton e Spot para reduzir custos
- Observabilidade com o Amazon CloudWatch Container Insights
- Pipelines CI/CD
# Começando
Utilize o terminal integrado do ambiente AWS Cloud9 para executar as etapas a seguir:
### Desabilite as credenciais temporárias gerenciadas pela AWS
```bash
aws cloud9 update-environment --environment-id $C9_PID --managed-credentials-action DISABLE
rm -vf ${HOME}/.aws/credentials
```
### Crie uma Spot Linked Role
```bash
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com
```
### Exporte variáveis que serão utilizadas pelo workshop.
```bash
export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region')
export AZS=($(aws ec2 describe-availability-zones --query 'AvailabilityZones[].ZoneName' --output text --region $AWS_REGION))
export CLUSTER_NAME='latam-containers-roadshow'
export TF_VAR_aws_region="${AWS_REGION}"
```
Exporte as variáveis no perfil do bash
```bash
echo "export ACCOUNT_ID=${ACCOUNT_ID}" | tee -a ~/.bash_profile
echo "export AWS_REGION=${AWS_REGION}" | tee -a ~/.bash_profile
echo "export AZS=(${AZS[@]})" | tee -a ~/.bash_profile
echo "export CLUSTER_NAME=${CLUSTER_NAME}" | tee -a ~/.bash_profile
echo "export TF_VAR_aws_region=${TF_VAR_aws_region}" | tee -a ~/.bash_profile
aws configure set default.region ${AWS_REGION}
aws configure get default.region
```
# Provisionando seu cluster EKS
Para o provisionamento do cluster, vamos usar o [**EKS Blueprints**](https://aws.amazon.com/blogs/containers/bootstrapping-clusters-with-eks-blueprints/). EKS Blueprints é uma coleção de módulos de infraestrutura como código (IaC) que ajuda a configurar e implantar clusters EKS consistentes e com addons instalados. Você pode usar EKS Blueprints para inicializar facilmente um cluster EKS com addons do Amazon EKS, bem como uma ampla variedade de addons populares de código aberto, incluindo Prometheus, Karpenter, Nginx, Traefik, AWS Load Balancer Controller, Fluent Bit, Keda , Argo CD e muito mais. O EKS Blueprints também ajuda a implementar controles de segurança relevantes necessários para operar cargas de trabalho de várias equipes no mesmo cluster.
### Etapa 1: clone o repositório usando o comando abaixo
```bash
git clone https://github.com/aws-samples/latam-containers-roadshow.git
```
### Etapa 2: execute o Terraform INIT
Inicialize um diretório de trabalho com arquivos de configuração
```bash
cd ~/environment/latam-containers-roadshow/workshops/eks/terraform/
terraform init
```
### Etapa 3: executar o Terraform PLAN
Verifique os recursos criados por esta execução
```bash
terraform plan
```
### Passo 4: Finalmente, Terraform APPLY
para criar recursos
```bash
terraform apply --auto-approve
```
## Configurar o kubectl e testar o cluster
Os detalhes do cluster EKS podem ser extraídos da saída do terraform ou do Console AWS para obter o nome do cluster. O comando a seguir atualiza o kubeconfig em sua máquina local onde você executa comandos kubectl para interagir com seu cluster EKS.
### Etapa 5: execute o comando update-kubeconfig
`~/.kube/config`arquivo é atualizado com detalhes do cluster:
```bash
aws eks --region ${AWS_REGION} update-kubeconfig --name ${CLUSTER_NAME}
```
### Etapa 6: Liste todos os worker nodes executando o comando abaixo
```bash
kubectl get nodes
```
# Cluster Autoscaler
Neste módulo, mostraremos padrões para dimensionar seus worker nodes e implementações de aplicativos automaticamente.
O dimensionamento automático no K8s vem em duas formas:
- **Horizontal Pod Autoscaling (HPA)** dimensiona os pods em uma implantação ou conjunto de réplicas. Ele é implementado como um recurso de API K8s e um controlador. O gerenciador do controlador consulta a utilização do recurso em relação às métricas especificadas em cada definição do HorizontalPodAutoscaler. Ele obtém as métricas da API de métricas de recursos (para métricas de recursos por pod) ou da API de métricas personalizadas (para todas as outras métricas).
- **Cluster AutoScaler (CA)** é um componente que ajusta automaticamente o tamanho de um cluster Kubernetes para que todos os pods tenham um local para execução e não haja nós desnecessários.
## Instalar kube-ops-view
Antes de começar a aprender sobre as várias opções de dimensionamento automático para seu cluster EKS, vamos instalar o [Kube-ops-view](https://github.com/hjacobs/kube-ops-view) a partir de [Henning Jacobs](https://github.com/hjacobs).
Kube-ops-view fornece uma imagem operacional comum para um cluster Kubernetes que ajuda a entender nossa configuração de cluster de maneira visual.
```bash
helm repo add stable https://charts.helm.sh/stable
helm install kube-ops-view \
stable/kube-ops-view \
--set service.type=LoadBalancer \
--set rbac.create=True
```
Para verificar se o chart foi instalado com sucesso:
```bash
helm list
```
deve exibir:
```output
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
kube-ops-view 1 Sun Sep 22 11:47:31 2019 DEPLOYED kube-ops-view-1.1.0 0.11 default
```
Com isso, podemos explorar a saída do kube-ops-view verificando os detalhes sobre o serviço recém-criado.
```bash
kubectl get svc kube-ops-view | tail -n 1 | awk '{ print "Kube-ops-view URL = http://"$4 }'
```
Isso exibirá uma linha semelhante a Kube-ops-view URL = http://<URL_PREFIX_ELB>.amazonaws.com Abrir a URL em seu navegador fornecerá o estado atual de nosso cluster.
## Dimensionar um aplicativo com HPA
Para aplicativos de escalabilidade com HPA, precisaremos do servidor de métricas instalado em nosso cluster. Ele já foi instalado usando os blueprints EKS com terraform conforme mostrado abaixo:
```terraform
# Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = true
```
### Implantar um aplicativo de exemplo
Vamos implantar um aplicativo e expor como um serviço na porta TCP 80.
O aplicativo é uma imagem personalizada com base na imagem php-apache. A página index.php realiza cálculos para gerar carga da CPU.
```bash
kubectl create deployment php-apache --image=us.gcr.io/k8s-artifacts-prod/hpa-example
kubectl set resources deploy php-apache --requests=cpu=200m
kubectl expose deploy php-apache --port 80
kubectl get pod -l app=php-apache
```
### Criar um recurso HPA
Esse HPA aumenta quando a CPU excede 50% do recurso de contêiner alocado.
```bash
kubectl autoscale deployment php-apache `#The target average CPU utilization` \
--cpu-percent=50 \
--min=1 `#The lower limit for the number of pods that can be set by the autoscaler` \
--max=10 `#The upper limit for the number of pods that can be set by the autoscaler`
```
Visualize o HPA usando kubectl. Você provavelmente vai ver`/50%`por 1-2 minutos e então você poderá ver`0%/50%`
```bash
kubectl get hpa
```
## Gerar carga para acionar o dimensionamento
Abra um novo terminal no ambiente Cloud9 e execute o comando a seguir para entrar em um shell em um novo contêiner
```bash
kubectl run -i --tty load-generator --image=busybox /bin/sh
```
Execute um loop while para continuar obtendo http:///php-apache
```bash
while true; do wget -q -O - http://php-apache; done
```
Na aba anterior, observe o HPA com o seguinte comando
```bash
kubectl get hpa -w
```
Você verá o HPA dimensionar os pods de 1 até o máximo configurado (10) até que a média da CPU esteja abaixo do nosso objetivo (50%)
Agora você pode parar (Ctrl + C) o teste de carga que estava sendo executado no outro terminal. Você notará que o HPA trará lentamente a contagem de réplicas para o número mínimo com base em sua configuração. Você também deve sair do aplicativo de teste de carga pressionando Ctrl + D.
## Configurar o escalonador automático de cluster (CA)
O Cluster Autoscaler para AWS oferece integração com grupos de Auto Scaling. Ele permite que os usuários escolham entre quatro opções diferentes de implantação:
One Auto Scaling group
Multiple Auto Scaling groups
Auto-Discovery
Control-plane Node setup
A descoberta automática é o método preferencial para configurar o dimensionador automático de cluster.
O Cluster Autoscaler tentará determinar os recursos de CPU, memória e GPU fornecidos por um Auto Scaling Group com base no tipo de instância especificado em sua configuração de execução ou modelo de execução.
### Configurar o ASG
Você configura o tamanho do seu grupo de Auto Scaling definindo a capacidade mínima, máxima e desejada. Quando criamos o cluster, definimos essas configurações como 2 no manifesto do terraform.
```terraform
managed_node_groups = {
mg_5 = {
node_group_name = "managed-ondemand"
instance_types = ["m5.large"]
desired_size = 2
max_size = 2
min_size = 2
subnet_ids = module.vpc.private_subnets
}
}
```
Agora, aumente a capacidade máxima para 4 instâncias. Abra o`latam-containers-roadshow/workshops/eks/terraform/main.tf`arquivo e atualize o`max_size`de 2 a 4, você terá seu manifesto assim:
```terraform
managed_node_groups = {
mg_5 = {
node_group_name = "managed-ondemand"
instance_types = ["m5.large"]
desired_size = 2
max_size = 4
min_size = 2
subnet_ids = module.vpc.private_subnets
}
}
```
Vamos habilitar também o`Cluster-Autoscaler`na seção de complementos do nosso manifesto do terraform. Abra o`latam-containers-roadshow/workshops/eks/terraform/main.tf`e mude o`enable_cluster_autoscaler`a partir de`false`para`true`.
```terraform
module "eks_blueprints_kubernetes_addons" {
source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.0.7"
eks_cluster_id = module.eks_blueprints.eks_cluster_id
# EKS Managed Add-ons
enable_amazon_eks_vpc_cni = true
enable_amazon_eks_coredns = true
enable_amazon_eks_kube_proxy = true
# Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = true
enable_cluster_autoscaler = true
enable_karpenter = false
enable_aws_cloudwatch_metrics = false
enable_aws_for_fluentbit = false
tags = local.tags
depends_on = [module.eks_blueprints.managed_node_groups]
}
```
### Executar o PLAN do Terraform
Verifique os recursos criados por esta execução
```bash
cd ~/environment/latam-containers-roadshow/workshops/eks/terraform/
terraform plan
```
### Por fim, o Terraform APPLY
para criar recursos
```bash
terraform apply --auto-approve
```
Observe os logs para verificar se o Cluster Autoscaler foi implantado com êxito.
```bash
kubectl -n kube-system logs -f deployment/cluster-autoscaler-aws-cluster-autoscaler
```
## Crie um aplicativo de exemplo para testar o CA
Implantaremos um aplicativo nginx de exemplo com um ReplicaSet de 1 pod.
```bash
cat < ~/environment/nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-to-scaleout
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
service: nginx
app: nginx
spec:
containers:
- image: nginx
name: nginx-to-scaleout
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 500m
memory: 512Mi
EoF
kubectl apply -f ~/environment/nginx.yaml
kubectl get deployment/nginx-to-scaleout
```
### Dimensione nosso ReplicaSet
```bash
kubectl scale --replicas=10 deployment/nginx-to-scaleout
```
Alguns pods estarão no estado `Pending`, que aciona o cluster-autoscaler para expandir a frota do EC2.
```bash
kubectl get pods -l app=nginx -o wide --watch
```
Verifique a [Console de gerenciamento EC2 AWS](https://console.aws.amazon.com/ec2/home?#Instances:sort=instanceId)para confirmar se os grupos do Auto Scaling estão aumentando para atender à demanda. Isso pode levar alguns minutos. Você também pode acompanhar a implantação do pod na linha de comando. Você deve ver a transição dos pods de pendente para execução à medida que os nós são criados.
Ou verifique usando`kubectl`
```bash
kubectl get nodes
```
A saída será semelhante a esta:
```output
ip-192-168-12-114.us-east-2.compute.internal Ready 3d6h v1.17.7-eks-bffbac
ip-192-168-29-155.us-east-2.compute.internal Ready 63s v1.17.7-eks-bffbac
ip-192-168-55-187.us-east-2.compute.internal Ready 3d6h v1.17.7-eks-bffbac
ip-192-168-82-113.us-east-2.compute.internal Ready 8h v1.17.7-eks-bffbac
```
### Limpe o ambiente
```bash
kubectl delete -f ~/environment/nginx.yaml
kubectl delete hpa,svc php-apache
kubectl delete deployment php-apache
kubectl delete pod load-generator
helm uninstall kube-ops-view
export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='latam-containers-roadshow']].AutoScalingGroupName" --output text)
aws autoscaling \
update-auto-scaling-group \
--auto-scaling-group-name ${ASG_NAME} \
--min-size 2 \
--desired-capacity 2 \
--max-size 2
```
# Computação Flexível com Karpenter
[Karpenter](https://karpenter.sh/)é um projeto de escalonamento automático de código aberto criado para Kubernetes. O Karpenter foi projetado para fornecer os recursos de computação certos para atender às necessidades do seu aplicativo em segundos, em vez de minutos, observando as solicitações de recursos agregados de pods não programáveis e tomando decisões para iniciar e encerrar nós para minimizar as latências de agendamento.
Vamos habilitar o Karpenter e desabilitar o Cluster Autoscaler em nosso cluster EKS usando o terraform. Abra o arquivo `latam-containers-roadshow/workshops/eks/terraform/main.tf` e altere o campo `enable_cluster_autoscale` de `true` para `false`, e também altere o atributo `enable_karpenter` de `false` para `true`:
```terraform
module "eks_blueprints_kubernetes_addons" {
source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.0.7"
eks_cluster_id = module.eks_blueprints.eks_cluster_id
# EKS Managed Add-ons
enable_amazon_eks_vpc_cni = true
enable_amazon_eks_coredns = true
enable_amazon_eks_kube_proxy = true
# Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = true
enable_cluster_autoscaler = false # Disable Cluster Autoscaler
enable_karpenter = true # Enable Karpenter
enable_aws_cloudwatch_metrics = false
enable_aws_for_fluentbit = false
tags = local.tags
depends_on = [module.eks_blueprints.managed_node_groups]
}
```
Agora, diminua a capacidade máxima para 2 instâncias. Abra o`latam-containers-roadshow/workshops/eks/terraform/main.tf`arquivo e atualize o `max_size` de 4 para 2, você terá seu manifesto assim:
```terraform
managed_node_groups = {
mg_5 = {
node_group_name = "managed-ondemand"
instance_types = ["m5.large"]
desired_size = 2
max_size = 2
min_size = 2
subnet_ids = module.vpc.private_subnets
}
}
```
### Execute o PLAN do Terraform
Verifique os recursos criados por esta execução
```bash
cd ~/environment/latam-containers-roadshow/workshops/eks/terraform/
terraform plan
```
### Por fim, o Terraform APPLY
para criar recursos
```bash
terraform apply --auto-approve
```
Fazer isso removerá o Cluster Autoscaler e todos os recursos relacionados do nosso cluster e instalará o Karpenter com todos os recursos.
## Configurar o Provisionador
A configuração do Karpenter vem na forma de um Provisioner CRD (Custom Resource Definition). Um único provisionador Karpenter é capaz de lidar com vários formatos de pods diferentes. O Karpenter toma decisões de agendamento e provisionamento com base em atributos de pod, como labels e afinidade. Um cluster pode ter mais de um Provisioner, mas no momento vamos declarar apenas um: o Provisioner padrão.
Um dos principais objetivos da Karpenter é simplificar a gestão da capacidade. Se você estiver familiarizado com outros Auto Scalers, notará que o Karpenter adota uma abordagem diferente. Você pode ter ouvido a abordagem referida como dimensionamento automático sem grupo. Outras soluções têm tradicionalmente usado o conceito de um grupo de nós como o elemento de controle que define as características da capacidade fornecida (ou seja: On-Demand, EC2 Spot, GPU Nodes, etc) e que controla a escala desejada do grupo no conjunto. Na AWS, a implementação de um grupo de nós corresponde a [Grupos de Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html). Com o tempo, os clusters que usam esse paradigma, que executam diferentes tipos de aplicativos que exigem diferentes tipos de capacidade, acabam com uma configuração e um modelo operacional complexos, onde os grupos de nós devem ser definidos e fornecidos com antecedência.
Vamos implantar o seguinte provisionador:
```bash
cat < ~/environment/provisioner.yaml
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
labels:
intent: apps
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
limits:
resources:
cpu: 1000
provider:
instanceProfile: ${CLUSTER_NAME}-managed-ondemand
subnetSelector:
Name: "${CLUSTER_NAME}-private*"
securityGroupSelector:
karpenter.sh/discovery/${CLUSTER_NAME}: ${CLUSTER_NAME}
ttlSecondsAfterEmpty: 30
EOF
kubectl apply -f ~/environment/provisioner.yaml
```
A configuração para este provisionador é bastante simples. Por enquanto, vamos nos concentrar em algumas das configurações usadas.
**Seção de Requisitos:** o [Provisioner CRD](https://karpenter.sh/docs/provisioner-crd/)suporta a definição de propriedades de nó como tipo de instância e zona. Por exemplo, em resposta a um label de `topology.kubernetes.io/zone=us-east-1c`, o Karpenter provisionará nós nessa zona de disponibilidade. Neste exemplo estamos configurando o`karpenter.sh/capacity-type` para pesquisar instâncias Spot do EC2. Você pode saber quais outras propriedades estão [disponíveis aqui](https://karpenter.sh/v0.7.2/aws/provisioning/).
**Seção de limites:** Os provisionadores podem definir um limite no número de CPUs e memória alocados para esse provisionador específico e parte do cluster.
**Seção do provisioner:** Este provisioner usa `securityGroupSelector` e subnetSelector para descobrir recursos usados para iniciar nós. Ele usa as tags que o Karpenter anexou às sub-redes.
**ttlSecondsAfterEmpty:** configura o Karpenter para encerrar nós vazios. Este comportamento pode ser desabilitado deixando o valor indefinido. Neste caso, configuramos para uma demonstração rápida um valor de 30 segundos.
**Tags:** Os provisionadores também podem definir um conjunto de tags que as instâncias do EC2 terão na criação. Isso ajuda a habilitar a contabilidade e a governança no nível do EC2.
## Exibindo Logs do Karpenter
Para ler os logs do Karpenter no console, você pode executar o seguinte comando.
```bash
kubectl logs -f deployment/karpenter -c controller -n karpenter
```
## Provisionando novos nós com o Karpenter
Com o Karpenter agora ativo, podemos começar a explorar como o Karpenter provisiona os nós. Nesta seção, vamos criar alguns pods usando uma implantação em que observaremos os nós de provisionamento do Karpenter em resposta.
Para este workshop, usaremos um contêiner nginx simples com **mais solicitações de recursos do que o necessário**, e veja como Karpenter lida com isso.
```bash
cat < ~/environment/karpenter-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-scale-karpenter
spec:
replicas: 3
selector:
matchLabels:
app: nginx-scale-karpenter
template:
metadata:
labels:
service: nginx-scale-karpenter
app: nginx-scale-karpenter
spec:
containers:
- image: nginx
name: nginx-scale-karpenter
resources:
limits:
cpu: 1000m
memory: 1000Mi
requests:
cpu: 1000m
memory: 1000Mi
EoF
kubectl apply -f ~/environment/karpenter-nginx.yaml
```
Liste todos os pods disponíveis no namespace padrão:
```bash
kubectl get pods
```
Observe que 2 pods estão com status`Pending`. O motivo é que o cluster não possui nenhum nó disponível exigido pela solicitação de recurso do pod.
Karpenter está monitorando o cluster e provisionando um novo nó. Observe a criação do nó até que o status seja `Ready`:
```bash
watch kubectl get nodes
```
Quando o novo nó estiver `Ready`, liste os pods para validar se o aplicativo está em execução:
```bash
kubectl get pods
```
## Desimplantar o aplicativo
Agora que entendemos como o Karpenter funciona, vamos remover a implantação do aplicativo nginx:
```bash
kubectl delete -f ~/environment/karpenter-nginx.yaml
```
Como o novo nó não possui pods em execução, o Karpenter removerá o nó. Observe a lista de nós até que o novo nó seja removido:
```bash
watch kubectl get nodes
```
## Implantar um aplicativo baseado em ARM
Quando um provisionador não especifica a arquitetura e também os tipos de instância em que pode criar novos nós, por padrão todas as instâncias e todas as arquiteturas (amd64 e arm64) serão usadas. Isso significa que uma instância [Graviton](https://aws.amazon.com/pm/ec2-graviton/) pode ser criada e, com isso, a aplicação a ser executada pode não suportar a arquitetura da instância, o que pode causar erros em tempo de execução.
É possível utilizar o [label conhecido](https://kubernetes.io/docs/reference/labels-annotations-taints/)kubernetes.io/arch no manifesto do aplicativo com um_seletor de nó_. Dessa forma, o Karpenter levará em consideração o seletor e provisionará o nó específico para esse caso de uso.
Vamos implantar uma aplicação `arm` com um seletor de nó `arm64`:
```bash
cat < ~/environment/karpenter-arm64.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-arm
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-arm
template:
metadata:
labels:
service: hello-world-arm
app: hello-world-arm
spec:
containers:
- image: public.ecr.aws/nginx/nginx:1.21-arm64v8
name: hello-world-arm
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 500m
memory: 512Mi
nodeSelector:
kubernetes.io/arch: arm64
EoF
kubectl apply -f ~/environment/karpenter-arm64.yaml
```
Liste todos os pods disponíveis no namespace padrão:
```bash
kubectl get pods
```
Observe que o pod está com o status Pendente. O motivo é que o cluster não possui nenhum nó disponível exigido pelo nodeSelector do Pod (kubernetes.io/arch: arm64).
Karpenter está monitorando o cluster e provisionando um novo nó. Observe a criação do nó até que o status seja`Ready`:
```bash
watch kubectl get nodes
```
Uma vez que o nó esteja `Ready`, vamos obter o nome do nó que o Pod está executando:
```bash
export ARM_NODE=$(kubectl get pods -owide | awk '{print $7}' | grep -vi node)
```
Agora vamos verificar os labels do Node, com os labels podemos ver se o nó foi provisionado na arquitetura correta.
```bash
kubectl get node $ARM_NODE --show-labels
```
O resultado será parecido com o abaixo:
```output
NAME STATUS ROLES AGE VERSION LABELS
ip-10-0-11-211.ec2.internal Ready 15m v1.21.12-eks-5308cf7 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/instance-type=t4g.small,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1b,intent=apps,karpenter.sh/capacity-type=spot,karpenter.sh/provisioner-name=default,kubernetes.io/arch=arm64,kubernetes.io/hostname=ip-10-0-11-211.ec2.internal,kubernetes.io/os=linux,node.kubernetes.io/instance-type=t4g.small,topology.kubernetes.io/region=us-east-1,topology.kubernetes.io/zone=us-east-1b
```
Como você pode ver este Node tem o label `kubernetes.io/arch=arm64`, mostrando que é um nó com processador por Graviton.
## Desimplantar o aplicativo ARM
```bash
kubectl delete -f ~/environment/karpenter-arm64.yaml
```
Como o novo nó não possui pods em execução, o Karpenter removerá o nó. Observe a lista de nós até que o novo nó seja removido:
```bash
watch kubectl get nodes
```
## Desimplantar o Karpenter
Vamos desabilitar o Karpenter em nosso cluster EKS usando o terraform. Abra o arquivo `latam-containers-roadshow/workshops/eks/terraform/main.tf` e altere a propriedade `enable_karpenter` de `true` para`false`:
```terraform
module "eks_blueprints_kubernetes_addons" {
source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.0.7"
eks_cluster_id = module.eks_blueprints.eks_cluster_id
# EKS Managed Add-ons
enable_amazon_eks_vpc_cni = true
enable_amazon_eks_coredns = true
enable_amazon_eks_kube_proxy = true
# Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = true
enable_cluster_autoscaler = false
enable_karpenter = false # Disable Karpenter
enable_aws_cloudwatch_metrics = false
enable_aws_for_fluentbit = false
tags = local.tags
depends_on = [module.eks_blueprints.managed_node_groups]
}
```
### Execute o PLAN do Terraform
Verifique os recursos criados por esta execução
```bash
cd ~/environment/latam-containers-roadshow/workshops/eks/terraform/
terraform plan
```
### Por fim, o Terraform APPLY
para remover o Karpenter
```bash
terraform apply --auto-approve
```
Fazer isso removerá o Karpenter e todos os recursos relacionados do nosso cluster.
# Observabilidade com o Amazon Cloudwatch Container Insights
Neste módulo, aprenderemos e aproveitaremos o novo CloudWatch Container Insights para ver como você pode usar os recursos nativos do CloudWatch para monitorar o desempenho do seu cluster EKS.
Você pode usar o CloudWatch Container Insights para coletar, agregar e resumir métricas e logs de seus aplicativos e microsserviços em contêiner. O Container Insights está disponível para plataformas Amazon Elastic Container Service, Amazon Elastic Kubernetes Service e Kubernetes no Amazon EC2. As métricas incluem a utilização de recursos como CPU, memória, disco e rede. O Container Insights também fornece informações de diagnóstico, como falhas de reinicialização de contêiner, para ajudá-lo a isolar problemas e resolvê-los rapidamente.
Vamos primeiro habilitar os complementos CloudWatch e Fluent-bit no arquivo `latam-containers-roadshow/workshops/eks/terraform/main.tf`. Altere o atributo `enable_aws_cloudwatch_metrics` `false` para `true`, o atributo `enable_aws_for_fluentbit` de `false`para`true` e o atributo `enable_karpenter` de `true`para`false`:
```terraform
module "eks_blueprints_kubernetes_addons" {
source = "github.com/aws-ia/terraform-aws-eks-blueprints//modules/kubernetes-addons?ref=v4.0.7"
eks_cluster_id = module.eks_blueprints.eks_cluster_id
# EKS Managed Add-ons
enable_amazon_eks_vpc_cni = true
enable_amazon_eks_coredns = true
enable_amazon_eks_kube_proxy = true
# Add-ons
enable_aws_load_balancer_controller = true
enable_metrics_server = true
enable_cluster_autoscaler = false
enable_karpenter = false # Disable Karpenter
enable_aws_cloudwatch_metrics = true # Enable CloudWatch metrics
enable_aws_for_fluentbit = true # Enable fluent-bit
tags = local.tags
depends_on = [module.eks_blueprints.managed_node_groups]
}
```
### Executar o PLAN do Terraform
Verifique os recursos criados por esta execução
```bash
cd ~/environment/latam-containers-roadshow/workshops/eks/terraform/
terraform plan
```
### Por fim, o Terraform APPLY
para criar recursos
```bash
terraform apply --auto-approve
```
Fazer isso criará o `amazon-cloudwatch` namespace em nosso cluster com os recursos do Container Insights e o `logging` namespace com os recursos de bits fluentes.
## Verifique se o CloudWatch Container Insights está funcionando
Para verificar se os dados estão sendo coletados no CloudWatch, inicie a IU do CloudWatch Containers em seu navegador usando o link gerado pelo comando abaixo:
```bash
echo "
Use the URL below to access Cloudwatch Container Insights in $AWS_REGION:
https://${AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${AWS_REGION}#container-insights:infrastructure
"
```
A partir daqui você pode ver que as métricas estão sendo coletadas e apresentadas para`CloudWatch`. Você pode alternar entre vários menus suspensos para ver EKS Services, EKS Cluster e muito mais.
## Verificando os logs do aplicativo
Para verificar os logs do aplicativo usando o Container Insights, você só precisa selecionar o aplicativo que deseja inspecionar no`List View`:
Neste caso selecionamos o Karpenter, após clicar no Karpenter será apresentado um painel do CloudWatch, com todas as métricas coletadas pelo agente CW em nosso cluster:
Para verificar os logs, role a página para baixo e sob `Pod performance`, selecione os`karpenter-xxx`debaixo:
Em seguida, clique em`Actions`>`View application logs`e clique em`Run query`:
Pronto, agora podemos ver os logs gerados pelo pod do aplicativo Karpenter.
# GitOps com Flux
GitOps, um termo cunhado pela [Weave](https://www.weave.works/), é uma forma de fazer[entrega contínua](https://aws.amazon.com/devops/continuous-delivery/). O Git é usado como fonte única de verdade para implantação em seu cluster.**Isso é fácil para uma equipe de desenvolvimento, pois eles já estão familiarizados com o git e não precisam conhecer outras ferramentas.**
Neste módulo do workshop vamos configurar o [CD do Flux](https://fluxcd.io) em nosso cluster EKS e implementar um aplicativo de exemplo usando o Flux.
1. Faça o Fork do repositório do GitHub para sua conta do GitHub. Se você não tiver uma conta do GitHub, crie uma usando[esse link](https://github.com/).
2. Exporte seu token de acesso pessoal e nome de usuário do GitHub:
```bash
export GITHUB_TOKEN=
export GITHUB_USER=
echo "export GITHUB_TOKEN=${GITHUB_TOKEN}" | tee -a ~/.bash_profile
echo "export GITHUB_USER=${GITHUB_USER}" | tee -a ~/.bash_profile
```
> **_NOTA:_** Para criar seu token de acesso pessoal do GitHub, siga as instruções de[esse link](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
3. Instale o Flux CLI:
```bash
curl -s https://fluxcd.io/install.sh | sudo bash
```
4. Verifique se você tem tudo o que é necessário para executar o Flux executando o seguinte comando:
```bash
flux check --pre
```
A saída é semelhante a:
```output
► checking prerequisites
✔ kubernetes 1.22.2 >=1.20.6
✔ prerequisites checks passed
```
5. Faça o Bootstrap, usando o comando `flux bootstrap` você pode instalar o Flux em um cluster Kubernetes e configurá-lo para se gerenciar a partir de um repositório Git.
```bash
flux bootstrap github \
--owner=${GITHUB_USER} \
--repository=latam-containers-roadshow \
--path=./workshops/eks/fluxcd-examples/clusters/my-cluster/ \
--read-write-key \
--branch=main \
--namespace=flux-system \
--components-extra=image-reflector-controller,image-automation-controller
```
O comando acima gerará uma chave SSH (o padrão é RSA 2048, mas pode ser alterado com --ssh-key-algorithm) e solicitará que você adicione a chave pública SSH como uma chave de implantação ao seu repositório.
A saída é semelhante a:
```output
► connecting to github.com
✔ repository created
✔ repository cloned
✚ generating manifests
✔ components manifests pushed
► installing components in flux-system namespace
deployment "source-controller" successfully rolled out
deployment "kustomize-controller" successfully rolled out
deployment "helm-controller" successfully rolled out
deployment "notification-controller" successfully rolled out
✔ install completed
► configuring deploy key
✔ deploy key configured
► generating sync manifests
✔ sync manifests pushed
► applying sync manifests
◎ waiting for cluster sync
✔ bootstrap finished
```
6. Para verificar se o fluxo fez a reconciliação com sucesso execute o comando abaixo:
```bash
flux get all
```
A saída terá a seguinte aparência:
```output
NAME REVISION SUSPENDED READY MESSAGE
gitrepository/flux-system main/2b3ef06 False True stored artifact for revision 'main/2b3ef069e9730074e4c48584228d6bfd1390b591'
NAME REVISION SUSPENDED READY MESSAGE
helmrepository/sample-chart 2df73a274124471769e659912cf88d9faae18f6307193bff262bc3eab75f52c7 False True stored artifact for revision '2df73a274124471769e659912cf88d9faae18f6307193bff262bc3eab75f52c7'
NAME REVISION SUSPENDED READY MESSAGE
helmchart/flux-system-app1 0.1.0 False True pulled 'hello-world' chart with version '0.1.0'
NAME REVISION SUSPENDED READY MESSAGE
helmrelease/app1 0.1.0 False True Release reconciliation succeeded
NAME REVISION SUSPENDED READY MESSAGE
kustomization/apps main/2b3ef06 False True Applied revision: main/2b3ef06
kustomization/flux-system main/2b3ef06 False True Applied revision: main/2b3ef06
kustomization/infrastructure False False kustomization path not found: stat /tmp/kustomization-1526705579/infrastructure: no such file or directory
```
## Estrutura do repositório do Flux
Use o [Kustomize Controller](https://fluxcd.io/docs/components/kustomize/) para fazer referência a manifestos no repositório.
Um objeto Kustomization define a origem dos manifestos do Kubernetes fazendo referência a um objeto gerenciado pelo [controlador de origem](https://github.com/fluxcd/source-controller), o caminho para o arquivo Kustomization dentro dessa origem e o intervalo no qual a saída da compilação kustomize é aplicada no cluster.
```output
├── apps
│ ├── app1.yaml
│ ├── kustomization.yaml
│ ├── sample-app.yaml
│ └── sources
│ ├── kustomization.yaml
│ └── sample-chart.yaml
├── clusters
│ └── my-cluster
│ ├── apps.yaml
│ └── infrastructure.yaml
└── infrastructure
├── add-ons
│ ├── cluster-autoscaler.yaml
│ ├── kustomization.yaml
│ ├── load-balancer-controller.yaml
│ └── metric-server.yaml
├── kustomization.yaml
└── sources
├── bitnami.yaml
├── cluster-autoscaler.yaml
├── eks-charts.yaml
└── kustomization.yaml
```
**apps:** A pasta onde vamos criar nossos manifestos de aplicação para implantação do Flux.
**infrastructure:** Pasta onde colocaremos seus complementos e manifestos de componentes de infraestrutura.
**clusters/meu-cluster:** Pasta onde o Flux coloca os arquivos bootstrap apontando para o nosso`Kustomizations manifests`.
### Manifesto de exemplo de customização
Vamos verificar um manifesto de Kustomization para ver como é, vamos abrir o`apps/kustomization.yaml`
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app1.yaml
- sources
```
Como você pode ver acima, o objeto Kustomization é responsável por referenciar todos os outros manifestos que temos no repositório, dessa forma o Flux sabe qual manifesto aplicar em nosso cluster.
## Alterando um manifesto de aplicativo existente
Vamos alterar o `replicaCount`no aplicativo nginx que foi implantado pelo flux.
1. Verifique quantas réplicas existem em`app1`namespace com o comando abaixo:
```bash
kubectl get pods -napp1
```
> **_NOTA:_** Este aplicativo é um exemplo de nginx implantado pelo flux, o manifesto deste aplicativo já está em nosso repositório bifurcado.
2. Altere a contagem de réplicas do nosso aplicativo, vamos clonar nosso novo repositório GitHub bifurcado:
```bash
git clone https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/latam-containers-roadshow ~/environment/latam-containers-roadshow-${GITHUB_USER}
```
3. Agora que o repositório está clonado, vamos alterar a quantidade de réplicas no manifesto da nossa aplicação:
```bash
cd ~/environment/latam-containers-roadshow-${GITHUB_USER}/workshops/eks/fluxcd-examples/apps/
```
4. Abrir`app1.yaml`arquivo e altere o`replicaCount`a partir de`2`para`1`:
```yaml
values:
replicaCount: 2 # Change here
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
install: {}
```
```yaml
values:
replicaCount: 1 # Change here
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
install: {}
```
5. Depois de editar o arquivo, vamos confirmar as alterações:
```bash
cd ~/environment/latam-containers-roadshow-${GITHUB_USER}/
git add .
git commit -m "Changed amount of replicas in app1"
git push origin main
```
6. Para verificar o processo de reconciliação, abra um novo terminal no Cloud9 e execute o comando abaixo:
```bash
watch flux get all
```
Na saída verifique o hash de commit em seu repositório e veja se ele corresponde ao`Applied revision:`
```output
kustomization/apps main/b92120e False True Applied revision: main/b92120e
```
7. Vamos verificar agora a quantidade de réplicas em nosso`app1`namespace:
```bash
kubectl get pods -napp1
```
A saída terá a seguinte aparência:
```output
NAME READY STATUS RESTARTS AGE
app1-hello-world-b75b989b-tgf5z 1/1 Running 0 22m
```
O Flux fez o **loop de reconciliação e garante o estado**que mudamos em nosso`app1`manifesto do aplicativo.
## Criando um novo aplicativo e implantando-o usando o Flux
Para exemplificar o deploy de uma nova aplicação vamos utilizar um[exemplo de aplicativo hello-world](https://gallery.ecr.aws/amazon-lightsail/hello-world)desenvolvido pela equipe do Amazon Lightsail.
1. Criando os manifestos Namespace, Deployment e Service:
```bash
cat < ~/environment/latam-containers-roadshow-${GITHUB_USER}/workshops/eks/fluxcd-examples/apps/hello-world-flux.yaml
apiVersion: v1
kind: Namespace
metadata:
name: hello-world
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: hello-world
name: hello-world
namespace: hello-world
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
strategy: {}
template:
metadata:
labels:
app: hello-world
spec:
containers:
- image: public.ecr.aws/amazon-lightsail/hello-world:hello-world
name: hello-world
ports:
- containerPort: 80
resources: {}
---
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-world
name: hello-world
namespace: hello-world
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: hello-world
type: LoadBalancer
EoF
```
3. Agora que criamos o arquivo, vamos enviá-lo:
```bash
cd ~/environment/latam-containers-roadshow-${GITHUB_USER}/
git add .
git commit -m "Created new hello-world application"
git push origin main
```
> **_NOTA:_** O Flux ainda não fará o loop de reconciliação, precisamos primeiro referenciar o novo arquivo criado em um manifesto de customização.
4. Adicione a referência do novo arquivo criado em nosso`kustomization.yaml`:
```bash
cd ~/environment/latam-containers-roadshow-${GITHUB_USER}/workshops/eks/fluxcd-examples/apps/
```
Adicione a linha`hello-world-flux.yaml`em`kustomization.yaml`manifest, deve ficar como abaixo:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app1.yaml
- sources
```
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- app1.yaml
- sources
- hello-world-flux.yaml # Add it here
```
5. Finalmente vamos confirmar as alterações.
```bash
cd ~/environment/latam-containers-roadshow-${GITHUB_USER}/
git add .
git commit -m "Added hello-world.yaml into Kustomization manifest"
git push origin main
```
## Verificando o aplicativo hello-world Implantado pelo Flux
Vamos verificar se nosso aplicativo foi implantado com sucesso, em nosso`hello-world`manifesto do aplicativo, definimos um Namespace, Deployment e um Service.
1. Vamos descrever os namespaces:
```bash
kubectl get ns
```
A saída terá a seguinte aparência:
```output
NAME STATUS AGE
amazon-cloudwatch Active 25h
app1 Active 73m
default Active 2d22h
flux-system Active 76m
hello-world Active 2m56s
karpenter Active 27h
kube-node-lease Active 2d22h
kube-public Active 2d22h
kube-system Active 2d22h
logging Active 25h
```
Como você pode ver, um`hello-world`namespace foi criado.
2. Verificando os objetos dentro do`hello-world`namespace:
```bash
kubectl get all -nhello-world
```
A saída terá a seguinte aparência:
```output
NAME READY STATUS RESTARTS AGE
pod/hello-world-654bd9b767-jd8hl 1/1 Running 0 4m2s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/hello-world LoadBalancer 172.20.47.89 a8a1c4a36beed41fb9b40106bcc79f4e-373990773.us-east-1.elb.amazonaws.com 80:30578/TCP 4m2s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello-world 1/1 1 1 4m2s
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-world-654bd9b767 1 1 1 4m2s
```
Então o Flux fez o loop de reconciliação com sucesso.
3. Execute o comando abaixo para obter a URL do aplicativo:
```bash
echo HELLO_WORLD_URL=$(kubectl get svc -nhello-world | awk '{print $4}' | grep -vi exter)
```
Em seguida, abra-o no navegador, se tudo correu bem, você poderá ver o exemplo do aplicativo hello-world lightsail implantado pelo Flux.
# Limpando os recursos
## Executar Terraform Destroy
```bash
cd latam-containers-roadshow/workshops/eks/terraform/
terraform destroy --auto-approve
```