View a markdown version of this page

Configurar o cluster do Amazon EKS para workloads de IA/ML usando CLIs - 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 CLIs

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 por meio de comandos da CLI. 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 o cluster do EKS com NodeClass e NodePool do Karpenter, pilha de monitoramento do Grafana e do Prometheus gravando no Amazon Managed Service para Prometheus, um bucket do Amazon S3 para os pesos do modelo e as etapas numeradas do fluxo de trabalho

O diagrama ilustra a arquitetura de alto nível da AWS para a configuração desta seção. As etapas numeradas à direita indicam a ordem na qual você realiza a configuração nas etapas abaixo.

Pré-requisitos

  • kubectl >= 1.35. 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.

  • Helm >= 3.14. Para obter instruções de configuração, consulte Configurar o Helm.

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

  • eksctl >= 0.227.0. Para obter instruções de instalação, consulte Installing na documentação do eksctl.

Verifique sua versão do eksctl:

eksctl version

Se você estiver usando uma versão anterior à 0.227.0, siga o eksctl installation guide para atualizar para a versão mais recente.

Definição de variáveis de ambiente

Mantenha o nome do cluster e a região da AWS a seguir consistentes ao longo de todas estas etapas. Alterá-los pode fazer com que os comandos subsequentes sejam direcionados ao cluster do EKS errado.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

O uso de todas as AZs disponíveis melhora a tolerância a falhas e aumenta as chances de obtenção de capacidade de GPU:

export AZS=$(aws ec2 describe-availability-zones \ --region ${AWS_REGION} \ --query "AvailabilityZones[?ZoneId!='use1-az3' && ZoneId!='usw1-az2' && ZoneId!='cac1-az3'].ZoneName" \ --output text | tr '\t' ',') echo $AZS
Importante

As zonas de disponibilidade use1-az3, usw1-az2, e cac1-az3 são excluídas porque o Amazon EKS não é compatível com colocação do ambiente de gerenciamento nessas zonas. A criação de um cluster com sub-redes em qualquer uma dessas zonas resulta em um erro de UnsupportedAvailabilityZoneException.

Saída esperada:

us-east-2a,us-east-2b,us-east-2c

As AZs na saída variam por região. Este exemplo mostra as AZs disponíveis para a região us-east-2.

Criar cluster e NodePool de GPU

Esta seção fornece dois caminhos para a criação do cluster do EKS e de nós habilitados para GPU, mostrados no diagrama a seguir. Escolha apenas uma opção ao longo de todo o guia.

  • Modo Automático do EKS: 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 a 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: em um cluster do EKS sem o Modo Automático do EKS, você é responsável por instalar e configurar 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.

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

Em cada uma das etapas a seguir, escolha um caminho (Modo Automático do EKS, Karpenter) e siga-o sempre. Depois de concluir as etapas do caminho escolhido, você terá um cluster do EKS com um NodePool de GPU pronto para programar as workloads da GPU.

Etapa 1: Criar um cluster

Comece por criar o cluster do EKS e instalar os componentes de cluster necessários para as workloads da GPU.

Com o Modo Automático do EKS, um único comando eksctl create cluster --enable-auto-modeprovisiona um cluster do EKS pronto para as workloads da GPU.

Com o Karpenter autogerenciado, o comando eksctl create cluster provisiona os principais complementos de rede; depois, etapas adicionais são necessárias para permitir o reparo automático de nós por meio de um controle de atributo do Karpenter, instalar o agente de monitoramento de nós do EKS e instalar o plug-in de dispositivo NVIDIA.

EKS Auto Mode

Criar cluster do Modo Automático do EKS

eksctl create cluster \ --name=$CLUSTER_NAME \ --region=$AWS_REGION \ --enable-auto-mode \ --version=1.35 \ --zones=$AZS

Esse comando pode levar alguns minutos para ser concluído. Após a conclusão, o eksctl atualiza automaticamente o arquivo kubeconfig para funcionar com o cluster recém-provisionado. Verifique se o cluster está operacional:

kubectl get pods --all-namespaces

Saída esperada:

NAMESPACE NAME READY STATUS RESTARTS AGE kube-system metrics-server-55cf976ddd-cz2mw 1/1 Running 0 3m kube-system metrics-server-55cf976ddd-wrjvv 1/1 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

Autenticar o Helm em ECR público

O eksctl extrai o chart do Helm do Karpenter do Amazon Public ECR. Faça a autenticação antes de criar o cluster para evitar um erro 403 na etapa de instalação do Helm:

aws ecr-public get-login-password --region us-east-1 \ | helm registry login --username AWS --password-stdin public.ecr.aws

O ECR público é um serviço global hospedado na região us-east-1. Use --region us-east-1 aqui, independentemente da região em que seu cluster do EKS esteja.

Saída esperada: Login Succeeded

Criar o cluster do EKS com o Karpenter

Armazene sua versão do Karpenter em uma variável de ambiente para uso posterior. Para as versões mais recentes do Karpenter, consulte Karpenter releases no GitHub.

export KARPENTER_VERSION=1.12.0
cat << EOF > /tmp/cluster-karpenter.yaml apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: ${CLUSTER_NAME} region: ${AWS_REGION} version: "1.35" tags: karpenter.sh/discovery: ${CLUSTER_NAME} availabilityZones: [$(echo $AZS | sed 's/,/, /g')] autoModeConfig: enabled: false iam: withOIDC: true karpenter: version: "${KARPENTER_VERSION}" withSpotInterruptionQueue: true managedNodeGroups: - name: system instanceType: m6i.2xlarge desiredCapacity: 2 minSize: 2 maxSize: 3 labels: node-role: system tags: karpenter.sh/discovery: ${CLUSTER_NAME} addons: - name: eks-pod-identity-agent - name: eks-node-monitoring-agent EOF eksctl create cluster -f /tmp/cluster-karpenter.yaml

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 Karpenter é instalado com a fila de interrupção de spot habilitada para que ele possa lidar com as recomendações de interrupção e rebalanceamento de spot. A configuração autoModeConfig.enabled: false deixa explícito que esse cluster não usa o Modo Automático do EKS, portanto, os componentes do Karpenter instalados nesse caminho são responsáveis pelo gerenciamento dos nós.

