View a markdown version of this page

Configurar o cluster do Amazon EKS para workloads de IA/ML usando o Terraform - Amazon EKS

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

dica

Registre-se 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 e do Karpenter 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=

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

Pré-requisitos

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.

  • kubectl >= 1.36. Consulte Configurar o kubectl e o eksctl para obter instruções de configuração.

  • AWSCLI >= 2.27. Para obter instruções de configuração, consulte Instalar.

  • jq. Para obter instruções de configuração, consulte Baixar jq.

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

Este passo a passo usa o código do Terraform no repositório de exemplos da AWS no GitHub 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, 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 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
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

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.

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

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

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

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

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

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 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)

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

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

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

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

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

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
  2. 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

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

Validar métricas de GPU do DCGM

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

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

Bucket do S3 de pesos do modelo

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

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
}

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

Com o cluster pronto, você pode prosseguir para Carregar e servir modelo para implantar um grande modelo de linguagem e interagir com o endpoint de inferência.

Limpeza

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

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.

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.

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.