

 **Ajudar a melhorar esta página** 

Para contribuir com este guia de usuário, escolha o link **Editar esta página no GitHub**, disponível no painel direito de cada página.

# Configurar o cluster do Amazon EKS para workloads de IA/ML usando o Terraform
<a name="ml-cluster-setup-tf"></a>

**dica**  
 [Registre-se](https://events.eksworkshop.com/workshops/genai/) para os próximos workshops do Amazon EKS AI/ML.

Esta seção guia você pelas etapas de criação da infraestrutura necessária para executar workloads de treinamento ou inferência no Amazon EKS usando o Terraform. As etapas incluem a criação de um cluster do EKS, nós habilitados para GPU com o Modo Automatizado do EKS ou o Karpenter, uma pilha de monitoramento com Prometheus e Grafana, e o armazenamento do Amazon S3 para os pesos do modelo.

Consulte a documentação do [Modo Automático do EKS](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) e do [Karpenter](https://karpenter.sh/docs/) para obter mais informações sobre como esses recursos provisionam e escalam automaticamente as instâncias do EC2 nos clusters do EKS.

 **Arquitetura e fluxo de trabalho de alto nível** 

![Arquitetura de alto nível mostrando um <shared id=](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-tf-architecture.png)


O diagrama ilustra a arquitetura de alto nível da AWS para a configuração desta seção.

## Pré-requisitos
<a name="cluster-setup-tf-prerequisites"></a>

**Importante**  
Os recursos que você cria neste tutorial, incluindo clusters do EKS, instâncias de GPU, Application Load Balancers e o Amazon Managed Service for Prometheus, incorrem em cobranças. Para evitar cobranças contínuas, exclua os recursos quando terminar.
+ Terraform >= 1.15.0. Para obter instruções de configuração, consulte [Installing Terraform](https://developer.hashicorp.com/terraform/install).
+  `kubectl` >= 1.36. Consulte [Configurar o `kubectl` e o `eksctl`](install-kubectl.md) para obter instruções de configuração.
+  AWSCLI >= 2.27. Para obter instruções de configuração, consulte [Instalar](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html).
+  `jq`. Para obter instruções de configuração, consulte [Baixar jq](https://jqlang.github.io/jq/download/).

Verifique as versões das suas ferramentas:

```
terraform --version
aws --version
kubectl version --client
jq --version
```

## Etapa 1: baixar e implantar o código do Terraform
<a name="cluster-setup-tf-deploy"></a>

Este passo a passo usa o código do Terraform no repositório de exemplos da AWS no GitHub [sample-eks-docs](https://github.com/aws-samples/sample-eks-docs). Clone o repositório em um diretório de trabalho:

```
git clone git@github.com:aws-samples/sample-eks-docs.git
cd sample-eks-docs/ai-ml/set-up-cluster
```

O repositório tem a seguinte estrutura no diretório `ai-ml/set-up-cluster/` em que você acabou de entrar:

```
set-up-cluster/
├── scripts/
│   └── cleanup.sh
└── terraform/
    ├── auto-mode/
    └── karpenter/
```

O repositório fornece dois caminhos de implantação. Escolha apenas um e use-o em todo o guia.
+  **Modo Automático do EKS** (`terraform/auto-mode/`): além dos principais [complementos de rede, armazenamento e balanceamento de carga](https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html#addon-consider-auto), o Modo Automático do EKS inclui e gerencia os seguintes recursos para workloads de treinamento e inferência: agente de monitoramento de nós do EKS, reparo automático de nós, snapshotter [SOCI](https://github.com/awslabs/soci-snapshotter) para extração rápida de contêineres e prontidão de GPU para o NodeClass padrão. O plug-in de dispositivo NVIDIA está incluído na AMI acelerada do Bottlerocket que o Modo Auomático do EKS usa para nós habilitados para GPU.
+  **Karpenter autogerenciado** (`terraform/karpenter/`): em um cluster do EKS sem o Modo Automático do EKS, o código do Terraform instala e configura os componentes necessários para workloads de treinamento e inferência. Isso inclui os complementos de rede (CNI da VPC, CoreDNS, kube-proxy), o Karpenter, o agente de monitoramento de nós do EKS, o plug-in de dispositivo NVIDIA e o snapshotter SOCI para extração rápida de contêineres.

**Importante**  
Escolha o Modo Automático do EKS ou o Karpenter autogerenciado e use-o em todo o guia. Mudar no meio do processo exige a destruição do cluster e o recomeço do zero.

 **Opções de cluster do EKS: Modo Automático do EKS e Karpenter autogerenciado** 

![Comparação lado a lado das duas opções de cluster: um cluster do Modo Automático do EKS com um NodePool e um cluster padrão do EKS com o Karpenter, o CoreDNS, o CNI da VPC, o plug-in de dispositivo NVIDIA, o agente do Identidade de Pods do EKS, o Agente de Monitoramento de Nós, o kube-proxy, e o NodeClass e o NodePool](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-cli-cluster-options.png)


**O Grafana é acessível publicamente por HTTP com credenciais padrão**  
O `Ingress` do ALB do Grafana padroniza `var.my_cidr` para `0.0.0.0/0`, o que expõe o Grafana à internet pública por meio de HTTP simples com credenciais de administrador padrão. Os verificadores automatizados descobrem balanceadores de carga públicos em minutos. Você **deve** restringir o acesso substituindo `var.my_cidr` pelo seu próprio endereço IP:  

```
export MY_CIDR="$(curl -s https://checkip.amazonaws.com)/32"
terraform apply -var "my_cidr=${MY_CIDR}"
```
Trate a lista de permissões de IP de origem como uma proteção mínima, não como uma proteção completa. Altere também a senha padrão do administrador do Grafana após o primeiro login. Para uma postura mais robusta, mude `alb.ingress.kubernetes.io/scheme` para `internal` (acessível somente de dentro da sua VPC ou em uma VPN conectada) e adicione um certificado TLS.

### Implantar o cluster
<a name="_deploy_the_cluster"></a>

Vá para o diretório do caminho escolhido, inicialize o Terraform e aplique:

Ambas as variantes usam como padrão a região `us-east-2`. Para implantar em outra região, adicione `-var "region={{region-code}}"` ao comando `terraform apply` na etapa a seguir, em que {{region-code}} é a região da AWS na qual você deseja implantar.

O código do Terraform usa todas as zonas de disponibilidade disponíveis na região de destino, excluindo `use1-az3`, `usw1-az2` e `cac1-az3` porque o [Amazon EKS não é compatível com a alocação do ambiente de gerenciamento nessas zonas](https://repost.aws/knowledge-center/eks-cluster-creation-errors).

------
#### [ EKS Auto Mode ]

```
cd terraform/auto-mode
terraform init
terraform apply
```

Esse comando pode levar alguns minutos para ser concluído.

------
#### [ Self-managed Karpenter ]

```
cd terraform/karpenter
terraform init
terraform apply
```

Esse processo leva cerca de 15 minutos. Ele cria um cluster do EKS com um grupo de nós gerenciados dedicado à hospedagem de complementos e do controlador do Karpenter. O Terraform instala o Karpenter com a fila de interrupção de instâncias spot habilitada e os future gates `NodeRepair` e `StaticCapacity` ativados. Ele também instala o plug-in de dispositivo da NVIDIA, o AWS Load Balancer Controller e a pilha de monitoramento.

------

### Analisar os resultados do Terraform
<a name="_review_the_terraform_outputs"></a>

Quando a aplicação é concluída, o Terraform exibe as seguintes saídas (os valores variam de acordo com sua configuração):

```
Apply complete! Resources: 74 added, 0 changed, 0 destroyed.

Outputs:

cluster_name         = "ai-eks-docs"
configure_kubectl    = "aws eks update-kubeconfig --region us-east-2 --name ai-eks-docs --alias ai-eks-docs"
configure_model_bucket = "export MODEL_BUCKET=ai-eks-docs-models-20250612abc1"
model_bucket         = "ai-eks-docs-models-20250612abc1"
node_iam_role_name   = "ai-eks-docs-eks-auto-20250612..."
region               = "us-east-2"
```

A saída `configure_kubectl` é um comando pronto para execução que aponta o `kubectl` para o cluster. A saída `model_bucket` contém o nome do bucket do S3 para os pesos do modelo. A saída `node_iam_role_name` mostra o perfil do IAM que os nós usam.

### Configurar o kubectl
<a name="_configure_kubectl"></a>

Aponte o `kubectl` para o novo cluster. A saída `configure_kubectl` é um comando pronto para ser executado:

```
eval "$(terraform output -raw configure_kubectl)"
```

### Verificar o cluster
<a name="_verify_the_cluster"></a>

------
#### [ EKS Auto Mode ]

```
kubectl get pods --all-namespaces
```

Saída esperada:

```
NAMESPACE     NAME                                                       READY   STATUS    RESTARTS   AGE
kube-system   metrics-server-5db89f9ffd-h4mlr                            1/1     Running   0          3m
kube-system   metrics-server-5db89f9ffd-nd748                            1/1     Running   0          3m
monitoring    kube-prometheus-stack-grafana-ff9b5fd57-dh562              3/3     Running   0          3m
monitoring    kube-prometheus-stack-kube-state-metrics-5dcbfdf69b-wd6qn  1/1     Running   0          3m
monitoring    kube-prometheus-stack-operator-548c4f4485-m5wh2            1/1     Running   0          3m
monitoring    kube-prometheus-stack-prometheus-node-exporter-p2z7l       1/1     Running   0          3m
monitoring    prometheus-kube-prometheus-stack-prometheus-0              2/2     Running   0          3m
```

No Modo Automático do EKS, a CNI da VPC, o kube-proxy e o CoreDNS são executados como componentes gerenciados e não aparecem como pods no `kube-system`.

------
#### [ Self-managed Karpenter ]

```
kubectl get pods --all-namespaces
```

A saída esperada inclui o Karpenter, o CoreDNS, o kube-proxy, o aws-node (CNI da VPC), o Agente de Identidade de Pods do EKS, o agente de monitoramento de nós do EKS e o plug-in de dispositivo da NVIDIA:

```
NAMESPACE     NAME                                                              READY   STATUS    RESTARTS   AGE
kube-system   aws-node-bzdcz                                                    2/2     Running   0          5m
kube-system   aws-node-vkbhb                                                    2/2     Running   0          5m
kube-system   coredns-7dbb8998cf-9b9wk                                          1/1     Running   0          5m
kube-system   coredns-7dbb8998cf-pwtjd                                          1/1     Running   0          5m
kube-system   ebs-csi-controller-748f54b69-8h7jv                                6/6     Running   0          5m
kube-system   ebs-csi-controller-748f54b69-v2mv8                                6/6     Running   0          5m
kube-system   eks-node-monitoring-agent-5qw4l                                   1/1     Running   0          5m
kube-system   eks-node-monitoring-agent-7lrtm                                   1/1     Running   0          5m
kube-system   eks-pod-identity-agent-ddvlv                                      1/1     Running   0          5m
kube-system   eks-pod-identity-agent-q4g29                                      1/1     Running   0          5m
kube-system   karpenter-898ff78-cndbd                                           1/1     Running   0          5m
kube-system   karpenter-898ff78-hfbhn                                           1/1     Running   0          5m
kube-system   kube-proxy-gcfnh                                                  1/1     Running   0          5m
kube-system   kube-proxy-tktcf                                                  1/1     Running   0          5m
kube-system   metrics-server-5b789db597-cm9qd                                   1/1     Running   0          5m
kube-system   metrics-server-5b789db597-w57b6                                   1/1     Running   0          5m
kube-system   nvidia-device-plugin-node-feature-discovery-gc-66cb7f5dc-xvftc    1/1     Running   0          5m
kube-system   nvidia-device-plugin-node-feature-discovery-master-854d6b5hnqtp   1/1     Running   0          5m
monitoring    kube-prometheus-stack-grafana-6797bcb59f-2zlwg                    3/3     Running   0          5m
monitoring    kube-prometheus-stack-kube-state-metrics-8446bd549c-q6gjc         1/1     Running   0          5m
monitoring    kube-prometheus-stack-operator-5f499b784-vf5xb                    1/1     Running   0          5m
monitoring    kube-prometheus-stack-prometheus-node-exporter-4c6wq              1/1     Running   0          5m
monitoring    kube-prometheus-stack-prometheus-node-exporter-l5t25              1/1     Running   0          5m
monitoring    prometheus-kube-prometheus-stack-prometheus-0                     2/2     Running   0          5m
```

Verifique se o plug-in de dispositivo NVIDIA está instalado. Nenhum pod de plug-in de dispositivo aparece até que um NodePool de GPU com o rótulo correspondente `amiFamily=al2023` seja provisionado:

```
kubectl get daemonset nvidia-device-plugin -n kube-system
```

Saída esperada:

```
NAME                   DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR      AGE
nvidia-device-plugin   0         0         0       0            0           amiFamily=al2023   5m
```

------

## Etapa 2: criar o NodePool dinâmico
<a name="cluster-setup-tf-create-gpu-nodepool"></a>

Os NodePools de GPU são opcionais. Por padrão, o `terraform apply` cria o cluster e a pilha de monitoramento sem capacidade e sem faturamento de GPU. Para provisionar nós de GPU, passe a variável `nodepools` com um nome de estratégia.

Habilite a estratégia `spot-ondemand`, que provisiona instâncias de GPU da família G com uma geração maior que 4, usando a capacidade spot com sob demanda como fallback:

```
terraform apply -var 'nodepools={"spot-ondemand"={}}'
```

Esse comando aplica os modelos de NodePool e NodeClass do diretório `nodepools/spot-ondemand/`. Ambos os caminhos usam a mesma API NodePool, mas diferem no NodeClass ao qual o NodePool faz referência.

------
#### [ EKS Auto Mode ]

O NodePool faz referência ao NodeClass gerenciado `default`, que já seleciona a AMI acelerada do Bottlerocket, os drivers da NVIDIA, o plug-in de dispositivo da NVIDIA e o SOCI parallel pull. A estratégia `spot-ondemand` não inclui nenhum NodeClass próprio nesse caminho.

Valide o NodePool:

```
kubectl get nodepools,nodeclasses
```

Saída esperada. O NodePool `gpu-inf` une os NodePools `general-purpose` e `system` integrados, e todos os três fazem referência ao NodeClass `default` gerenciado:

```
NAME                                    NODECLASS   NODES   READY   AGE
nodepool.karpenter.sh/general-purpose   default     0       True    12m
nodepool.karpenter.sh/gpu-inf           default     0       True    20s
nodepool.karpenter.sh/system            default     1       True    12m

NAME                                  ROLE                       READY   AGE
nodeclass.eks.amazonaws.com/default   ai-eks-docs-eks-auto-...   True    12m
```

------
#### [ Self-managed Karpenter ]

O Terraform aplica um `EC2NodeClass` `gpu-inf` personalizado junto com o NodePool. O `EC2NodeClass` fixa o alias da AMI AL2023 otimizada para EKS, habilita o SOCI por meio do feature gate `FastImagePull` e configura `instanceStorePolicy: RAID0` para mover o cache de imagem containerd para o NVMe local.

Valide o NodePool e o EC2NodeClass:

```
kubectl get nodepools,ec2nodeclasses
```

Saída esperada. O par `gpu-inf` une o EC2NodeClass e o NodePool `general-purpose` que o Terraform cria para workloads sem GPU:

```
NAME                                    NODECLASS         NODES   READY   AGE
nodepool.karpenter.sh/general-purpose   general-purpose   1       True    14m
nodepool.karpenter.sh/gpu-inf           gpu-inf           0       True    25s

NAME                                             READY   AGE
ec2nodeclass.karpenter.k8s.aws/general-purpose   True    14m
ec2nodeclass.karpenter.k8s.aws/gpu-inf           True    25s
```

------

Ambos os caminhos mostram nós `0` para `gpu-inf` até que uma workload de GPU seja agendada. O Modo Automático do EKS e o Karpenter só inicializam nós quando os pods pendentes os exigem.

## Etapa 3: testar com um exemplo de pod
<a name="cluster-setup-tf-test-with-a-sample-pod"></a>

Teste a configuração do NodePool da GPU com um pod `nvidia-smi`:

```
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nvidia-smi
  labels:
    guide: ai-eks-docs
spec:
  restartPolicy: OnFailure
  tolerations:
    - key: "nvidia.com/gpu"
      operator: "Exists"
      effect: "NoSchedule"
  containers:
    - name: nvidia-smi
      image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
      command: ["nvidia-smi"]
      resources:
        limits:
          nvidia.com/gpu: 1
EOF
```

Verifique se o pod foi agendado e concluído com êxito:

```
kubectl get pods nvidia-smi
```

Saída esperada:

```
NAME         READY   STATUS      RESTARTS   AGE
nvidia-smi   0/1     Completed   0          67s
```

O `STATUS: Completed` significa que o comando `nvidia-smi` foi executado e encerrado. Verifique os logs do pod para ver a GPU detectada pelo nó:

```
kubectl logs nvidia-smi
```

Saída esperada:

```
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.159.03             Driver Version: 580.159.03     CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA L4                      On  |   00000000:31:00.0 Off |                    0 |
| N/A   41C    P8             13W /   72W |       0MiB /  23034MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
```

A saída mostra o modelo da GPU, a versão do driver, a versão do CUDA e a memória disponível. Neste exemplo, o Karpenter provisionou uma instância G6 que tem uma GPU NVIDIA L4 com 24 GB de memória. O modelo e a memória da GPU variam de acordo com o tipo de instância selecionado pelo Karpenter. As instâncias G5 têm GPUs NVIDIA A10G (24 GB), as instâncias G6 têm GPUs NVIDIA L4 (24 GB) e as instâncias G6e têm GPUs NVIDIA L40S (48 GB).

Para entender como o Karpenter e o agendador do Kubernetes se coordenaram para provisionar um nó e posicionar o pod, confira os eventos do ciclo de vida do pod:

```
kubectl describe pod nvidia-smi
```

Saída esperada:

```
Events:
  Type     Reason            Age   From                   Message
  ----     ------            ----  ----                   -------
  Warning  FailedScheduling  75s   default-scheduler      0/2 nodes are available: 2 node(s) had untolerated taint(s).
  Normal   Nominated         74s   eks-auto-mode/compute  Pod should schedule on: nodeclaim/gpu-inf-z6q75
  Normal   Scheduled         35s   default-scheduler      Successfully assigned default/nvidia-smi to i-0eb897a8302551589
  Normal   Pulling           27s   kubelet                spec.containers{nvidia-smi}: Pulling image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal"
  Normal   Pulled            22s   kubelet                spec.containers{nvidia-smi}: Successfully pulled image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" in 5.625s (5.626s including waiting). Image size: 37440620 bytes.
  Normal   Created           22s   kubelet                spec.containers{nvidia-smi}: Container created
  Normal   Started           21s   kubelet                spec.containers{nvidia-smi}: Container started
```

Esses eventos mostram a sequência de agendamento do pod: o agendamento inicial do pod falha porque não existe nenhum nó de GPU (`FailedScheduling`), o Karpenter nomeia um novo NodeClaim (`Nominated`), o agendador atribui o pod quando o nó fica pronto (`Scheduled`) e, em seguida, a imagem do contêiner é extraída e iniciada. O Modo Automático do EKS tem o pull paralelo SOCI (Seekable OCI) instalado e configurado por padrão nas instâncias G, P e Trn, e o caminho autogerenciado do Karpenter o configura explicitamente por meio do feature gate `FastImagePull`.

**nota**  
Em um cluster autogerenciado do Karpenter, o evento `Nominated` exibe `karpenter/compute` em vez de `eks-auto-mode/compute`.

Um NodeClaim é uma requisição que o Karpenter cria para provisionar um nó específico. Ele mostra o tipo de instância, o tipo de capacidade, a AZ e se o nó está pronto:

```
kubectl get nodeclaims
```

Saída esperada:

```
NAME            TYPE        CAPACITY   ZONE         NODE                  READY   AGE
gpu-inf-z6q75   g6.xlarge   spot       us-east-2a   i-0eb897a8302551589   True    5m
```

O tipo de instância e a AZ variam. Qualquer instância da família G com uma geração maior que 4 é elegível.

**dica**  
Se nenhum nó aparecer, verifique se há erros de capacidade insuficiente:  

```
kubectl get events | grep InsufficientCapacityError
```
O Karpenter armazena em cache as ofertas indisponíveis por três minutos. Ampliar os tipos de instância e as AZs permitidas no NodePool aumenta as chances de conseguir capacidade.

**nota**  
As instâncias spot inicializadas pelo Karpenter não aparecem no console de requisições spot do EC2. O Karpenter usa a API [`CreateFleet`](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateFleet.html) do EC2 com o `type: instant`. As instâncias aparecem no console de instâncias do EC2 com um ciclo de vida `spot`.

## Etapa 4: adicionar capacidade reservada ao NodePool (opcional)
<a name="cluster-setup-tf-attach-odcr"></a>

Embora o NodePool de GPU da Etapa 2 provisione instâncias spot ou sob demanda dinamicamente, alguns casos de uso exigem capacidade garantida. Você pode criar uma reserva de capacidade sob demanda (ODCR) para garantir que a capacidade da GPU esteja disponível quando necessário.

Com o Terraform, um único comando cria o ODCR, um NodeClass personalizado que faz referência à reserva por tag e atualiza o NodePool para incluir `reserved` como um tipo de capacidade. O Terraform marca o ODCR com `nodepool=reserved-spot-ondemand` e o NodeClass o seleciona por essa tag.

**Atenção**  
O comando a seguir cria um ODCR que fatura imediatamente e continua cobrando até que você o destrua com `terraform destroy` ou com o script de limpeza, independentemente de os nós estarem em execução nele ou não.

Use os padrões (`g6e.4xlarge`, 1 instância, primeiro cluster da AZ):

```
terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={}}}'
```

Escolha o tipo de instância, a contagem e a AZ:

```
terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={instance_type="g6e.2xlarge",instance_count=1,az="us-east-2a"}}}'
```

O objeto `reservation` tem os seguintes campos:
+  `instance_type`: o tipo de instância de GPU a ser reservada. Padrão: `g6e.4xlarge`.
+  `instance_count`: o número de instâncias a serem reservadas. Padrão: `1`.
+  `az`: a zona de disponibilidade para a reserva. Padrão: `""` (usa o primeiro cluster da AZ).

**Importante**  
As estratégias `spot-ondemand` e `reserved-spot-ondemand` são mutuamente exclusivas. Você pode habilitar no máximo um na variável `nodepools`. Se você usou `spot-ondemand` anteriormente na Etapa 2, o comando `reserved-spot-ondemand` o substitui porque ambos gerenciam o mesmo NodePool `gpu-inf`.

Se você receber um erro `InsufficientInstanceCapacity`, a reserva não poderá ser atendida na AZ especificada. Cancele a operação do Terraform (Ctrl\+C) e execute novamente com outro valor de `az`:

```
terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={instance_type="g6e.4xlarge",az="us-east-2b"}}}'
```

Depois de aplicar, o Terraform atualiza o NodePool para incluir `reserved`, `spot` e `on-demand` nos requisitos de tipo de capacidade. O Karpenter trata `reserved` como a opção mais econômica e a inicia primeiro. Quando a reserva está cheia, ele recorre a spot ou sob demanda.

No caminho do Modo Automático do EKS, o Terraform cria um NodeClass `gpu-inf` personalizado (porque o NodeClass `default` incluído é somente leitura) que faz referência ao ODCR por tag por meio de `capacityReservationSelectorTerms`. No caminho autogerenciado do Karpenter, o Terraform reaplica o EC2NodeClass `gpu-inf` com os `capacityReservationSelectorTerms` adicionados e atualiza o NodePool para incluir `reserved`.

Verifique se o ODCR foi criado:

```
aws ec2 describe-capacity-reservations \
  --filters "Name=state,Values=active" "Name=tag:nodepool,Values=reserved-spot-ondemand" \
  --query 'CapacityReservations[0].{Id:CapacityReservationId,State:State,InstanceType:InstanceType,AvailableCount:AvailableInstanceCount}' \
  --output table \
  --region $(terraform output -raw region)
```

Verifique se o NodeClass faz referência ao ODCR:

------
#### [ EKS Auto Mode ]

```
kubectl get nodeclasses gpu-inf -o yaml | grep 'id: cr-.*'
```

Saída esperada:

```
        id: cr-xxxxxxxxxxxxxxxxx
```

------
#### [ Self-managed Karpenter ]

```
kubectl get ec2nodeclasses gpu-inf -o yaml | grep 'id: cr-.*'
```

Saída esperada:

```
        id: cr-xxxxxxxxxxxxxxxxx
```

------

Verifique se o NodePool está pronto:

```
kubectl get nodepools gpu-inf
```

Saída esperada:

```
NAME      NODECLASS   NODES   READY   AGE
gpu-inf   gpu-inf     0       True    30s
```

### Verificar se a capacidade reservada é usada primeiro, com fallback para spot
<a name="cluster-setup-tf-step4-validate"></a>

Depois de aplicar as alterações, verifique se o Karpenter prioriza a capacidade reservada e depois recorre a spot ou sob demanda. Faça uma implantação com duas réplicas que solicite uma GPU por pod. O ODCR é para uma instância (1 GPU), então, o primeiro pod aciona o Karpenter para iniciar um nó reservado. O segundo pod não cabe no nó reservado e aciona o Karpenter para iniciar outro nó usando capacidade spot ou sob demanda.

```
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-overflow-test
  labels:
    guide: ai-eks-docs
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gpu-overflow-test
  template:
    metadata:
      labels:
        app: gpu-overflow-test
        guide: ai-eks-docs
    spec:
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: nvidia-smi
          image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
          command: ["sh", "-c", "nvidia-smi && sleep infinity"]
          resources:
            limits:
              nvidia.com/gpu: 1
EOF
```

Ao contrário do pod de teste `nvidia-smi` da Etapa 3, que foi executado e encerrado, essa implantação mantém os pods em execução (`sleep infinity`) para que eles retenham a GPU e impeçam que o nó seja consolidado.

Verifique os pods agendados em nós diferentes:

```
kubectl get pods -l app=gpu-overflow-test -o wide
```

Saída esperada:

```
NAME                                 READY   STATUS    RESTARTS   AGE     IP            NODE                  NOMINATED NODE   READINESS GATES
gpu-overflow-test-55d55ff5b9-dvg52   1/1     Running   0          4m42s   10.0.75.210   i-08741a36089ff2088   <none>           <none>
gpu-overflow-test-55d55ff5b9-hw4m9   1/1     Running   0          4m43s   10.0.82.49    i-0f50cdbacb2017202   <none>           <none>
```

Verifique as NodeClaims para ver os tipos de capacidade:

```
kubectl get nodeclaims
```

Saída esperada:

```
NAME            TYPE          CAPACITY   ZONE         NODE                  READY   AGE
gpu-inf-vw99m   g6e.4xlarge   reserved   us-east-2c   i-0f50cdbacb2017202   True    6m
gpu-inf-s65s6   g6.xlarge     spot       us-east-2b   i-08741a36089ff2088   True    5m59s
```

O nó reservado foi iniciado primeiro, seguido por um nó spot ou sob demanda quando a reserva ficou cheia.

Limpar a implantação de teste:

```
kubectl delete deployment gpu-overflow-test
```

## Monitoramento
<a name="cluster-setup-tf-monitoring"></a>

O Terraform já provisionou a pilha de monitoramento completa durante o `terraform apply` na Etapa 1. A pilha inclui um espaço de trabalho do Amazon Managed Service for Prometheus (AMP), políticas do IAM e associações da Identidade de Pods do EKS para gravação remota do Prometheus e acesso a consultas do Grafana, o chart do Helm kube-prometheus-stack (Prometheus, Grafana, kube-state-metrics, node-exporter) e o NVIDIA DCGM Exporter para métricas de GPU.

Esta seção aborda a verificação dos componentes de monitoramento implantados.

### Verificar os pods de monitoramento
<a name="_verify_monitoring_pods"></a>

Aguarde até que todos os pods de monitoramento estejam prontos:

```
kubectl wait --for=condition=Ready pod --all -n monitoring --timeout=300s
kubectl get pods -n monitoring
```

Saída esperada:

```
NAME                                                       READY   STATUS    RESTARTS   AGE
kube-prometheus-stack-grafana-7c58f54f77-rftrj             3/3     Running   0          5m
kube-prometheus-stack-kube-state-metrics-d68dcbc84-5smxq   1/1     Running   0          5m
kube-prometheus-stack-operator-5895df479f-ttm47            1/1     Running   0          5m
kube-prometheus-stack-prometheus-node-exporter-t9q7s       1/1     Running   0          5m
kube-prometheus-stack-prometheus-node-exporter-x6vfb       1/1     Running   0          5m
prometheus-kube-prometheus-stack-prometheus-0              2/2     Running   0          5m
```

### Acessar o Grafana
<a name="cluster-setup-tf-grafana"></a>

O Grafana é exposto por meio de um AWS Application Load Balancer (ALB) voltado para a internet, restrito ao CIDR que você definiu em `var.my_cidr`. Exiba o URL do balanceador de carga (aguarde um ou dois minutos para o ALB provisionar):

```
echo "http://$(kubectl get ingress kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
```

Abra o URL em seu navegador. Faça login com o nome de usuário `admin` e a senha do seguinte comando:

```
kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
```

### Verificar o pipeline de métricas
<a name="_verify_the_metrics_pipeline"></a>

Para verificar se o pipeline de métricas está funcionando de ponta a ponta:

1. Navegue até **Conexões > Fontes de dados** e confirme se o **Amazon-Managed-Prometheus** está listado como a fonte de dados padrão.

    **Valide a fonte de dados do AMP no Grafana**   
![Página Connections do Grafana mostrando Amazon-Managed-Prometheus listado como a fonte de dados padrão](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-cli-prometheus-ds-validate.png)

1. **Navegue até Drilldown > Metrics** e procure a métrica `up`. Você deve ver os resultados dos alvos de extração do cluster.

    **Validar a métrica `up` no Grafana**   
![A página Drilldown Metrics do Grafana mostrando a métrica up com barras de status verdes indicando os alvos de extração ativos](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-cli-prometheus-metrics-validate.png)

Se `up` mostrar resultados, o pipeline (cluster → Prometheus → AMP → Grafana) estará funcionando.

### Validar métricas de GPU do DCGM
<a name="_validate_dcgm_gpu_metrics"></a>

O DaemonSet do DCGM Exporter é executado em nós de GPU e reporta métricas de utilização da GPU, memória, temperatura, consumo de energia, largura de banda do NVLink e atividade do tensor.

Verifique o DaemonSet do DCGM Exporter:

```
kubectl get daemonset dcgm-exporter -n monitoring
```

Quando um nó da GPU estiver em execução (na Etapa 2 ou na Etapa 4), você verá um ou mais pods prontos. Para validar as métricas do DCGM, navegue até **Drilldown > Metrics** no Grafana e procure `DCGM_`.

 **Validar as métricas do DCGM no Grafana** 

![Página Drilldown Metrics do Grafana filtrada por DCGM_ mostrando métricas de GPU incluindo DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, DCGM_FI_DEV_ENC_UTIL, DCGM_FI_DEV_FB_FREE e DCGM_FI_DEV_FB_USEDCGM_FI_DEV_FB_USED](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-cli-dcgm-metrics-validate.png)


Para visualizar o painel, navegue até **Dashboards > GPU Monitoring > NVIDIA DCGM Exporter Dashboard**.

 **Painel do NVIDIA DCGM Exporter no Grafana** 

![Painel do NVIDIA DCGM Exporter do Grafana mostrando os painéis Grafana GPU Utilization, GPU Avg Temp, GPU Framebuffer Mem Used e GPU Power Total](https://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-cluster-setup-cli-dcgm-dashboard.png)


## Bucket do S3 de pesos do modelo
<a name="cluster-setup-tf-model-bucket"></a>

O Terraform já criou um bucket do Amazon S3 para armazenar pesos de modelos, um ServiceAccount `model-storage-sa` no namespace `default`, uma política do IAM tendo como escopo o bucket e uma associação da Identidade de Pods do EKS que os vincule. Os pods de workloads que definiram `serviceAccountName: model-storage-sa` poderão ler e gravar no bucket.

### Verificar o bucket
<a name="_verify_the_bucket"></a>

Recupere o nome do bucket das saídas do Terraform:

```
MODEL_BUCKET=$(terraform output -raw model_bucket)
echo ${MODEL_BUCKET}
```

Verifique se o bucket existe:

```
aws s3api head-bucket --bucket ${MODEL_BUCKET}
```

Saída esperada:

```
{
    "BucketArn": "arn:aws:s3:::ai-eks-docs-models-20250612abc1",
    "BucketRegion": "us-east-2",
    "AccessPointAlias": false
}
```

#### Validação do acesso ao S3 a partir de um pod
<a name="cluster-setup-tf-s3-validate"></a>

Execute um pod temporário com a imagem da AWS CLI, usando a ServiceAccount `model-storage-sa`, para confirmar que o Identidade de Pods do EKS está configurado corretamente e o acesso ao S3 funciona:

```
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: s3-test
  labels:
    guide: ai-eks-docs
spec:
  serviceAccountName: model-storage-sa
  containers:
    - name: aws-cli
      image: public.ecr.aws/aws-cli/aws-cli:2.27.0
      command:
        - sh
        - -c
        - |
          echo "=== Caller Identity ==="
          aws sts get-caller-identity
          echo ""
          echo "=== S3 Write Test ==="
          echo "pod identity works" | aws s3 cp - s3://${MODEL_BUCKET}/test.txt
          echo ""
          echo "=== S3 List Test ==="
          aws s3 ls s3://${MODEL_BUCKET}/
          echo ""
          echo "=== S3 Delete Test ==="
          aws s3 rm s3://${MODEL_BUCKET}/test.txt
  restartPolicy: Never
EOF
```

Aguarde a conclusão do pod e verifique os logs:

```
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/s3-test --timeout=300s
kubectl logs s3-test
```

Saída esperada:

```
=== Caller Identity ===
{
    "UserId": "AROA...:eks-ai-eks-docs-model-s-...",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/ai-eks-docs-models-.../eks-ai-eks-docs-..."
}

=== S3 Write Test ===
upload: - to s3://ai-eks-docs-models-20250612abc1/test.txt

=== S3 List Test ===
2026-07-15 12:00:00         19 test.txt

=== S3 Delete Test ===
delete: s3://ai-eks-docs-models-20250612abc1/test.txt
```

A identidade do chamador confirma que o pod assumiu o perfil de armazenamento do modelo por meio da Identidade de Pods do EKS. Os comandos do S3 confirmam o acesso para leitura e gravação.

Limpe o pod de teste:

```
kubectl delete pod s3-test
```

## Próximas etapas
<a name="cluster-setup-tf-next-steps"></a>

Com o cluster pronto, você pode prosseguir para [Carregar e servir modelo](ml-inference-load-serve-model.md) para implantar um grande modelo de linguagem e interagir com o endpoint de inferência.

## Limpeza
<a name="cluster-setup-tf-cleanup"></a>

**dica**  
Se você planejar continuar nas próximas seções deste guia, pule a limpeza completa. Faça a limpeza completa apenas quando terminar.

Exclua as workloads de teste para que nenhum pod esteja retendo nós de GPU:

```
kubectl delete pod nvidia-smi --ignore-not-found
kubectl delete deployment gpu-overflow-test --ignore-not-found
```

### Cancelar a reserva de capacidade sem destruir o cluster
<a name="cluster-setup-tf-cleanup-cancel-reservation"></a>

Se você quiser apenas liberar o ODCR e retornar para a capacidade spot e sob demanda, mude a variável `nodepools` de volta para a estratégia `spot-ondemand`:

```
terraform apply -var 'nodepools={"spot-ondemand"={}}'
```

Isso remove `reserved` dos requisitos de tipo de capacidade do NodePool, destrói o ODCR e mantém o cluster, a pilha de monitoramento e o bucket do S3 intactos.

**Importante**  
O cancelamento de uma reserva não encerra as instâncias já em execução nela. Essas instâncias continuam funcionando com taxas padrão sob demanda até serem encerradas. Exclua primeiro as workloads da GPU, conforme mostrado acima, para que o nó reservado seja drenado antes que a reserva seja liberada.

### Destruir o cluster e todos os recursos gerenciados pelo Terraform
<a name="cluster-setup-tf-cleanup-destroy"></a>

Drene os nós gerenciados pelo Karpenter antes de destruí-los, para que nenhum ciclo de vida de nó em andamento bloqueie a destruição. Exclua quaisquer PodDisruptionBudgets que impeçam a drenagem e, em seguida, exclua os NodeClaims:

```
kubectl delete pdb -A --all --ignore-not-found
kubectl delete nodeclaim --all --wait=true --timeout=900s
```

Em seguida, destrua tudo o que o Terraform criou, incluindo o cluster do EKS, a VPC, a pilha de monitoramento, os NodePools e NodeClasses, o bucket do S3 do modelo e qualquer ODCR:

```
terraform destroy
```

**Atenção**  
O bucket do S3 de pesos do modelo é criado com `force_destroy = true`, então `terraform destroy` exclui o bucket junto com todos os pesos do modelo que você enviou para ele. Primeiro, copie tudo o que você quiser manter em outro local.

**nota**  
O repositório também disponibiliza um auxiliar `scripts/cleanup.sh` que executa as etapas de drenagem e destruição acima e, em seguida, varre quaisquer volumes EBS órfãos marcados com o nome do cluster. Execute-o de dentro do diretório `terraform/<mode>/` do qual você aplicou e passe `--auto-approve` para ignorar o prompt de confirmação do Terraform.

### Verificar se a reserva foi cancelada
<a name="cluster-setup-tf-cleanup-verify"></a>

Confirme que nenhuma reserva de capacidade ativa permaneça para o cluster:

```
aws ec2 describe-capacity-reservations \
  --filters "Name=state,Values=active" "Name=tag:nodepool,Values=reserved-spot-ondemand" \
  --query 'CapacityReservations[].CapacityReservationId' \
  --output text
```

Um resultado vazio significa que nenhuma reserva está ativa e nenhuma cobrança adicional será aplicada.