O cluster também tem o Agente de Identidade de Pods do EKS e o agente de monitoramento de nós do EKS instalados como complementos do EKS. O Identidade de Pods do EKS é usado posteriormente neste guia. O agente de monitoramento de nós do EKS é executado em todos os nós e lê os logs do kernel para definir as condições dos nós, como AcceleratedHardwareReady, KernelReady e NetworkingReady, que o reparo automático de nós do Karpenter usa para decidir quando substituir um nó não íntegro.

Verifique se o cluster está operacional:

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 e o agente de monitoramento de nós do EKS.

NAMESPACE NAME READY STATUS RESTARTS AGE karpenter karpenter-567547464c-s6vkx 1/1 Running 0 3m40s karpenter karpenter-567547464c-x7gmw 1/1 Running 0 3m40s kube-system aws-node-b6gf2 2/2 Running 0 12m kube-system aws-node-lcphh 2/2 Running 0 12m kube-system coredns-7d4dcbf4fb-ccvrr 1/1 Running 0 16m kube-system coredns-7d4dcbf4fb-qbhk2 1/1 Running 0 16m kube-system eks-node-monitoring-agent-h79vm 1/1 Running 0 9m45s kube-system eks-node-monitoring-agent-tf4dw 1/1 Running 0 9m45s kube-system eks-pod-identity-agent-5jbtc 1/1 Running 0 12m kube-system eks-pod-identity-agent-rwcrc 1/1 Running 0 12m kube-system kube-proxy-p4bmq 1/1 Running 0 12m kube-system kube-proxy-v5nwr 1/1 Running 0 12m kube-system metrics-server-5b966ff79c-hr58p 1/1 Running 0 9m22s kube-system metrics-server-5b966ff79c-szs2d 1/1 Running 0 9m22s

Habilitar o reparo automático de nós

O Modo Automático do EKS habilita o reparo automático de nós por padrão. No Karpenter autogerenciado, o reparo automático de nós é controlado pelo controle de atributo NodeRepair=true e deve ser habilitado explicitamente. O comando a seguir corrige a implantação do Karpenter para adicionar o controle de atributo NodeRepair=true. A atualização do ambiente de implantação aciona a implantação dos pods do Karpenter:

kubectl set env deployment/karpenter -n karpenter \ FEATURE_GATES=NodeRepair=true

Saída esperada:

deployment.apps/karpenter env updated

Espere que os pods do Karpenter sejam implantados:

kubectl rollout status deployment/karpenter -n karpenter

Instale o plug-in de dispositivo NVIDIA

A AMI do AL2023 otimizada para EKS não inclui o plug-in de dispositivo NVIDIA (ao contrário da AMI do Bottlerocket usada pelo Modo Automático do EKS). Instale-o via Helm para tornar os recursos da GPU utilizáveis com pods no cluster.

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin helm repo update
cat << 'EOF' > /tmp/nvdp-values.yaml mofedEnabled: false nodeSelector: amiFamily: al2023 gfd: enabled: true nfd: worker: tolerations: - operator: "Exists" EOF
helm install nvidia-device-plugin nvdp/nvidia-device-plugin \ --namespace kube-system \ -f /tmp/nvdp-values.yaml
  • mofedEnabled: false: desabilita a verificação da presença do Mellanox OFED (InfiniBand), que a AWS não usa

  • nodeSelector.amiFamily: al2023: limita o escopo do DaemonSet apenas aos nós do AL2023 (o Bottlerocket já tem o plug-in incorporado)

  • gfd.enabled: true: habilita os rótulos do GPU Feature Discovery (nvidia.com/gpu.product, nvidia.com/gpu.memory etc.)

Verifique se o plug-in de dispositivo NVIDIA está instalado. A expectativa é que não haja nenhum pod de plug-in de dispositivo até que um NodePool de GPU com o rótulo correspondente 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 2m5s
Atenção

Tanto para o Modo Automático do EKS quanto para os caminhos autogerenciados do Karpenter, o reparo automático de nós se comporta da mesma maneira para os nós provisionados por NodePools. O reparo automático de nós no Modo Automático do EKS e no Karpenter é um método de interrupção imperativo que ignora PodDisruptionBudgets, a anotação karpenter.sh/do-not-disrupt e o terminationGracePeriod. O reparo automático do nó espera 10 minutos antes de substituir um nó com a condição AcceleratedHardwareReady definida como False e 30 minutos para outras condições de reparo.

Etapa 2: criar o NodePool dinâmico

Defina um NodePool que provisione dinamicamente as instâncias de GPU da família G com geração acima de 4, usando a capacidade spot com capacidade sob demanda como fallback. Os caminhos Modo Automático do EKS e do Karpenter usam a mesma API NodePool, sendo a única diferença o NodeClass para o qual ele aponta. No Modo Automático do EKS, a NodeClass default incluída já seleciona a AMI correta e configura a extração paralela do SOCl, portanto, o NodePool é o único objeto que você cria. No Karpenter autogerenciado, você também precisa de uma EC2NodeClass personalizada que fixe a AMI e ajuste o SOCI.

EKS Auto Mode

No Modo Automático do EKS, a NodeClass default incluída seleciona automaticamente a AMI do Bottlerocket para as instâncias da GPU, o que inclui drivers NVIDIA pré-instalados, o plug-in de dispositivo NVIDIA e a extração paralela do SOCI. Você só precisa aplicar um NodePool que referencie a NodeClass default:

cat << 'EOF' | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs spec: nodeClassRef: group: eks.amazonaws.com kind: NodeClass name: default taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: eks.amazonaws.com/instance-category operator: In values: ["g"] - key: eks.amazonaws.com/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

Esse NodePool provisiona instâncias de GPU da família G com geração acima de 4 (G5, G6e, G7e etc.). A taint nvidia.com/gpu:NoSchedule garante que somente pods qualificados para GPU sejam programados nesses nós.

Self-managed Karpenter

O Karpenter autogerenciado não inclui uma NodeClass padrão. Primeiro, você cria uma EC2NodeClass que fixa o alias da AMI NVIDIA do AL2023 otimizada para EKS, habilita o SOCI por meio do controle de atributo FastImagePull e configura instanceStorePolicy: RAID0 para mover o cache de imagem containerd para o NVMe local. Depois, você cria o NodePool que o referencia.

Criar a EC2NodeClass

cat << EOF | kubectl apply -f - apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "eksctl-KarpenterNodeRole-${CLUSTER_NAME}" amiSelectorTerms: - alias: al2023@latest subnetSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} securityGroupSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} tags: karpenter.sh/discovery: ${CLUSTER_NAME} instanceStorePolicy: RAID0 userData: | MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="BOUNDARY" --BOUNDARY Content-Type: application/node.eks.aws --- apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: featureGates: FastImagePull: true containerd: config: | [plugins."io.containerd.snapshotter.v1.soci"] [plugins."io.containerd.snapshotter.v1.soci".blob] max_concurrent_downloads_per_image = 20 concurrent_download_chunk_size = "16mb" max_concurrent_unpacks_per_image = 12 discard_unpacked_layers = true --BOUNDARY-- EOF

instanceStorePolicy: RAID0 reúne discos NVMe locais em uma matriz RAID-0. O alias da AMI al2023@latest é resolvido para a AMI do AL2023 otimizada para EKS. Quando o Karpenter inicia um tipo de instância de GPU, ele seleciona automaticamente a variante acelerada AL2023_x86_64_NVIDIA, que inclui o driver NVIDIA pré-instalado.

O controle de atributo FastImagePull habilita o modo de extração paralela do snapshotter SOCI, que baixa e descompacta as camadas de imagem simultaneamente. Isso corresponde ao comportamento do Modo Automático do EKS nas famílias de instâncias G, P e Trn. O bloco containerd.config ajusta o snapshotter SOCI para imagens hospedadas pelo ECR:

  • max_concurrent_downloads_per_image: 20 permite até 20 downloads de camadas em paralelo por imagem. O padrão são 3 no Bottlerocket e 20 no AL2023. Valor recomendado para o ECR.

  • concurrent_download_chunk_size: "16mb" divide cada camada em blocos de 16 MB baixados paralelamente por meio de solicitações do intervalo HTTP. Recomendado para registros compatíveis com GETs de intervalo (o ECR é).

  • max_concurrent_unpacks_per_image: 12 descompacta até 12 camadas de uma única vez. O padrão são 1 no Bottlerocket e 12 no AL2023.

  • discard_unpacked_layers: true exclui blobs das camadas compactadas após a descompactação para economizar espaço em disco.

Para obter mais opções de ajuste do SOCI (downloads simultâneos por imagem, tamanho do bloco etc.), consulte o esquema do Karpenter com SOCI.

Valide a EC2NodeClass:

kubectl get ec2nodeclass gpu-inf

Saída esperada: READY True. Se False, execute kubectl describe ec2nodeclass gpu-inf e verifique se há condições de tags de sub-rede ou grupo de segurança faltando.

Criar o NodePool da GPU

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs amiFamily: al2023 spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: karpenter.k8s.aws/instance-category operator: In values: ["g"] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

O rótulo amiFamily: al2023 do modelo de nó é o que o DaemonSet do plug-in de dispositivo NVIDIA usa para selecionar esses nós.

Valide que o NodePool foi criado:

kubectl get nodepool gpu-inf

Saída esperada:

NAME NODECLASS NODES READY AGE gpu-inf default 0 True 8s

No caminho autogerenciado do Karpenter, a coluna NODECLASS mostra gpu-inf em vez de default.

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: 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 restartPolicy: OnFailure EOF

Verifique se o pod foi agendado e concluído com sucesso.

kubectl get pods

Saída esperada:

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

O STATUS: Concluído 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.126.09 Driver Version: 580.126.09 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 RTX PRO 6000 Blac... On | 00000000:2B:00.0 Off | 0 | | N/A 30C P0 81W / 600W | 0MiB / 97887MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+

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 G7e que tem uma GPU NVIDIA RTX PRO 6000 Blackwell com 96 GB de memória. 30C é a temperatura atual da GPU e P0 significa que a GPU está em seu mais alto estado de performance (ociosa, mas pronta). O valor 81W/600W mostra o consumo de energia atual em relação à capacidade máxima de energia, e 0MiB/97887MiB mostra a memória atual da GPU usada em relação ao total disponível. Como o Pod acabou de executar o nvidia-smi e sair, nenhuma workload está usando a GPU, então a memória está em 0 e a alimentação está ociosa. A versão do driver de GPU NVIDIA (580.126.09) vem da AMI do Bottlerocket, enquanto a versão do CUDA (13.0) vem da imagem do contêiner. 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 G6e têm GPUs NVIDIA L40S (48 GB) e as instâncias G7e têm GPUs NVIDIA RTX PRO 6000 (96 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 po nvidia-smi

Saída esperada:

Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 60s default-scheduler 0/2 nodes are available: 2 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling. Normal Nominated 59s eks-auto-mode/compute Pod should schedule on: nodeclaim/gpu-inf-vxcnj Normal Scheduled 24s default-scheduler Successfully assigned default/nvidia-smi to i-0fb17a09bc4203164 Warning FailedCreatePodSandBox 21s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "7f85e25b220c8fb245187758dbbbc8efb3d40f3e49e13054404880daf4c3b2f0": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to setup network policy Normal Pulling 7s kubelet spec.containers{nvidia-smi}: Pulling image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" Normal Pulled 5s kubelet spec.containers{nvidia-smi}: Successfully pulled image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" in 1.237s (1.237s including waiting). Image size: 37442701 bytes. Normal Created 5s kubelet spec.containers{nvidia-smi}: Container created Normal Started 5s 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 uma nova NodeClaim (Nominada), o agendador atribui o Pod quando o nó fica pronto (Agendada) e, em seguida, a imagem do contêiner é extraída e iniciada. O Modo Automático do EKS vem com a extração paralela do SOCI (Seekable OCI) instalada e configurada imediatamente nas instâncias G, P e Trn. Observe que, graças à extração paralela do SOCI, a imagem do contêiner foi extraída do ECR em menos de 2 segundos (1,237 s).

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

kubectl get nodeclaims

Saída esperada da NodeClaim:

NAME            TYPE          CAPACITY    ZONE         NODE                  READY   AGE
gpu-inf-xxxxx   g7e.2xlarge   spot        us-east-2a   i-0xxxxxxxxxxxx       True    2m

O tipo de instância e a AZ podem variar. Qualquer instância da família G com geração > 4 é elegível.

O aviso FailedCreatePodSandBox em kubectl describe pod nvidia-smi é transitório e esperado. A CNI da VPC é inicializada de maneira assíncrona após a união do nó, e o kubelet faz uma nova tentativa automaticamente. Se o pod permanecer em ContainerCreating, verifique os eventos do nó com kubectl describe node <node-name>.

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 3 minutos. Ampliar os tipos de instância e as AZs permitidas no NodePool aumenta as chances de conseguir capacidade.

nota

As instâncias spot iniciadas pelo Karpenter não aparecem no console Solicitaçõ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)

Para usar capacidade reservada primeiro com o fallback spot/sob demanda, crie uma ODCR e conecte-a ao NodeClass e, em seguida, atualize o NodePool dinâmico da etapa 2 para também permitir capacidade reserved. A chamada de API de reserva é a mesma para os dois caminhos; o anexo NodeClass é diferente porque o Modo Automático do EKS e o Karpenter autogerenciado usam tipos diferentes de NodeClass.

Atenção

O comando a seguir gera uma cobrança do tipo de instância reservada até que você a cancele com aws ec2 cancel-capacity-reservation --capacity-reservation-id <id>.

Criar a reserva de capacidade:

CR_AZ="us-east-2a" INSTANCE_TYPE="g6e.4xlarge" aws ec2 create-capacity-reservation \ --instance-type $INSTANCE_TYPE \ --instance-platform Linux/UNIX \ --availability-zone "$CR_AZ" \ --instance-count 1 \ --instance-match-criteria open \ --end-date-type unlimited

Se você receber um erro InsufficientInstanceCapacity, mude CR_AZ para uma AZ diferente e tente novamente.

Procure o ID da reserva de capacidade e armazene-o em uma variável shell para as seguintes etapas:

CAPACITY_RESERVATION_ID=$(aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=instance-type,Values=${INSTANCE_TYPE}" \ --query 'CapacityReservations[0].CapacityReservationId' \ --output text \ --region ${AWS_REGION}) echo "Capacity reservation ID: ${CAPACITY_RESERVATION_ID}"

Depois, aplique as alterações de NodeClass e NodePool para o caminho:

EKS Auto Mode

No Modo Automático do EKS, o NodeClass default empacotado é somente leitura, então, crie uma NodeClass personalizada que referencie a reserva e atualize o NodePool para apontar para a NodeClass e adicione a capacidade reserved à lista capacity-type.

NODE_ROLE=$(kubectl get nodeclass default -o jsonpath='{.spec.role}') cat << EOF | kubectl apply -f - apiVersion: eks.amazonaws.com/v1 kind: NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "$NODE_ROLE" subnetSelectorTerms: - tags: alpha.eksctl.io/cluster-name: "$CLUSTER_NAME" kubernetes.io/role/internal-elb: "1" securityGroupSelectorTerms: - tags: aws:eks:cluster-name: "$CLUSTER_NAME" capacityReservationSelectorTerms: - id: "$CAPACITY_RESERVATION_ID" EOF

A tag kubernetes.io/role/internal-elb: "1" garante que os nós sejam iniciados somente em sub-redes privadas.

Atualize o NodePool para usar a NodeClass baseada em ODCR e inclua reserved como tipo de capacidade:

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs spec: nodeClassRef: group: eks.amazonaws.com kind: NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand", "reserved"] - key: eks.amazonaws.com/instance-category operator: In values: ["g"] - key: eks.amazonaws.com/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF
Self-managed Karpenter

Para o Karpenter autogerenciado, reaplique a EC2NodeClass criada na Etapa 2 com os capacityReservationSelectorTerms adicionados. O nome e a forma do campo correspondem à NodeClass do Modo Automático do EKS mostrado na outra guia.

cat << EOF | kubectl apply -f - apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "eksctl-KarpenterNodeRole-${CLUSTER_NAME}" amiSelectorTerms: - alias: al2023@latest subnetSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} securityGroupSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} tags: karpenter.sh/discovery: ${CLUSTER_NAME} instanceStorePolicy: RAID0 capacityReservationSelectorTerms: - id: "$CAPACITY_RESERVATION_ID" userData: | MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="BOUNDARY" --BOUNDARY Content-Type: application/node.eks.aws --- apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: featureGates: FastImagePull: true containerd: config: | [plugins."io.containerd.snapshotter.v1.soci"] [plugins."io.containerd.snapshotter.v1.soci".blob] max_concurrent_downloads_per_image = 20 concurrent_download_chunk_size = "16mb" max_concurrent_unpacks_per_image = 12 discard_unpacked_layers = true --BOUNDARY-- EOF

A única alteração na etapa 2 é o novo campo capacityReservationSelectorTerms. Todos os outros campos permanecem os mesmos.

Atualize o NodePool para incluir reserved como tipo de capacidade:

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs amiFamily: al2023 spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand", "reserved"] - key: karpenter.k8s.aws/instance-category operator: In values: ["g"] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

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.

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. A ODCR é para uma instância, 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 conservem a GPU e não liberem o nó.

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-59b97944fb-lq56c   1/1     Running   0          2m42s   192.168.186.240   i-057692590480155da   <none>           <none>
gpu-overflow-test-59b97944fb-z4zcx   1/1     Running   0          2m42s   192.168.130.64    i-0521ecd1849fa0578   <none>           <none>

Os dois pods estão operando, cada um em um nó diferente.

Verifique as NodeClaims para ver os tipos de capacidade:

kubectl get nodeclaims

Saída esperada:

NAME            TYPE          CAPACITY    ZONE         NODE                  READY   AGE
gpu-inf-shg5w   g6e.xlarge    reserved    us-east-2a   i-0ea91fdeef65b8cb6   True    2m2s
gpu-inf-ssnqf   g7e.2xlarge   spot        us-east-2b   i-00ccf7ce65cf3f6ca   True    112s

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

Instale uma pilha de monitoramento que colete métricas de cluster, de nó e de GPU no Amazon Managed Service for Prometheus (AMP) e visualize-as com o Grafana. O chart do Helm kube-prometheus-stack implanta o Prometheus para extrair e gravar remotamente métricas no AMP, além de um Grafana autogerenciado para painéis. O NVIDIA DCGM Exporter adiciona métricas específicas da GPU (utilização, memória, temperatura, alimentação, NVLink e atividade do tensor).

Prometheus, Grafana e o operador usam nós não de GPU por padrão porque os nós de GPU levam o taint nvidia.com/gpu:NoSchedule. O node-exporter e o DCGM Exporter são executados em nós de GPU para poderem extrair métricas de host e GPU de toda a frota.

Se você abriu um novo terminal, defina o nome e a região do cluster:

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

Criar um espaço de trabalho do AMP

Crie um espaço de trabalho do AMP para armazenar as métricas:

aws amp create-workspace \ --alias "amp-ws-${CLUSTER_NAME}" \ --region ${AWS_REGION}

Obtenha o ID do espaço de trabalho:

AMP_WORKSPACE_ID=$(aws amp list-workspaces \ --alias "amp-ws-${CLUSTER_NAME}" \ --query 'workspaces[0].workspaceId' \ --output text \ --region ${AWS_REGION}) echo "AMP Workspace ID: ${AMP_WORKSPACE_ID}"

Obtenha o endpoint de gravação remota:

AMP_ENDPOINT=$(aws amp describe-workspace \ --workspace-id ${AMP_WORKSPACE_ID} \ --query 'workspace.prometheusEndpoint' \ --output text \ --region ${AWS_REGION}) echo "AMP Endpoint: ${AMP_ENDPOINT}"

Criar uma política do IAM e associações do Identidade de Pods do EKS

Crie uma política do IAM que permita ao Prometheus gravar métricas remotamente e ao Grafana consultá-las:

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) AMP_POLICY_ARN=$(aws iam create-policy \ --policy-name "${CLUSTER_NAME}-amp-grafana-policy" \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Sid\": \"AllowAMPReadWrite\", \"Effect\": \"Allow\", \"Action\": [\"aps:ListWorkspaces\", \"aps:DescribeWorkspace\", \"aps:GetMetricMetadata\", \"aps:GetSeries\", \"aps:QueryMetrics\", \"aps:RemoteWrite\", \"aps:GetLabels\"], \"Resource\": \"arn:aws:aps:${AWS_REGION}:${ACCOUNT_ID}:workspace/*\"}, {\"Sid\": \"AllowCloudWatchMetrics\", \"Effect\": \"Allow\", \"Action\": [\"cloudwatch:DescribeAlarmsForMetric\", \"cloudwatch:ListMetrics\", \"cloudwatch:GetMetricData\", \"cloudwatch:GetMetricStatistics\"], \"Resource\": \"*\"}]}" \ --query 'Policy.Arn' \ --output text) echo "AMP Policy ARN: ${AMP_POLICY_ARN}"

Crie o namespace do monitoramento e as contas de serviço para o Prometheus e o Grafana:

kubectl create namespace monitoring kubectl create serviceaccount amp-iamproxy-ingest-service-account -n monitoring kubectl create serviceaccount grafana-sa -n monitoring

Crie associações do Identidade de Pods do EKS para vincular as contas do serviço à política do IAM:

eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name amp-iamproxy-ingest-service-account \ --role-name "${CLUSTER_NAME}-amp-ingest-role" \ --permission-policy-arns ${AMP_POLICY_ARN} \ --region ${AWS_REGION} eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name grafana-sa \ --role-name "${CLUSTER_NAME}-grafana-role" \ --permission-policy-arns ${AMP_POLICY_ARN} \ --region ${AWS_REGION}

Verifique se as ambas as associações do Identidade de Pods do EKS foram criadas:

eksctl get podidentityassociation --cluster ${CLUSTER_NAME} --region ${AWS_REGION}

A saída esperada deve incluir amp-iamproxy-ingest-service-account e grafana-sa no namespace monitoring.

Instalar o kube-prometheus-stack

Adicione o repositório do Helm:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update

Esse arquivo de valores omite um nodeSelector para o Prometheus, o Grafana e o operador: o taint nvidia.com/gpu:NoSchedule dos nós de GPU os mantém fora dos nós de GPU, então eles acabam no sistema ou ni pool de uso geral por padrão. O node-exporter usa uma tolerância curinga, então é executado em todos os nós, incluindo os nós de GPU, para coletar métricas em toda a frota.

Crie o arquivo de valores:

cat << EOF > /tmp/kube-prometheus-values.yaml prometheus: serviceAccount: create: false name: amp-iamproxy-ingest-service-account prometheusSpec: serviceAccountName: amp-iamproxy-ingest-service-account remoteWrite: - url: "${AMP_ENDPOINT}api/v1/remote_write" sigv4: region: "${AWS_REGION}" queueConfig: maxSamplesPerSend: 1000 maxShards: 200 capacity: 2500 retention: 5h scrapeInterval: 30s evaluationInterval: 30s podMonitorSelectorNilUsesHelmValues: false serviceMonitorSelectorNilUsesHelmValues: false alertmanager: enabled: false grafana: enabled: true serviceAccount: create: false name: grafana-sa grafana.ini: auth.sigv4: enabled: true sidecar: datasources: defaultDatasourceEnabled: false plugins: - grafana-amazonprometheus-datasource additionalDataSources: - name: Amazon-Managed-Prometheus type: grafana-amazonprometheus-datasource access: proxy url: "${AMP_ENDPOINT}" isDefault: true jsonData: sigV4Auth: true defaultRegion: "${AWS_REGION}" sigV4Region: "${AWS_REGION}" editable: true dashboardProviders: dashboardproviders.yaml: apiVersion: 1 providers: - name: default orgId: 1 folder: 'GPU Monitoring' type: file disableDeletion: false editable: true options: path: /var/lib/grafana/dashboards/default dashboards: default: nvidia-dcgm: gnetId: 25261 revision: 1 datasource: - name: DS_PROMETHEUS value: Amazon-Managed-Prometheus vllm: gnetId: 25263 revision: 1 datasource: - name: DS_PROMETHEUS value: Amazon-Managed-Prometheus prometheus-node-exporter: tolerations: - operator: Exists EOF

Valide que as variáveis foram preenchidas corretamente:

grep -E "url:|region:|tolerations:" /tmp/kube-prometheus-values.yaml

Você deve ver o URL completo do endpoint do AMP (começando com https://aps-workspaces…​), a região e a linha tolerations: do node-exporter. Se algum desses valores estiver vazio, exporte novamente as variáveis e recrie o arquivo.

Instale o chart:

helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring \ -f /tmp/kube-prometheus-values.yaml

Verifique se os pods estão em execução:

kubectl get pods -n monitoring

Saída esperada:

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

A pilha implanta os seguintes componentes:

  • Prometheus (StatefulSet): extrai métricas e as grava remotamente no AMP

  • Grafana: painéis e visualização, pré-configurado com a fonte de dados do AMP

  • kube-state-metrics: gera métricas sobre estado de objetos do Kubernetes (status de pod, solicitações/limites de recurso, estados de NodeClaim)

  • node-exporter (DaemonSet, um por nó): coleta métricas no nível do host (incluindo CPU, memória, disco e rede)

  • operator: gerencia os recursos personalizados do Prometheus e do Alertmanager

O Alertmanager está desabilitado nessa configuração.

Acessar o Grafana

Abra um terminal separado e use port-forward para acessar o Grafana:

kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring

Abra o http://localhost:3000 no 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

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

  1. Navegue até Connectionse > Data sources e confirme que 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.

Implante o DCGM Exporter para coletar métricas da GPU

O kube-prometheus-stack coleta métricas da CPU e da memória no nível do nó, mas não métricas da GPU. O NVIDIA DCGM Exporter adiciona utilização, memória, temperatura, consumo de energia, largura de banda NVLink e atividade do tensor da GPU.

helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts helm repo update

Defina a chave seletora de nó de GPU para o caminho. O Modo Automático do EKS e o Karpenter autogerenciado usam chaves de tag diferentes para o fabricante da GPU.

EKS Auto Mode
GPU_NODE_SELECTOR_KEY="eks.amazonaws.com/instance-gpu-manufacturer"
Self-managed Karpenter
GPU_NODE_SELECTOR_KEY="karpenter.k8s.aws/instance-gpu-manufacturer"

Crie o arquivo de valores do DCGM Exporter:

cat << EOF > /tmp/dcgm-exporter-values.yaml resources: requests: memory: "512Mi" cpu: "100m" limits: memory: "1Gi" cpu: "500m" serviceMonitor: enabled: true additionalLabels: release: kube-prometheus-stack nodeSelector: ${GPU_NODE_SELECTOR_KEY}: nvidia tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" customMetrics: | # Clocks DCGM_FI_DEV_SM_CLOCK, gauge, SM clock frequency (in MHz). DCGM_FI_DEV_MEM_CLOCK, gauge, Memory clock frequency (in MHz). # Temperature DCGM_FI_DEV_MEMORY_TEMP, gauge, Memory temperature (in C). DCGM_FI_DEV_GPU_TEMP, gauge, GPU temperature (in C). # Power DCGM_FI_DEV_POWER_USAGE, gauge, Power draw (in W). DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION, counter, Total energy consumption since boot (in mJ). # PCIe DCGM_FI_PROF_PCIE_TX_BYTES, counter, Number of bytes transmitted through PCIe TX (in KB) via NVML. DCGM_FI_PROF_PCIE_RX_BYTES, counter, Number of bytes received through PCIe RX (in KB) via NVML. DCGM_FI_DEV_PCIE_REPLAY_COUNTER, counter, Total number of PCIe retries. # Utilization (the sample period varies depending on the product) DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %). DCGM_FI_DEV_MEM_COPY_UTIL, gauge, Memory utilization (in %). DCGM_FI_DEV_ENC_UTIL, gauge, Encoder utilization (in %). DCGM_FI_DEV_DEC_UTIL, gauge, Decoder utilization (in %). # Errors and violations DCGM_FI_DEV_XID_ERRORS, gauge, Value of the last XID error encountered. DCGM_EXP_XID_ERRORS_COUNT, gauge, Value of count of XID errors encountered. DCGM_FI_DEV_POWER_VIOLATION, counter, Throttling duration due to power constraints (in us). DCGM_FI_DEV_THERMAL_VIOLATION, counter, Throttling duration due to thermal constraints (in us). DCGM_FI_DEV_SYNC_BOOST_VIOLATION, counter, Throttling duration due to sync-boost constraints (in us). DCGM_FI_DEV_BOARD_LIMIT_VIOLATION, counter, Throttling duration due to board limit constraints (in us). DCGM_FI_DEV_LOW_UTIL_VIOLATION, counter, Throttling duration due to low utilization (in us). DCGM_FI_DEV_RELIABILITY_VIOLATION, counter, Throttling duration due to reliability constraints (in us). # Memory usage DCGM_FI_DEV_FB_FREE, gauge, Framebuffer memory free (in MiB). DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB). # Retired pages DCGM_FI_DEV_RETIRED_SBE, counter, Total number of retired pages due to single-bit errors. DCGM_FI_DEV_RETIRED_DBE, counter, Total number of retired pages due to double-bit errors. DCGM_FI_DEV_RETIRED_PENDING, counter, Total number of pages pending retirement. # NVLink DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL, counter, Total number of NVLink bandwidth counters for all lanes. DCGM_FI_PROF_NVLINK_TX_BYTES, counter, The rate of data transmitted over NVLink not including protocol headers in bytes per second. DCGM_FI_PROF_NVLINK_RX_BYTES, counter, The rate of data received over NVLink not including protocol headers in bytes per second. # DCP metrics DCGM_FI_PROF_GR_ENGINE_ACTIVE, gauge, Ratio of time the graphics engine is active (in %). DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned (in %). DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM (in %). DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active (in %). DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data (in %). DCGM_FI_DEV_CLOCK_THROTTLE_REASONS, gauge, Current clock throttle reasons (bitmask of DCGM_CLOCKS_THROTTLE_REASON_*). DCGM_FI_DEV_GPU_NVLINK_ERRORS, gauge, Identifies a GPU NVLink error type returned by DCGM_FI_DEV_GPU_NVLINK_ERRORS. ## NVLink DCGM_FI_DEV_NVLINK_BANDWIDTH_L0, counter, The number of bytes of active NVLink rx or tx data including both header and payload. ## Remapped rows DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for uncorrectable errors. DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for correctable errors. DCGM_FI_DEV_ROW_REMAP_FAILURE, gauge, whether remapping of rows has failed. ## Profiling metrics DCGM_FI_PROF_PIPE_FP64_ACTIVE, gauge, Ratio of cycles the fp64 pipes are active (in %). DCGM_FI_PROF_PIPE_FP32_ACTIVE, gauge, Ratio of cycles the fp32 pipes are active (in %). DCGM_FI_PROF_PIPE_FP16_ACTIVE, gauge, Ratio of cycles the fp16 pipes are active (in %). # ECC DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, counter, Total number of single-bit volatile ECC errors. DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, counter, Total number of double-bit volatile ECC errors. EOF

O campo customMetrics substitui o conjunto de métricas padrão do DCGM Exporter por um conjunto estendido que inclui largura de banda NVLink, atividade de tensor, throughput de PCIe, erros de ECC e o controle de utilização térmico. Para workloads de inferência, essas métricas ajudam a saber se as unidades de computação da GPU são utilizadas totalmente, se a GPU fica ociosa entre as solicitações devido ao baixo tamanho dos lotes, se a transferência de dados entre a CPU e a GPU é um gargalo, se o controle de utilização térmico está causando picos de latência e quanto espaço extra de memória da GPU resta para lotes maiores.

Instale o DCGM Exporter:

helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \ --namespace monitoring \ -f /tmp/dcgm-exporter-values.yaml

As tolerations permitem que o exportador seja executado nos nós de GPU com taint provisionados na etapa 2. O serviceMonitor com o rótulo release: kube-prometheus-stack garante que o Prometheus o descubra e extraia automaticamente.

Verifique o DaemonSet do DCGM Exporter:

kubectl get daemonset dcgm-exporter -n monitoring

Quando um nó de GPU entra em execução, você deve ver um pod pronto. 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

Crie um bucket do Amazon S3 para armazenar os pesos do modelo e configure uma associação de Identidade de Pods do EKS para que os pods de workload possam ler e gravar nele.

Se você abriu um novo terminal, defina o nome e a região do cluster:

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

Criar um bucket do S3

Crie o bucket com um sufixo aleatório para evitar colisões de nomes:

BUCKET_SUFFIX=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') MODEL_BUCKET="${CLUSTER_NAME}-models-${BUCKET_SUFFIX}" aws s3 mb s3://${MODEL_BUCKET} --region ${AWS_REGION}

Os buckets do S3 criados após janeiro de 2023 têm criptografia do lado do servidor (AES256) e bloqueio de acesso público habilitados por padrão.

Configurar o Identidade de Pods do EKS para acessar o S3

Crie uma ServiceAccount model-storage-sa no namespace default, uma política do IAM tendo como escopo o bucket de modelo e uma associação do Identidade de Pods do EKS que os vincule. Os pods de workload que definirem serviceAccountName: model-storage-sa poderão ler e gravar no bucket.

kubectl create serviceaccount model-storage-sa

Criar a política do IAM:

POLICY_ARN=$(aws iam create-policy \ --policy-name "${CLUSTER_NAME}-model-storage-policy" \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:ListBucket\", \"s3:DeleteObject\"], \"Resource\": [\"arn:aws:s3:::${MODEL_BUCKET}\", \"arn:aws:s3:::${MODEL_BUCKET}/*\"]}]}" \ --query 'Policy.Arn' \ --output text) echo "Policy ARN: ${POLICY_ARN}"
nota

Essa política concede os direitos de s3:DeleteObject e s3:PutObject para a etapa de validação. Para pods de inferência de produção que leem apenas os pesos do modelo, remova s3:PutObject e s3:DeleteObject para seguir a regra de privilégio mínimo.

Crie uma associação do Identidade de Pods do EKS. O eksctl cria um perfil do IAM com a política de confiança correta e a vincula à ServiceAccount:

eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace default \ --service-account-name model-storage-sa \ --role-name "${CLUSTER_NAME}-model-storage-role" \ --permission-policy-arns ${POLICY_ARN} \ --region ${AWS_REGION}

Verifique a associação:

eksctl get podidentityassociation --cluster ${CLUSTER_NAME} --region ${AWS_REGION}

A saída deve incluir a associação model-storage-sa no namespace default.

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-model-storage-role/eks-ai-eks-docs-model-s-..."
}

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

=== S3 List Test ===
2026-05-04 12:00:00         19 test.txt

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

A identidade do chamador confirma que o pod assumiu o perfil ${CLUSTER_NAME}-model-storage-role por meio do 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.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2
kubectl delete pod nvidia-smi --ignore-not-found kubectl delete deployment gpu-overflow-test --ignore-not-found

Se você criou uma ODCR, cancele-a primeiro:

INSTANCE_TYPE="g6e.4xlarge" CAPACITY_RESERVATION_ID=$(aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=instance-type,Values=${INSTANCE_TYPE}" \ --query 'CapacityReservations[0].CapacityReservationId' \ --output text \ --region ${AWS_REGION}) aws ec2 cancel-capacity-reservation --capacity-reservation-id ${CAPACITY_RESERVATION_ID}
Importante

O cancelamento de uma reserva não encerra as instâncias em execução. Elas continuam aos preços padrão das instâncias sob demanda até serem encerradas. Exclua primeiro a implantação para drenar o nó reservado antes de cancelar.

Procure o ARN da política do IAM:

AMP_POLICY_ARN=$(aws iam list-policies \ --scope Local \ --query "Policies[?PolicyName=='${CLUSTER_NAME}-amp-grafana-policy'].Arn" \ --output text) echo "AMP Policy ARN: ${AMP_POLICY_ARN}"

Procure o ID do espaço de trabalho do AMP:

AMP_WORKSPACE_ID=$(aws amp list-workspaces \ --alias "amp-ws-${CLUSTER_NAME}" \ --query 'workspaces[0].workspaceId' \ --output text \ --region ${AWS_REGION}) echo "AMP Workspace ID: ${AMP_WORKSPACE_ID}"

Desinstale a versão do Helm do DCGM Exporter:

helm uninstall dcgm-exporter -n monitoring

Desinstale a versão do Helm do kube-prometheus-stack:

helm uninstall kube-prometheus-stack -n monitoring

Exclua a associação do Identidade de Pods do EKS para a conta do serviço de entrada do Prometheus:

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name amp-iamproxy-ingest-service-account \ --region ${AWS_REGION}

Exclua a associação do Identidade de Pods do EKS para a conta do serviço do Grafana:

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name grafana-sa \ --region ${AWS_REGION}

Exclua a política do IAM usada pelo Prometheus e pelo Grafana:

aws iam delete-policy --policy-arn ${AMP_POLICY_ARN}

Exclua o espaço de trabalho do AMP:

aws amp delete-workspace --workspace-id ${AMP_WORKSPACE_ID} --region ${AWS_REGION}

Exclua o namespace do monitoramento:

kubectl delete namespace monitoring

Procure o nome do bucket do modelo:

MODEL_BUCKET=$(aws s3api list-buckets \ --query "Buckets[?starts_with(Name, '${CLUSTER_NAME}-models-')].Name | [0]" \ --output text) echo "Model bucket: ${MODEL_BUCKET}"

Procure o ARN da política do IAM:

POLICY_ARN=$(aws iam list-policies \ --scope Local \ --query "Policies[?PolicyName=='${CLUSTER_NAME}-model-storage-policy'].Arn" \ --output text) echo "Policy ARN: ${POLICY_ARN}"

Exclua o bucket do modelo S3 e todos os seus objetos:

aws s3 rb s3://${MODEL_BUCKET} --force

Para excluir a associação do Identidade de Pods do EKS:

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace default \ --service-account-name model-storage-sa \ --region ${AWS_REGION}

Exclua a política do IAM:

aws iam delete-policy --policy-arn ${POLICY_ARN}

Exclua a ServiceAccount do Kubernetes:

kubectl delete serviceaccount model-storage-sa
kubectl delete nodepool gpu-inf --ignore-not-found kubectl delete nodeclass gpu-inf --ignore-not-found kubectl delete ec2nodeclass gpu-inf --ignore-not-found eksctl delete cluster --name=$CLUSTER_NAME --region=$AWS_REGION