

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

# Guia de configuração de cluster do melhores práticas para inferência em tempo real no Amazon EKS
<a name="ml-realtime-inference-cluster"></a>

**dica**  
 [Registre-se](https://aws-experience.com/emea/smb/events/series/get-hands-on-with-amazon-eks?trk=4a9b4147-2490-4c63-bc9f-f8a84b122c8c&sc_channel=el) para os próximos workshops do Amazon EKS AI/ML.

## Introdução
<a name="_introduction"></a>

Este guia oferece um passo a passo prático para configurar um cluster do Amazon Elastic Kubernetes Service (EKS) otimizado para workloads de inferência online em tempo real, incorporando as melhores práticas organizadas por especialistas da AWS em todo o processo. Ele usa uma arquitetura opinativa de início rápido do EKS: um conjunto selecionado de drivers, tipos de instância e configurações alinhados às melhores práticas da AWS para modelos, aceleradores e escalabilidade. Essa abordagem ajuda você a ignorar a tarefa de selecionar as configurações do cluster, permitindo que você coloque um cluster funcional e pré-configurado em funcionamento rapidamente. Ao longo do caminho, implantaremos amostras de workloads para validar sua configuração, explicar os principais conceitos arquitetônicos (como desacoplar tarefas vinculadas à CPU de cálculos com uso intensivo de GPU) e abordar questões comuns (por exemplo, por que escolher o Bottlerocket AMI em vez do AL2023?) e descrever as próximas etapas para ampliar os recursos do seu cluster.

Projetado especificamente para engenheiros de Machine Learning (ML) e Inteligência Artificial (IA), administradores de plataformas, operadores e especialistas em dados/IA que são novos no ecossistema do EKS e da AWS, este guia pressupõe familiaridade com o Kubernetes, mas nenhuma experiência anterior com o EKS. Ele foi projetado para ajudar você a entender as etapas e os processos necessários para colocar as workloads de inferência online em tempo real em funcionamento. O guia mostra os fundamentos da criação de um cluster do inferência de nó único, incluindo o provisionamento de recursos de GPU, a integração do armazenamento para artefatos de modelo, a habilitação do acesso seguro aos serviços da AWS e a exposição de endpoints de inferência. Ele enfatiza o design resiliente e de baixa latência para aplicações voltadas para o usuário, como detecção de fraudes, chatbots em tempo real e análise de sentimentos em sistemas de feedback de clientes.

Neste guia, nos concentramos exclusivamente na configuração de um ponto de partida básico e prescritivo usando instâncias G5 do EC2. Se você está procurando configurações de cluster específicas do AWS Inferentia ou fluxos de trabalho de ponta a ponta, consulte [Usar instâncias do AWS Inferentia com o Amazon EKS para machine learning](inferentia-support.md) ou nossos workshops em [Recursos para começar a usar IA/ML no Amazon EKS](ml-resources.md).

## Antes de começar
<a name="_before_you_begin"></a>

Antes de começar, certifique-se de ter realizado as seguintes tarefas:
+  [Configuração do seu ambiente para o Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/setting-up.html) 
+  [Instalação da versão mais recente do eksctl](https://eksctl.io/installation/) 
+  [Instale o Helm](https://helm.sh/docs/intro/install/) 
+  [(Opcional) Instalação do Docker](https://docs.docker.com/get-started/) 
+  [(Opcional) Instalação da CLI do NGC](https://org.ngc.nvidia.com/setup/installers/cli) 

## Arquitetura
<a name="_architecture"></a>

A inferência online em tempo real se refere ao processo de usar um modelo de machine learning treinado para gerar previsões ou resultados em fluxos de dados recebidos com latência mínima. Por exemplo, ele permite a detecção de fraudes em tempo real, a classificação de imagens ou a geração de gráficos de conhecimento em resposta às entradas do usuário. A arquitetura de um sistema de inferência online em tempo real fornece previsões de machine learning de baixa latência em aplicações voltadas para o usuário ao desacoplar o tratamento do tráfego da Web vinculado à CPU dos cálculos de IA com uso intensivo de GPU. Esse processo normalmente reside em um ecossistema de aplicações maior e geralmente inclui serviços de backend, frontend, vetor e modelos, com foco em componentes especializados para permitir escalabilidade independente, desenvolvimento paralelo e resiliência contra falhas. O isolamento de tarefas de inferência em hardware de GPU dedicado e o aproveitamento de interfaces como APIs e WebSockets garante alta simultaneidade, processamento rápido de modelos como transformadores e interações com o usuário por meio do frontend. Observe que, embora os bancos de dados de vetores e os pipelines de geração aumentada via recuperação (RAG) geralmente desempenhem um papel importante nos sistemas de inferência em tempo real, esses componentes não são abordados neste guia. No mínimo, uma arquitetura típica geralmente inclui:
+  **Serviço de frontend**: serve como interface voltada para o usuário, manipulando a lógica do lado do cliente, renderizando conteúdo dinâmico e facilitando interações em tempo real. Ele se comunica com o serviço de backend para iniciar solicitações de inferência e exibir resultados, geralmente iniciando solicitações para o serviço de backend que usa WebSockets para atualizações de streaming ou APIs para troca estruturada de dados. Esse serviço normalmente não requer um balanceador de carga dedicado, pois pode ser hospedado em redes de entrega de conteúdo (CDN) como o AWS CloudFront para ativos estáticos ou servido diretamente de servidores Web, com escalabilidade gerenciada por meio de grupos de ajuste de escala automático, se necessário, para conteúdo dinâmico.
+  **Serviço de backend**: atua como orquestrador da aplicação, gerenciando a lógica de negócios, como autenticação de usuários, validação de dados e coordenação de serviços (por exemplo, por meio de APIs para endpoints RESTful ou WebSockets para conexões persistentes). Ele se comunica com o serviço de inferência, escala de forma independente em CPUs e RAM de vários núcleos para lidar com o alto tráfego da Web sem depender de GPUs e geralmente requer um balanceador de carga (como o AWS Application Load Balancer ou Network Load Balancer) para distribuir as solicitações recebidas em várias instâncias, especialmente em cenários de alta simultaneidade. Um controlador de entrada pode gerenciar ainda mais o acesso externo e as regras de roteamento para melhorar a segurança e a modelagem do tráfego.
+  **Serviço de inferência**: serve como núcleo para cálculos de IA, sendo executado em GPUs com VRAM suficiente (por exemplo, 8-12 GB para modelos como DistilBERT) para realizar incorporações de vetor, extração de conhecimento e inferência do modelo (por exemplo, expostos por meio de APIs para solicitações em lote ou WebSockets para streaming em tempo real) usando modelos personalizados ou de código aberto. Esse isolamento evita conflitos de dependência, permite atualizações do modelo sem tempo de inatividade e possibilita a escalabilidade horizontal com balanceamento de carga para várias solicitações simultâneas. Para expor o serviço de modelo de forma eficaz, ele normalmente fica atrás de um balanceador de carga para distribuir workloads vinculadas à GPU entre instâncias replicadas, enquanto um recurso ou controlador de entrada (como o ALB Ingress Controller na AWS) lida com roteamento externo, terminação de SSL e encaminhamento baseado em caminho para garantir acesso seguro e eficiente sem sobrecarregar GPUs individuais.

## Visão geral da solução
<a name="_solution_overview"></a>

Os sistemas de inferência online em tempo real exigem uma arquitetura resiliente e de alta performance que possa oferecer latência ultrabaixa e, ao mesmo tempo, lidar com picos de tráfego imprevisíveis e de alto volume. Essa visão geral da solução explica como os componentes da AWS a seguir funcionam juntos no cluster do Amazon EKS que criaremos para garantir que nosso cluster seja capaz de hospedar e gerenciar modelos de machine learning que forneçam previsões imediatas sobre dados ativos com o mínimo de atraso para os usuários finais.
+  [Instâncias Amazon G5 do EC2](https://aws.amazon.com/ec2/instance-types/g5/): para tarefas de inferência com uso intensivo de GPU, estamos usando os tipos de instância G5.xlarge e g5.2xlarge G5 do EC2, que apresentam uma única (1) GPU NVIDIA A10G com 24 GB de memória (por exemplo, 8 bilhões de parâmetros no FP16). Com base na arquitetura NVIDIA Ampere, essas GPUs são equipadas com GPUs NVIDIA A10G Tensor Core e processadores AMD EPYC de 2ª geração, suportam de 4 a 8 vCPUs, largura de banda da rede de até 10 Gbps e 250 a 450 GB de armazenamento SSD NVMe local, garantindo rápida movimentação de dados e potência computacional para modelos complexos, tornando-as ideais para tarefas de inferência de baixa latência e alto throughput. A escolha de um tipo de instância do EC2 é específica da aplicação, depende do seu modelo (por exemplo, imagem, vídeo, modelo de texto) e dos requisitos de latência e throughput. Por exemplo, se estiver usando um modelo de imagem e/ou vídeo, talvez você queira usar [instâncias P5 do EC2](https://aws.amazon.com/ec2/instance-types/p5/) para obter uma latência ideal em tempo real. Recomendamos começar com as [instâncias G5 do EC2](https://aws.amazon.com/ec2/instance-types/g5/), pois elas fornecem um bom ponto de partida para começar a trabalhar rapidamente e, em seguida, avaliar se elas são adequadas para suas workloads por meio de testes de benchmark de performance. Para casos mais avançados, considere as [instâncias G6 do EC2](https://aws.amazon.com/ec2/instance-types/g6/).
+  [Instâncias M7g do Amazon EC2](https://aws.amazon.com/ec2/instance-types/m7g/): para tarefas intensivas de CPU, como pré-processamento de dados, tratamento de solicitações de API, hospedagem do controlador Karpenter, complementos e outros componentes do sistema, estamos usando o tipo de instância m5.xlarge M7g do EC2. As instâncias M7g são baseadas em ARM que apresentam 4 vCPUs, 16 GB de memória, largura de banda da rede de até 12,5 Gbps e são alimentadas por processadores AWS Graviton3. A escolha de um tipo de instância do EC2 é específica da aplicação e depende dos requisitos de computação, memória e escalabilidade da sua workload. Para workloads otimizadas para computação, é possível considerar as [instâncias C7g do EC2](https://aws.amazon.com/ec2/instance-types/c7g/), que também usam processadores Graviton3, mas são otimizadas para maior performance computacional do que as instâncias M7g para determinados casos de uso. Como alternativa, as [instâncias C8g do EC2](https://aws.amazon.com/ec2/instance-types/c8g/) mais novas (quando disponíveis) oferecem performance computacional até 30% melhor do que as instâncias C7g. Recomendamos começar com as instâncias M7g do EC2 por sua eficiência de custos e compatibilidade com uma ampla variedade de workloads (por exemplo, servidores de aplicações, microsserviços, servidores de jogos, armazenamentos de dados de médio porte) e, em seguida, avaliar se é a opção certa para suas workloads por meio de testes de benchmark de performance.
+  [Driver CSI Mountpoint do Amazon S3](https://docs.aws.amazon.com/eks/latest/userguide/s3-csi.html): para workloads em instâncias de GPU única em que vários pods compartilham uma GPU (por exemplo, vários pods programados no mesmo nó para utilizar seus recursos de GPU), estamos usando o driver CSI Mountpoint do S3 para otimizar o uso da memória, essencial para tarefas como inferência de modelos grandes em configurações econômicas e de baixa complexidade. Ele expõe os buckets do Amazon S3 como um sistema de arquivos semelhante ao POSIX disponível para o cluster do Kubernetes, o que permite que os pods de inferência leiam artefatos do modelo (por exemplo, pesos do modelo) diretamente na memória sem precisar baixá-los primeiro e inserir conjuntos de dados usando operações de arquivo padrão. Além disso, o S3 tem capacidade de armazenamento praticamente ilimitada e acelera as workloads de inferência com uso intenso de dados. A escolha de um driver CSI de armazenamento é específica da aplicação e depende dos requisitos de throughput e latência da workload. Embora o [driver CSI FSx para OpenZFS](https://docs.aws.amazon.com/eks/latest/userguide/fsx-openzfs-csi.html) ofereça latência inferior a um milissegundo para E/S aleatória ou volumes persistentes compartilhados totalmente compatíveis com POSIX entre os nós, recomendamos começar com o driver CSI Mountpoint S3 devido à sua escalabilidade, custos mais baixos para grandes conjuntos de dados e integração integrada com armazenamento de objetos gerenciado pelo S3 para padrões de inferência com muita leitura (por exemplo, entradas do modelo de streaming) e, em seguida, avaliar se é o correto adequado às suas workloads por meio de testes de benchmark de performance.
+  [Agente de Identidade de Pods do EKS](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html): para permitir o acesso aos serviços da AWS, estamos usando o Agente de Identidade de Pods do EKS, que usa uma única entidade principal de serviço e facilita associações de perfis do IAM em nível de pod no cluster do Amazon EKS. A Identidade de Pods do EKS oferece uma alternativa simplificada à abordagem tradicional de [perfis do IAM para contas de serviço (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html), utilizando uma única entidade principal de serviço (pods.eks.amazonaws.com) em vez de depender de provedores OIDC individuais para cada cluster, o que facilita a atribuição de permissões. Além disso, ele permite que os perfis sejam reutilizados em vários clusters e oferece suporte a recursos avançados, como [tags de sessão de perfil do IAM](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-abac.html) e [perfis do IAM de destino](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-assign-target-role.html).
+  [Agente de monitoramento de nós do EKS](https://docs.aws.amazon.com/eks/latest/userguide/node-health.html): para garantir a disponibilidade e a confiabilidade contínuas dos serviços de inferência, estamos usando o agente de monitoramento de nós do EKS com reparo automático, que detecta e substitui automaticamente os nós não saudáveis, minimizando o tempo de inatividade. Ele monitora continuamente os nós em busca de problemas de hardware, kernel, rede e armazenamento usando verificações de integridade aprimoradas (por exemplo, KernelReady, NetworkingReady). Para nós de GPU, ele detecta falhas específicas do acelerador, iniciando uma correção normal com o isolamento de nós não íntegros, aguardando 10 minutos para que problemas transitórios de GPU sejam resolvidos e substituindo os nós após 30 minutos por falhas persistentes.
+  [AMI do Bottlerocket](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami-bottlerocket.html): para fornecer uma base segura para nosso cluster de EKS, estamos usando a AMI do Bottlerocket, que inclui apenas os componentes essenciais necessários para executar contêineres e oferece tempos mínimos de inicialização para escalabilidade rápida. A escolha de uma AMI de nó é específica da aplicação e depende dos requisitos de personalização, segurança e escalabilidade de sua workload. Embora a [AMI AL2023](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html) ofereça maior flexibilidade para instalações e personalizações em nível de host (por exemplo, especificar um diretório de cache dedicado em um PV/PVC sem nenhuma configuração de nó adicional), recomendamos começar com a AMI do Bottlerocket por seu tamanho menor e otimização integrada para workloads em contêineres (por exemplo, microsserviços, servidores de inferência, APIs escaláveis) e, em seguida, avaliar se ela é a opção certa para suas workloads por meio de testes de benchmark de performance.
+  [AWS Load Balancer Controller (LBC)](https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html): para expor endpoints de inferência em tempo real, estamos usando o AWS Load Balancer Controller, que provisiona e gerencia automaticamente Application Load Balancers (ALBs) para tráfego HTTP/HTTPS e Network Load Balancers (NLBs) para tráfego TCP/UDP com base nos recursos de entrada e serviço do Kubernetes, permitindo a integração de modelos de inferência com clientes externos. Além disso, ele oferece suporte a recursos como roteamento baseado em caminhos para distribuir solicitações de inferência em vários pods ou nós, garantindo escalabilidade durante picos de tráfego e minimizando a latência por meio de otimizações nativas da AWS, como multiplexação de conexão e verificações de integridade.

## 1. Criação do seu cluster de EKS
<a name="_1_create_your_eks_cluster"></a>

Nesta etapa, criamos um cluster com nós de CPU e um grupo de nós gerenciados usando um modelo eksctl [ClusterConfig](https://eksctl.io/usage/creating-and-managing-clusters/) baseado no AWS CloudFormation. Inicializar o cluster apenas com nós de CPU nos permite usar o Karpenter exclusivamente para gerenciar nós de GPU e com uso intensivo de CPU para alocação otimizada de recursos usando Karpenter NodePools, que criaremos em etapas posteriores. Para oferecer suporte às nossas workloads de inferência em tempo real, provisionamos o cluster com a AMI do Bottlerocket do EKS, o agente de monitoramento de nós do EKS, o agente de Identidade de Pods do EKS, o driver CSI Mountpoint S3, o AWS Load Balancer Controller (LBC) e os drivers [kube-proxy](https://docs.aws.amazon.com/eks/latest/userguide/managing-kube-proxy.html), [vpc-cni](https://docs.aws.amazon.com/eks/latest/userguide/managing-vpc-cni.html) e [coredns](https://docs.aws.amazon.com/eks/latest/userguide/managing-coredns.html). As instâncias m7g.xlarge serão usadas para tarefas do sistema de CPU, incluindo hospedagem do controlador Karpenter, complementos e outros componentes do sistema.

Por padrão, o `eksctl` criará uma VPC dedicada para o cluster com um bloco CIDR `192.168.0.0/16`. A VPC inclui três sub-redes públicas e três sub-redes privadas, cada uma distribuída em três zonas de disponibilidade diferentes (ou duas AZs na região `us-east-1`), o que é o método ideal para implantar workloads do Kubernetes. O modelo também implanta um gateway da Internet, fornecendo acesso à Internet às sub-redes públicas por meio de rotas padrão em suas tabelas de rotas e um único gateway de NAT em uma das sub-redes públicas, com rotas padrão nas tabelas de rotas das sub-redes privadas direcionando o tráfego de saída por meio do gateway de NAT para acesso à Internet. Para saber mais sobre essa configuração, consulte [Implantação de nós em sub-redes privadas](https://docs.aws.amazon.com/eks/latest/best-practices/subnets.html#_deploy_nodes_to_private_subnets).

### Verificação das suas credenciais
<a name="_check_your_credentials"></a>

Verifique se suas credenciais da AWS CLI são válidas e podem ser autenticadas com serviços da AWS:

```
aws sts get-caller-identity
```

Se tiver êxito, a CLI retornará detalhes sobre sua identidade da AWS (ID do usuário, conta e ARN).

### Verificação de disponibilidade de instâncias
<a name="_check_instance_availability"></a>

Os tipos de instâncias G5 não estão disponíveis em todas as regiões. Verifique sua região mais próxima. Por exemplo:

```
aws ec2 describe-instance-types --instance-types g5.xlarge g5.2xlarge --region us-east-1
```

Se tiver êxito, o tipo de instância G5 estará disponível na região que você especificou.

A AMI do Bottlerocket não está disponível em todas as regiões. Verifique recuperando um ID de AMI do Bottlerocket para sua região mais próxima. Por exemplo:

```
aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-1.33/arm64/latest/image_id \
    --region us-east-1 --query "Parameter.Value" --output text
```

Se tiver êxito, a AMI do Bottlerocket estará disponível na região que você especificou.

### Preparação do seu ambiente
<a name="_prepare_your_environment"></a>

Primeiro, defina as seguintes variáveis de ambiente em uma nova janela do terminal. **Observação**: certifique-se de substituir os espaços reservados do exemplo por seus valores exclusivos, incluindo o nome do cluster, a região desejada, a [versão de lançamento do Karpenter](https://github.com/kubernetes-sigs/karpenter/releases) e a [versão do Kubernetes](https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html).

**dica**  
Algumas variáveis (como `${AWS_REGION}` e `${K8S_VERSION}`) são definidas no início do bloco e depois referenciadas em comandos posteriores para fins de consistência e para evitar repetições. Certifique-se de executar os comandos em sequência para que esses valores sejam exportados adequadamente e estejam disponíveis para uso em definições subsequentes.

```
export TEMPOUT="$(mktemp)"
export K8S_VERSION=1.33
export KARPENTER_VERSION="1.5.0"
export AWS_REGION="us-east-1"
export EKS_CLUSTER_NAME="eks-rt-inference-${AWS_REGION}"
export S3_BUCKET_NAME="eks-rt-inference-models-${AWS_REGION}-$(date +%s)"
export NVIDIA_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}-nvidia/x86_64/latest/image_id --query Parameter.Value --output text)"
export STANDARD_BOTTLEROCKET_AMI="$(aws ssm get-parameter --name /aws/service/bottlerocket/aws-k8s-${K8S_VERSION}/arm64/latest/image_id --query Parameter.Value --output text)"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export ALIAS_VERSION="$(aws ssm get-parameter --name "/aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2023/x86_64/standard/recommended/image_id" --query Parameter.Value | xargs aws ec2 describe-images --query 'Images[0].Name' --image-ids | sed -r 's/^.*(v[[:digit:]]+).*$/\1/')"
```

### Criação dos perfis e das políticas necessárias
<a name="_create_required_roles_and_policies"></a>

O Karpenter precisa de políticas e perfis do IAM específicos (por exemplo, perfil do IAM do controlador do Karpenter, perfil de instância e políticas) para gerenciar instâncias do EC2 como nós de processamento do Kubernetes. Ele usa esses perfis para realizar ações como iniciar e encerrar instâncias do EC2, marcar recursos e interagir com outros serviços da AWS. Crie os perfis e as políticas do Karpenter usando o [cloudformation.yaml](https://raw.githubusercontent.com/aws/karpenter-provider-aws/v1.5.0/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml) do Karpenter:

```
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v${KARPENTER_VERSION}/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml > "${TEMPOUT}" \
&& aws cloudformation deploy \
  --stack-name "Karpenter-${EKS_CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${EKS_CLUSTER_NAME}"
```

O AWS LBC precisa de permissão para provisionar e gerenciar balanceadores de carga da AWS, como criar ALBs para recursos do Ingress ou NLBs para serviços do tipo `LoadBalancer`. Especificaremos essa política de permissões durante a criação do cluster. Durante a criação do cluster, criaremos a conta de serviço com o eksctl no ClusterConfig. Criação da política do IAM da LBC:

```
aws iam create-policy \
  --policy-name AWSLoadBalancerControllerIAMPolicy \
  --policy-document "$(curl -fsSL https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.14.1/docs/install/iam_policy.json)"
```

Quando o driver CSI Mountpoint S3 é instalado, seus pods DaemonSet são configurados para usar uma conta de serviço para execução. O driver CSI Mountpoint para Mountpoint S3 precisa de permissão para interagir com o bucket do Amazon S3 que você criará posteriormente neste guia. Especificaremos essa política de permissões durante a criação do cluster. Durante a criação do cluster, criaremos a conta de serviço com o eksctl no ClusterConfig. Criação da política do IAM do S3:

```
aws iam create-policy \
    --policy-name S3CSIDriverPolicy \
    --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:ListBucket\"], \"Resource\": [\"arn:aws:s3:::${S3_BUCKET_NAME}\", \"arn:aws:s3:::${S3_BUCKET_NAME}/*\"]}]}"
```

 **Observação**: se já existir um perfil com esse nome, dê ao perfil um nome diferente. O perfil que criamos nesta etapa é específico para seu cluster e seu bucket do S3.

### Criar um cluster
<a name="_create_the_cluster"></a>

Neste modelo, o eksctl cria automaticamente uma conta de serviço do Kubernetes para a Identidade de Pods do EKS, o Agente de monitoramento de nós, o CoreDNS, o Kubeproxy e o plug-in CNI da VPC. Atualmente, o driver CSI Mountpoint S3 não está disponível para a Identidade de Pods do EKS, então criamos Perfis do IAM para contas de serviço (IRSA) e um [endpoint do OIDC](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html). Além disso, criamos uma conta de serviço para o AWS Load Balancer Controller (LBC). Para acessar os nós do Bottlerocket, o eksctl anexa automaticamente o AmazonSSMManagedInstanceCore para Bottlerocket para permitir sessões de shell seguras via SSM.

No mesmo terminal em que você define suas variáveis de ambiente, execute o bloco de comando a seguir para criar o cluster:

```
eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ${EKS_CLUSTER_NAME}
  region: ${AWS_REGION}
  version: "${K8S_VERSION}"
  tags:
    karpenter.sh/discovery: ${EKS_CLUSTER_NAME}
    # Add more tags if needed for billing
iam:
  # Creates an OIDC endpoint and IRSA service account for the Mountpoint S3 CSI Driver
  # Uses the S3 CSI Driver policy for permissions
  withOIDC: true
  podIdentityAssociations:
  # Creates the pod identity association and service account
  # Uses the Karpenter controller IAM policy for permissions
  - namespace: "kube-system"
    serviceAccountName: karpenter
    roleName: ${EKS_CLUSTER_NAME}-karpenter
    permissionPolicyARNs:
    - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${EKS_CLUSTER_NAME}
  # Creates the pod identity association and service account
  # Uses the {aws} LBC policy for permissions
  - namespace: kube-system
    serviceAccountName: aws-load-balancer-controller
    createServiceAccount: true
    roleName: AmazonEKSLoadBalancerControllerRole
    permissionPolicyARNs:
    - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy
iamIdentityMappings:
- arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${EKS_CLUSTER_NAME}"
  username: system:node:{{EC2PrivateDNSName}}
  groups:
  - system:bootstrappers
  - system:nodes
managedNodeGroups:
  # Creates 2 CPU nodes for lightweight system tasks
  - name: ${EKS_CLUSTER_NAME}-m7-cpu
    instanceType: m7g.xlarge
    amiFamily: Bottlerocket
    desiredCapacity: 2
    minSize: 1
    maxSize: 10
    labels:
      role: cpu-worker
# Enable automatic Pod Identity associations for VPC CNI Driver, coreDNS, kube-proxy
addonsConfig:
  autoApplyPodIdentityAssociations: true
addons:
  # Installs the S3 CSI Driver addon and creates IAM role
  # Uses the S3 CSI Driver policy for IRSA permissions
  - name: aws-mountpoint-s3-csi-driver
    attachPolicyARNs:
      - "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/S3CSIDriverPolicy"
  - name: eks-pod-identity-agent
  - name: eks-node-monitoring-agent
  - name: coredns
  - name: kube-proxy
  - name: vpc-cni
EOF
```

Esse processo leva vários minutos para ser concluído. Se quiser monitorar o status, consulte o console do [AWS CloudFormation](https://console.aws.amazon.com/cloudformation).

## 2. Verificação da integridade do nó do cluster e do pod
<a name="_2_verify_cluster_node_and_pod_health"></a>

Vamos realizar algumas verificações de integridade para garantir que o cluster esteja pronto. Quando o comando anterior for concluído, veja os tipos de instâncias e verifique se os nós de sistema da CPU atingiram o estado `Ready` com o comando a seguir:

```
kubectl get nodes -L node.kubernetes.io/instance-type
```

A saída esperada deve ser semelhante a essa:

```
NAME                             STATUS   ROLES    AGE     VERSION               INSTANCE-TYPE
ip-192-168-35-103.ec2.internal   Ready    <none>   12m     v1.33.0-eks-802817d   m7g.xlarge
ip-192-168-7-15.ec2.internal     Ready    <none>   12m     v1.33.0-eks-802817d   m7g.xlarge
```

Verifique todas as associações de Identidade de Pods e como elas mapeiam um perfil para uma conta de serviço em um namespace no cluster com o comando a seguir:

```
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
```

A saída deve mostrar os perfis do IAM para Karpenter ("karpenter") e o AWS LBC ("aws-load-balancer-controller").

Verifique se os DaemonSets estão disponíveis:

```
kubectl get daemonsets -n kube-system
```

A saída esperada deve ser semelhante a essa:

```
NAME                           DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR          AGE
aws-node                       3       3       3     3          3         <none>                 12m
dcgm-server                    0       0       0     0          0         kubernetes.io/os=linux 12m
eks-node-monitoring-agent      3       3       3     3          3         kubernetes.io/os=linux 12m
eks-pod-identity-agent         3       3       3     3          3         <none>                 12m
kube-proxy                     3       3       3     3          3         <none>                 12m
s3-csi-node                    2       2       2     2          2         kubernetes.io/os=linux 12m
```

Verifique se todos os complementos estão instalados no cluster:

```
eksctl get addons --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
```

A saída esperada deve ser semelhante a essa:

```
NAME                           VERSION              STATUS    ISSUES    IAMROLE                                           UPDATE AVAILABLE    CONFIGURATION VALUES    POD IDENTITY ASSOCIATION ROLES
aws-mountpoint-s3-csi-driver   v1.15.0-eksbuild.1   ACTIVE    0    arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-RAUjk4sJnc0L
coredns                        v1.12.1-eksbuild.2   ACTIVE    0
eks-node-monitoring-agent      v1.3.0-eksbuild.2    ACTIVE    0
eks-pod-identity-agent         v1.3.7-eksbuild.2    ACTIVE    0
kube-proxy                     v1.33.0-eksbuild.2   ACTIVE    0
metrics-server                 v0.7.2-eksbuild.3    ACTIVE    0
vpc-cni                        v1.19.5-eksbuild.1   ACTIVE    0
```

## 3. Instalação do Karpenter
<a name="_3_install_karpenter"></a>

Instale o controlador Karpenter nos nós de processamento da CPU (`cpu-worker`) para otimizar custos e conservar os recursos da GPU. Vamos instalá-lo no namespace "kube-system" e especificar a conta de serviço "karpenter" que definimos durante a criação do cluster. Além disso, esse comando configura o nome do cluster e uma fila de interrupção de instância spot para os nós da CPU. O Karpenter usará IRSA para assumir esse perfil do IAM.

```
# Logout of helm registry before pulling from public ECR
helm registry logout public.ecr.aws

# Install Karpenter
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "kube-system" --create-namespace  \
  --set "settings.clusterName=${EKS_CLUSTER_NAME}" \
  --set "settings.interruptionQueue=${EKS_CLUSTER_NAME}" \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${EKS_CLUSTER_NAME}-karpenter" \
  --wait
```

A saída esperada deve ser semelhante a essa:

```
Release "karpenter" does not exist. Installing it now.
Pulled: public.ecr.aws/karpenter/karpenter:1.5.0
Digest: sha256:9a155c7831fbff070669e58500f68d7ccdcf3f7c808dcb4c21d3885aa20c0a1c
NAME: karpenter
LAST DEPLOYED: Thu Jun 19 09:57:06 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
```

Verifique se o Karpenter está em execução:

```
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
```

A saída esperada deve ser semelhante a essa:

```
NAME                       READY   STATUS    RESTARTS   AGE
karpenter-555895dc-865bc   1/1     Running   0          5m58s
karpenter-555895dc-j7tk9   1/1     Running   0          5m58s
```

## 4. Configuração dos NodePools do Karpenter
<a name="_4_setup_karpenter_nodepools"></a>

Nesta etapa, configuraremos os [NodePools do Karpenter](https://karpenter.sh/docs/concepts/nodepools/) de CPU e GPU mutuamente exclusivos. O campo `limits` na especificação do NodePool restringe o total máximo de recursos (por exemplo, CPU, memória, GPUs) que cada NodePool pode consumir em todos os nós provisionados, impedindo o provisionamento adicional de nós se esses limites forem excedidos. Embora o NodePools ofereça suporte a categorias amplas de instâncias (por exemplo, `c`, `g`), a especificação de [tipos de instância](https://karpenter.sh/docs/concepts/nodepools/#instance-types), [tipos de funcionalidade](https://karpenter.sh/docs/concepts/nodepools/#capacity-type) e [limites](https://karpenter.sh/docs/concepts/nodepools/#speclimits) de recursos específicos ajuda você a estimar com mais facilidade os custos de suas workloads sob demanda. Nesses NodePools, usamos um conjunto diversificado de tipos de instância dentro da família de instâncias G5. Isso permite que o Karpenter selecione automaticamente o tipo de instância mais apropriado com base nas solicitações de recursos do pod, otimizando a utilização dos recursos e respeitando os limites totais do NodePool. Para saber mais, consulte [Criação de NodePools](https://docs.aws.amazon.com/eks/latest/best-practices/karpenter.html#_creating_nodepools).

### Configuração do NodePool de GPU
<a name="_setup_the_gpu_nodepool"></a>

Neste NodePool, definimos limites de recursos para gerenciar o provisionamento de nós com recursos de GPU. Esses limites foram projetados para limitar o total de recursos em todos os nós do pool, permitindo até 10 instâncias no total. Cada instância pode ser g5.xlarge (4 vCPUs, 16 GiB de memória, 1 GPU) ou g5.2xlarge (8 vCPUs, 32 GiB de memória, 1 GPU), desde que o total de vCPUs não exceda 80, a memória total não exceda 320 GiB e o total de GPUs não exceda 10. Por exemplo, o grupo pode provisionar 10 instâncias g5.2xlarge (80 vCPUs, 320 GiB, 10 GPUs) ou 10 instâncias g5.xlarge (40 vCPUs, 160 GiB, 10 GPUs) ou uma combinação, como 5 g5.xlarge e 5 g5.2xlarge (60 vCPUs, 240 GiB, 10 PUs), garantindo flexibilidade com base nas demandas de workload, respeitando as restrições de recursos.

Além disso, especificamos o ID da variante Nvidia da AMI do Bottlerocket. Por fim, definimos uma [política de interrupção](https://karpenter.sh/docs/concepts/disruption/#nodepool-disruption-budgets) para remover nós vazios após 30 minutos (`consolidateAfter: 30m`) e definimos uma vida útil máxima de nó de 30 dias (`expireAfter: 720h`) para otimizar os custos e manter a integridade dos nós para tarefas que exijam muita GPU. Para saber mais, consulte [Desabilitação da consolidação do Karpenter para workloads sensíveis à interrupção](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_disable_karpenter_consolidation_for_interruption_sensitive_workloads) e [Uso de TTLSecondAfterFinished para lipeza automática de tarefas do Kubernetes](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_use_ttlsecondsafterfinished_to_auto_clean_up_kubernetes_jobs).

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-a10g-inference-g5
spec:
  template:
    metadata:
      labels:
        role: gpu-worker
        gpu-type: nvidia-a10g
    spec:
      requirements:
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["g5.xlarge", "g5.2xlarge"]
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["on-demand"]
      taints:
        - key: nvidia.com/gpu
          value: "true"
          effect: NoSchedule
      nodeClassRef:
        name: gpu-a10g-inference-ec2
        group: karpenter.k8s.aws
        kind: EC2NodeClass
      expireAfter: 720h
  limits:
    cpu: "80"
    memory: "320Gi"
    nvidia.com/gpu: "10"
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30m
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: gpu-a10g-inference-ec2
spec:
  amiFamily: Bottlerocket
  amiSelectorTerms:
    - id: ${NVIDIA_BOTTLEROCKET_AMI}
  role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
  tags:
    nvidia.com/gpu: "true"
EOF
```

A saída esperada deve ser semelhante a essa:

```
nodepool.karpenter.sh/gpu-a10g-inference-g5 created
ec2nodeclass.karpenter.k8s.aws/gpu-a10g-inference-ec2 created
```

Verifique se o NodePool foi criado e está íntegro:

```
kubectl get nodepool gpu-a10g-inference-g5 -o yaml
```

Procure por `status.conditions` como `ValidationSucceeded: True`, `NodeClassReady: True` e `Ready: True` para confirmar se o NodePool está íntegro.

### Configuração do NodePool de CPU
<a name="_setup_the_cpu_nodepool"></a>

Neste NodePool, definimos limites para oferecer suporte a aproximadamente 50 instâncias, alinhando-os a uma workload de CPU moderada (por exemplo, 100-200 pods) e cotas típicas de vCPU da AWS (por exemplo, 128-1152). Os limites são calculados supondo que o NodePool deva aumentar a escala verticalmente até 50 instâncias m7.xlarge: CPU (4 vCPUs por instância × 50 instâncias = 200 vCPUs) e memória (16 GiB por instância × 50 instâncias = 800 GiB). Esses limites foram projetados para limitar o total de recursos em todos os nós do grupo, permitindo instâncias de até 50 m7g.xlarge (cada uma com 4 vCPUs e 16 GiB de memória), desde que o total de vCPUs não exceda 200 e a memória total não exceda 800 GiB.

Além disso, especificamos o ID da variante padrão da AMI do Bottlerocket. Por fim, definimos uma [política de interrupção](https://karpenter.sh/docs/concepts/disruption/#nodepool-disruption-budgets) para remover nós vazios após 60 minutos (`consolidateAfter: 60m`) e definimos uma vida útil máxima de nó de 30 dias (`expireAfter: 720h`) para otimizar os custos e manter a integridade dos nós para tarefas que exijam muita GPU. Para saber mais, consulte [Desabilitação da consolidação do Karpenter para workloads sensíveis à interrupção](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_disable_karpenter_consolidation_for_interruption_sensitive_workloads) e [Uso de TTLSecondAfterFinished para lipeza automática de tarefas do Kubernetes](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_use_ttlsecondsafterfinished_to_auto_clean_up_kubernetes_jobs).

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cpu-inference-m7gxlarge
spec:
  template:
    metadata:
      labels:
        role: cpu-worker
    spec:
      requirements:
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m7g.xlarge"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
      taints:
        - key: role
          value: cpu-intensive
          effect: NoSchedule
      nodeClassRef:
        name: cpu-inference-m7gxlarge-ec2
        group: karpenter.k8s.aws
        kind: EC2NodeClass
      expireAfter: 720h
  limits:
    cpu: "200"
    memory: "800Gi"
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 60m
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: cpu-inference-m7gxlarge-ec2
spec:
  amiFamily: Bottlerocket
  amiSelectorTerms:
    - id: ${STANDARD_BOTTLEROCKET_AMI}
  role: "KarpenterNodeRole-${EKS_CLUSTER_NAME}"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
EOF
```

A saída esperada deve ser semelhante a essa:

```
nodepool.karpenter.sh/cpu-inference-m7gxlarge created
ec2nodeclass.karpenter.k8s.aws/cpu-inference-m7gxlarge-ec2 created
```

Verifique se o NodePool foi criado e está íntegro:

```
kubectl get nodepool cpu-inference-m7gxlarge -o yaml
```

Procure por `status.conditions` como `ValidationSucceeded: True`, `NodeClassReady: True` e `Ready: True` para confirmar se o NodePool está íntegro.

## 5. Implantação de um pod de GPU para expor uma GPU
<a name="_5_deploy_a_gpu_pod_to_expose_a_gpu"></a>

Você precisa do plug-in para dispositivos Nvidia para permitir que o Kubernetes exponha dispositivos de GPU aos clusters Kubernetes. Normalmente, você precisaria implantar o plug-in como DaemonSet; no entanto, a AMI do Bottlerocket pré-instala o plug-in como parte da AMI. Isso significa que, ao usar as AMIs do Bottlerocket, não há necessidade de implantar o DaemonSet do plug-in para dispositivo Nvidia. Para saber mais, consulte [Plug-in para dispositivos Kubernetes para expor GPUs](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_use_kubernetes_device_plugin_for_exposing_gpus).

### Implantação de um pod de exemplo
<a name="_deploy_a_sample_pod"></a>

O Karpenter age dinamicamente: ele provisiona os nós da GPU quando uma workload (pod) solicita recursos da GPU. Para verificar se os pods são capazes de solicitar e usar GPUs, implante um pod que solicite o recurso `nvidia.com/gpu` dentro de seus limites (por exemplo, `nvidia.com/gpu: 1`). Para saber mais sobre esses rótulos, consulte [Programação de workloads com requisitos de GPU usando rótulos conhecidos](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-compute.html#_schedule_workloads_with_gpu_requirements_using_well_known_labels).

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: gpu-nvidia-smi
spec:
  restartPolicy: OnFailure
  tolerations:
  - key: "nvidia.com/gpu"
    operator: "Exists"
    effect: "NoSchedule"
  nodeSelector:
    role: gpu-worker  # Matches GPU NodePool's label
  containers:
  - name: cuda-container
    image: nvidia/cuda:12.9.1-base-ubuntu20.04
    command: ["nvidia-smi"]
    resources:
      limits:
        nvidia.com/gpu: 1
      requests:
        nvidia.com/gpu: 1
EOF
```

A saída esperada deve ser semelhante a essa:

```
pod/gpu-nvidia-smi created
```

Espere um minuto e verifique se o pod tem o status "Pendente", "CriandoContêiner", "Em execução", e depois "Concluído":

```
kubectl get pod gpu-nvidia-smi -w
```

Verifique se o nó do pod pertence ao NodePool da GPU:

```
kubectl get node $(kubectl get pod gpu-nvidia-smi -o jsonpath='{.spec.nodeName}') -o custom-columns="Name:.metadata.name,Nodepool:.metadata.labels.karpenter\.sh/nodepool"
```

A saída esperada deve ser semelhante a essa:

```
Name                             Nodepool
ip-192-168-83-245.ec2.internal   gpu-a10g-inference-g5
```

Verifique os logs do pod:

```
kubectl logs gpu-nvidia-smi
```

A saída esperada deve ser semelhante a essa:

```
Thu Jul 17 04:31:33 2025
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.148.08                 Driver Version: 570.148.08         CUDA Version: 12.9 |
|-----------------------------------------+----------------------+----------------------+
| 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 A10G                    On  | 00000000:00:1E.0 Off |                    0 |
|  0%   30C    P8               9W / 300W |      0MiB / 23028MiB |      0%      Default |
|                                         |                      |                  N/A |
+---------------------------------------------------------------------------------------+

+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU        GI     CI        PID   Type   Process name                  GPU Memory    |
|                     ID        ID                                         Usage        |
|=======================================================================================|
|  No running processes found                                                           |
+---------------------------------------------------------------------------------------+
```

## 6. (Opcional) Preparação e transferência de artefatos de modelo para implantação
<a name="_6_optional_prepare_and_upload_model_artifacts_for_deployment"></a>

Nesta etapa, você implantará um serviço de modelo para classificação de imagens em tempo real, começando com a transferência dos pesos do modelo para um bucket do Amazon S3. Para demonstração, estamos usando o modelo de visão [GPUnet-0](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/dle/models/gpunet_0_pyt_ckpt) de código aberto, parte da [GPUnet](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/dle/resources/gpunet_pyt) da NVIDIA, que suporta inferência de baixa latência em imagens usando GPUs NVIDIA e TensorRT. Esse modelo é pré-treinado no [ImageNet](https://www.image-net.org/), e permite classificar objetos em fotos ou streams de vídeo em tempo real e é considerado um modelo pequeno com 11,9 milhões de parâmetros.

### Configure o ambiente.
<a name="_set_up_your_environment"></a>

Para baixar os pesos do modelo GPUnet-0 nesta etapa, você precisará acessar o catálogo NGC da NVIDIA e ter o [Docker](https://docs.docker.com/get-started/) instalado em sua máquina local. Siga estas etapas para criar uma conta gratuita e configurar a CLI do NGC:
+  [Inscreva-se em uma conta gratuita do NGC](https://ngc.nvidia.com/signup) e gere uma chave de API no painel do NGC (Ícone do usuário > Configuração > Gerar chave de API > Gerar chave pessoal > Catálogo NGC).
+  [Baixe e instale a CLI do NGC](https://org.ngc.nvidia.com/setup/installers/cli) (Linux/macOS/Windows) e configure a CLI usando: `ngc config set`. Insira sua chave de API quando solicitada; defina org como `nvidia` e pressione Enter para aceitar padrões para outras pessoas. Se tiver êxito, será necessário ver algo parecido com: `Successfully saved NGC configuration to /Users/your-username/.ngc/config`.

### Verificação de permissões de conta de serviço
<a name="_verify_service_account_permissions"></a>

Antes de começarmos, verifique as permissões da conta de serviço do Kubernetes:

```
kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml
```

Durante a criação do cluster, anexamos a S3CSIDriverPolicy a um perfil do IAM e anotamos a conta de serviço ("s3-csi-driver-sa"). Os pods do driver CSI Mountpoint S3 herdam as permissões do perfil do IAM ao interagir com o S3. A saída esperada deve ser semelhante a essa:

```
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::143095308808:role/eksctl-eks-rt-inference-us-east-1-addon-aws-m-Role1-fpXXjRYdKN8r
  creationTimestamp: "2025-07-17T03:55:29Z"
  labels:
    app.kubernetes.io/component: csi-driver
    app.kubernetes.io/instance: aws-mountpoint-s3-csi-driver
    app.kubernetes.io/managed-by: EKS
    app.kubernetes.io/name: aws-mountpoint-s3-csi-driver
  name: s3-csi-driver-sa
  namespace: kube-system
  resourceVersion: "2278"
  uid: 50b36272-6716-4c68-bdc3-c4054df1177c
```

### Adição de uma tolerância
<a name="_add_a_toleration"></a>

O driver CSI S3 é executado como um DaemonSet em todos os nós. Os pods usam o driver CSI nesses nós para montar volumes do S3. Para permitir que ele seja programado em nossos nós de GPU que tenham taint, adicione uma tolerância ao DaemonSet:

```
kubectl patch daemonset s3-csi-node -n kube-system --type='json' -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'
```

A saída esperada deve ser semelhante a essa:

```
daemonset.apps/s3-csi-node patched
```

### Transferência dos pesos do modelo para o S3
<a name="_upload_model_weights_to_s3"></a>

Nesta etapa, você criará um bucket do Amazon S3, baixará os pesos do modelo GPUnet-0 da NVIDIA GPU Cloud (NGC) e os transferirá para o bucket. Esses pesos serão acessados por nossa aplicação no runtime para inferência.

Crie seu bucket do Amazon S3:

```
aws s3 mb s3://${S3_BUCKET_NAME} --region ${AWS_REGION}
```

Habilite o [versionamento do S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html) para o bucket, para evitar que exclusões e substituições acidentais causem perda imediata e permanente de dados:

```
aws s3api put-bucket-versioning --bucket ${S3_BUCKET_NAME} --versioning-configuration Status=Enabled
```

Aplique uma regra de ciclo de vida ao bucket para remover versões de objetos substituídas ou excluídas 14 dias depois de se tornarem inatuais, remover marcadores de exclusão expirados e remover transferências incompletas de várias partes após 7 dias. Para saber mais, consulte [Exemplos de configuração do Ciclo de Vida do S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html).

```
aws s3api put-bucket-lifecycle-configuration --bucket $S3_BUCKET_NAME --lifecycle-configuration '{"Rules":[{"ID":"LifecycleRule","Status":"Enabled","Filter":{},"Expiration":{"ExpiredObjectDeleteMarker":true},"NoncurrentVersionExpiration":{"NoncurrentDays":14},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}'
```

Baixe os pesos do modelo GPUnet-0 do NGC. Por exemplo, no macOS:

```
ngc registry model download-version nvidia/dle/gpunet_0_pyt_ckpt:21.12.0_amp --dest ~/downloads
```

**nota**  
Talvez seja necessário ajustar esse comando de download para seu sistema operacional. Para que esse comando funcione em um sistema Linux, você provavelmente precisará criar o diretório como parte do comando (por exemplo, `mkdir ~/downloads`).

A saída esperada deve ser semelhante a essa:

```
{
  "download_end": "2025-07-18 08:22:39",
  "download_start": "2025-07-18 08:22:33",
  "download_time": "6s",
  "files_downloaded": 1,
  "local_path": "/Users/your-username/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp",
  "size_downloaded": "181.85 MB",
  "status": "Completed",
  "transfer_id": "gpunet_0_pyt_ckpt[version=21.12.0_amp]"
}
```

Renomeie o arquivo do ponto de verificação para corresponder à nomenclatura esperada no código da nossa aplicação nas etapas posteriores (nenhuma extração é necessária, pois é um ponto de verificação padrão do PyTorch \*.pth.tar contendo o dicionário de estado do modelo):

```
mv ~/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp/0.65ms.pth.tar gpunet-0.pth
```

Habilite o [AWS Common Runtime](https://aws.amazon.com/blogs/storage/improving-amazon-s3-throughput-for-the-aws-cli-and-boto3-with-the-aws-common-runtime/) na CLI da AWS para otimizar o throughput do S3:

```
aws configure set s3.preferred_transfer_client crt
```

Transfira os pesos do modelo no bucket do S3:

```
aws s3 cp gpunet-0.pth s3://${S3_BUCKET_NAME}/gpunet-0.pth
```

A saída esperada deve ser semelhante a essa:

```
upload: ./gpunet-0.pth to s3://eks-rt-inference-models-us-east-1-1752722786/gpunet-0.pth
```

### Criação do serviço de modelo
<a name="_create_the_model_service"></a>

Nesta etapa, você configurará uma aplicação Web FastAPI para classificação de imagens acelerada por GPU usando o modelo de visão GPUnet-0. A aplicação baixa os pesos do modelo do Amazon S3 no runtime, busca a arquitetura do modelo no repositório da NVIDIA para armazenamento em cache e baixa os rótulos da classe ImageNet via HTTP. A aplicação inclui transformações de pré-processamento de imagens e expõe dois endpoints: um GET raiz para verificação de status e um endpoint POST `/predict` que aceita uma URL de imagem.

Servimos o modelo usando FastAPI com PyTorch, carregando pesos do Amazon S3 no runtime em uma configuração em contêineres para prototipagem rápida e implantação do Kubernetes. Para outros métodos, como lotes otimizados ou mecanismos de alto throughput, consulte [Fornecimento de modelos de ML](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-performance.html#_serving_ml_models).

#### Criar a aplicação
<a name="_create_the_application"></a>

Crie um diretório para os arquivos da sua aplicação `model-testing`, como, em seguida, altere os diretórios nele e adicione o código a seguir a um novo arquivo chamado `app.py`:

```
import os
import torch
import json
import requests
from fastapi import FastAPI, HTTPException
from PIL import Image
from io import BytesIO, StringIO
import torchvision.transforms as transforms
from torch.nn.functional import softmax
import warnings
from contextlib import redirect_stdout, redirect_stderr
import argparse
import boto3
app = FastAPI()

# Suppress specific warnings from the model code (quantization is optional and unused here)
warnings.simplefilter("ignore", UserWarning)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Load model code from cache (if present)
# Use backed cache directory
torch.hub.set_dir('/cache/torch/hub')

# Allowlist for secure deserialization (handles potential issues in older checkpoints)
torch.serialization.add_safe_globals([argparse.Namespace])
# Load the model architecture only on container startup (changed to pretrained=False)
# Precision (FP32 for full accuracy, could be 'fp16' for speed on Ampere+ GPUs)
with redirect_stdout(StringIO()), redirect_stderr(StringIO()):
    gpunet = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_gpunet', pretrained=False, model_type='GPUNet-0', model_math='fp32')

# Download weights from S3 if not present, then load them
model_path = os.getenv('MODEL_PATH', '/cache/torch/hub/checkpoints/gpunet-0.pth')
os.makedirs(os.path.dirname(model_path), exist_ok=True)  # Ensure checkpoints dir exists
if not os.path.exists(model_path):
    s3 = boto3.client('s3')
    s3.download_file(os.getenv('S3_BUCKET_NAME'), 'gpunet-0.pth', model_path)
checkpoint = torch.load(model_path, map_location=device, weights_only=True)
gpunet.load_state_dict(checkpoint['state_dict'])
# Move to GPU/CPU
gpunet.to(device)
gpunet.eval()

# Preprocessing
preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# Load ImageNet labels
labels_url = "https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json"
response = requests.get(labels_url)
json_data = json.loads(response.text)
labels = [json_data[str(i)][1].replace('_', ' ') for i in range(1000)]

# Required, FastAPI root
@app.get("/")
async def hello():
    return {"status": "hello"}

# Serve model requests
@app.post("/predict")
async def predict(image_url: str):
    try:
        response = requests.get(image_url)
        response.raise_for_status()
        img = Image.open(BytesIO(response.content)).convert("RGB")
        input_tensor = preprocess(img).unsqueeze(0).to(device)

        with torch.no_grad():
            output = gpunet(input_tensor)

        probs = softmax(output, dim=1)[0]
        top5_idx = probs.topk(5).indices.cpu().numpy()
        top5_probs = probs.topk(5).values.cpu().numpy()

        results = [{ "label": labels[idx], "probability": float(prob) } for idx, prob in zip(top5_idx, top5_probs)]

        return {"predictions": results}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))
```

#### Crie o Dockerfile
<a name="_create_the_dockerfile"></a>

O Dockerfile a seguir cria uma imagem de contêiner para nossa aplicação utilizando o modelo GPUnet do repositório [NVIDIA Deep Learning Examples for Tensor Cores](https://github.com/NVIDIA/DeepLearningExamples) do GitHub.

Reduzimos o tamanho da imagem do contêiner usando uma base PyTorch somente no runtime, instalando somente pacotes essenciais com limpeza de cache, pré-armazenamento em cache do código do modelo e evitando o "baking" de pesos na imagem do contêiner para permitir extrações e atualizações mais rápidas. Para saber mais, consulte [Redução de tamanhos de imagens de contêineres](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-performance.html#_reducing_container_image_sizes).

No mesmo diretório do `app.py`, crie o `Dockerfile`:

```
FROM pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime

# Install required system packages required for git cloning
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*

# Install application dependencies
RUN pip install --no-cache-dir fastapi uvicorn requests pillow boto3 timm==0.5.4

# Pre-cache the GPUNet code from Torch Hub (without weights)
# Clone the repository containing the GPUNet code
RUN mkdir -p /cache/torch/hub && \
    cd /cache/torch/hub && \
    git clone --branch torchhub --depth 1 https://github.com/NVIDIA/DeepLearningExamples NVIDIA_DeepLearningExamples_torchhub

COPY app.py /app/app.py

WORKDIR /app

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]
```

#### Teste a aplicação
<a name="_test_the_application"></a>

No mesmo diretório do seu `app.py` e `Dockerfile`, crie a imagem do contêiner para a aplicação de inferência, visando a arquitetura AMD64:

```
docker build --platform linux/amd64 -t gpunet-inference-app .
```

Defina variáveis de ambiente para suas credenciais da AWS e, opcionalmente, um token de sessão da AWS. Por exemplo:

```
export AWS_REGION="us-east-1"
export AWS_ACCESS_KEY_ID=ABCEXAMPLESCUJFEIELSMUHHAZ
export AWS_SECRET_ACCESS_KEY=123EXAMPLEMZREoQXr8XkiicsOgWDQ5TpUsq0/Z
```

Execute o contêiner localmente, injetando credenciais da AWS como variáveis de ambiente para acesso ao S3. Por exemplo:

```
docker run --platform linux/amd64 -p 8080:80 \
  -e S3_BUCKET_NAME=${S3_BUCKET_NAME} \
  -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
  -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
  -e AWS_DEFAULT_REGION=${AWS_REGION} \
  gpunet-inference-app
```

A saída esperada deve ser semelhante a essa:

```
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
```

Em uma nova janela de terminal, teste o endpoint de inferência enviando uma amostra de solicitação POST com uma URL de imagem pública como parâmetro de consulta:

```
curl -X POST "http://localhost:8080/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
```

A saída esperada deve ser uma resposta JSON com as 5 principais previsões, semelhante a esta (rótulos e probabilidades reais podem variar um pouco com base na precisão da imagem e do modelo):

```
{"predictions":[{"label":"desk","probability":0.28885871171951294},{"label":"laptop","probability":0.24679335951805115},{"label":"notebook","probability":0.08539070934057236},{"label":"library","probability":0.030645888298749924},{"label":"monitor","probability":0.02989606373012066}]}
```

Saia da aplicação usando "Ctrl \+ C".

### Envio do contêiner para o Amazon ECR por push
<a name="_push_the_container_to_amazon_ecr"></a>

Nesta etapa, carregamos a imagem do contêiner do serviço de modelo GPUnet-0 no [Amazon Elastic Container Registry (ECR)](https://docs.aws.amazon.com/AmazonECR/latest/userguide/what-is-ecr.html), disponibilizando-a para implantação no Amazon EKS. Esse processo envolve a criação de um novo repositório do ECR para armazenar a imagem, a autenticação com o ECR e, em seguida, a marcação e o envio da imagem do contêiner para o nosso registro.

Primeiro, volte para o diretório em que você definiu suas variáveis de ambiente no início deste guia. Por exemplo:

```
cd ..
```

Crie um repositório no Amazon ECR:

```
aws ecr create-repository --repository-name gpunet-inference-app --region ${AWS_REGION}
```

Faça login no Amazon ECR:

```
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
```

A saída esperada deve ser semelhante a essa:

```
Login Succeeded
```

Aplique tags na imagem:

```
docker tag gpunet-inference-app:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
```

Envie a imagem por push para o seu repositório do Amazon ECR:

```
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
```

Demorará vários minutos para que essa etapa seja concluída.

## 7. (Opcional) Exposição do serviço de modelo
<a name="_7_optional_expose_the_model_service"></a>

Nesta etapa, você exporá seu serviço de modelo de inferência em tempo real externamente no Amazon EKS usando o AWS Load Balancer Controller (LBC). Isso envolve configurar o LBC, montar pesos do modelo do Amazon S3 como um volume persistente usando o driver CSI Mountpoint S3, implantar um pod de aplicações acelerado por GPU, criar um serviço e uma entrada para provisionar um Application Load Balancer (ALB) e testar o endpoint.

Primeiro, verifique a associação de Identidade de Pods com o AWS LBC, confirmando se a conta de serviço está vinculada corretamente ao perfil do IAM necessário:

```
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --namespace kube-system --service-account-name aws-load-balancer-controller
```

A saída esperada deve ser semelhante a essa:

```
ASSOCIATION ARN                                                    NAMESPACE    SERVICE ACCOUNT NAME        IAM ROLE ARN    OWNER ARN
arn:aws:eks:us-east-1:143095308808:podidentityassociation/eks-rt-inference-us-east-1/a-buavluu2wp1jropya    kube-system     aws-load-balancer-controller    arn:aws:iam::143095308808:role/AmazonEKSLoadBalancerControllerRole
```

### Aplicação de tags no seu grupo de segurança de cluster
<a name="_tag_your_cluster_security_group"></a>

O AWS Load Balancer Controller oferece suporte apenas um único grupo de segurança com a chave de tag `karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"` para a seleção do grupo de segurança do Karpenter. Ao criar um cluster com o eksctl, o grupo de segurança padrão do cluster (que tem a tag `"kubernetes.io/cluster/<cluster-name>: owned"`) não é automaticamente marcado com tags `karpenter.sh/discovery`. Essa tag é essencial para que o Karpenter descubra e anexe esse grupo de segurança aos nós que ele provisiona. A associação desse grupo de segurança garante a compatibilidade com o AWS Load Balancer Controller (LBC), permitindo que ele gerencie automaticamente as regras de tráfego de entrada para serviços expostos via Ingress, como o serviço modelo nessas etapas.

Exporte o ID da VPC para o seu cluster:

```
CLUSTER_VPC_ID="$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query cluster.resourcesVpcConfig.vpcId --output text)"
```

Exporte o grupo de segurança padrão para seu cluster:

```
CLUSTER_SG_ID="$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=$CLUSTER_VPC_ID Name=tag-key,Values=kubernetes.io/cluster/${EKS_CLUSTER_NAME} --query 'SecurityGroups[].[GroupId]' --output text)"
```

Adicione a tag `karpenter.sh/discovery` ao grupo de segurança padrão do cluster. Isso permitirá que nossos seletores EC2NodeClass de CPU e GPU o usem:

```
aws ec2 create-tags --resources ${CLUSTER_SG_ID} --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}
```

Verifique se a tag foi adicionada:

```
aws ec2 describe-security-groups --group-ids ${CLUSTER_SG_ID} --query "SecurityGroups[].Tags"
```

Entre os resultados, é necessário ver a saída a seguir com a tag e o nome do seu cluster. Por exemplo:

```
{
  "Key": "karpenter.sh/discovery",
  "Value": "eks-rt-inference-us-east-1"
}
```

### Configuração do AWS Load Balancer Controller (LBC)
<a name="setup_the_shared_aws_load_balancer_controller_lbc"></a>

O AWS LBC é essencial para gerenciar o tráfego de entrada para workloads de IA/ML no Amazon EKS, garantindo acesso a endpoints de inferência ou pipelines de processamento de dados. Ao se integrar com AWS Application Load Balancers (ALB) e Network Load Balancers (NLB), o LBC roteia dinamicamente o tráfego para aplicações em contêineres, como aqueles que executam grandes modelos de linguagem, modelos de visão computacional ou serviços de inferência em tempo real. Como já criamos a conta de serviço e a associação de Identidade de Pods durante a criação do cluster, definimos `serviceAccount.name` para corresponder ao que está definido na configuração do nosso cluster (`aws-load-balancer-controller`).

Adicione o repositório de chart do Helm **eks-charts** de propriedade de AWS:

```
helm repo add eks https://aws.github.io/eks-charts
```

Atualize seus repositórios locais do Helm com os charts mais recentes:

```
helm repo update eks
```

Implante o AWS LBC usando o Helm, especificando o nome do cluster de EKS e referenciando a conta de serviço pré-criada:

```
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=${EKS_CLUSTER_NAME} \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller
```

A saída esperada deve ser semelhante a essa:

```
NAME: aws-load-balancer-controller
LAST DEPLOYED: Wed Jul 9 15:03:31 2025
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!
```

### Monte o modelo em um volume persistente
<a name="_mount_the_model_in_a_persistent_volume"></a>

Nesta etapa, você montará pesos de modelo a partir do seu bucket do Amazon S3 usando um PersistentVolume (PV) apoiado pelo driver CSI Mountpoint para Amazon S3. Isso permite que os pods do Kubernetes acessem objetos do S3 como arquivos locais, eliminando downloads que consumam muitos recursos para armazenamento temporário de pods ou contêineres iniciais, ideais para pesos de modelos grandes de vários gigabytes.

O PV monta toda a raiz do bucket (nenhum caminho especificado em `volumeAttributes`), oferece suporte ao acesso simultâneo somente para leitura por vários pods e expõe arquivos como os pesos do modelo (`/models/gpunet-0.pth`) dentro do contêiner para inferência. Isso garante que o "download" alternativo em nossa aplicação (`app.py`) não seja acionado porque o arquivo existe por meio da montagem. Ao desacoplar o modelo da imagem do contêiner, isso permite acesso compartilhado e atualizações independentes da versão do modelo sem reconstruções de imagens.

#### Criação do PersistentVolume (PV)
<a name="_create_the_persistentvolume_pv"></a>

Crie um recurso PersistentVolume (PV) para montar o bucket do S3 contendo os pesos do seu modelo, permitindo o acesso somente de leitura a vários pods sem baixar arquivos no runtime:

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  name: s3-model-pv
spec:
  capacity:
    storage: 5Gi  # Ignored by the driver; can be any value
  accessModes:
    - ReadOnlyMany  # Read only
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""  # Required for static provisioning
  claimRef:
    namespace: default  # Adjust if you prefer a different namespace
    name: s3-model-pvc
  mountOptions:
    - allow-other  # Enables multi-user access (useful for non-root pods)
    - region ${AWS_REGION} # Optional, include if your bucket is in a different region than the cluster
  csi:
    driver: s3.csi.aws.com
    volumeHandle: gpunet-model-volume  # Must be unique across all PVs
    volumeAttributes:
      bucketName: ${S3_BUCKET_NAME}
EOF
```

#### Criação do PersistentVolumeClaim (PVC)
<a name="_create_the_persistentvolumeclaim_pvc"></a>

Crie um PersistentVolumeClaim (PVC) para vincular ao PV, solicitando acesso somente de leitura aos dados do modelo S3 montado:

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: s3-model-pvc
spec:
  accessModes:
    - ReadOnlyMany
  storageClassName: ""  # Required for static provisioning
  resources:
    requests:
      storage: 5Gi  # Ignored, match PV capacity
  volumeName: s3-model-pv  # Bind to the PV created above
EOF
```

#### Implantar a aplicação
<a name="_deploy_the_application"></a>

Implante a aplicação de inferência como uma implantação do Kubernetes, montando o volume persistente baseado em S3 para acesso ao modelo, aplicando seletores e tolerâncias de nós da GPU e definindo variáveis de ambiente para o caminho do modelo. Essa implantação define o caminho do modelo (var env de `"/models/gpunet-0.pth"`), então nossa aplicação (em `app.py`) usará esse caminho por padrão. Com a montagem do volume de implantação em `/models` (somente leitura), o download do modelo não será acionado se o arquivo já estiver presente por meio do PVC.

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpunet-inference-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gpunet-inference-app
  template:
    metadata:
      labels:
        app: gpunet-inference-app
    spec:
      tolerations:
      - key: "nvidia.com/gpu"
        operator: "Exists"
        effect: "NoSchedule"
      nodeSelector:
        role: gpu-worker
      containers:
      - name: inference
        image: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
        ports:
        - containerPort: 80
        env:
        - name: MODEL_PATH
          value: "/models/gpunet-0.pth"
        resources:
          limits:
            nvidia.com/gpu: 1
          requests:
            nvidia.com/gpu: 1
        volumeMounts:
        - name: model-volume
          mountPath: /models
          readOnly: true
      volumes:
      - name: model-volume
        persistentVolumeClaim:
          claimName: s3-model-pvc
EOF
```

Serão necessários alguns minutos para que o Karpenter provisione um nó de GPU, caso ainda não haja um disponível. Verifique se o pod de inferência está em um estado "Em execução":

```
kubectl get pods -l app=gpunet-inference-app
```

A saída esperada deve ser semelhante a essa:

```
NAME                               READY   STATUS    RESTARTS   AGE
gpunet-inference-app-5d4b6c7f8-abcde        1/1     Running   0          2m
```

### Exposição do serviço com Ingress e balanceador de carga
<a name="_expose_the_service_with_ingress_and_load_balancer"></a>

Crie um serviço ClusteriP para expor a implantação de inferência internamente no cluster de EKS, visando a porta da aplicação:

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: gpunet-model-service
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: gpunet-inference-app
EOF
```

Crie um recurso do Ingress para provisionar um Application Load Balancer (ALB) voltado para a Internet por meio do AWS LBC, roteando o tráfego externo para o serviço de inferência:

```
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gpunet-model-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  ingressClassName: alb
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gpunet-model-service
            port:
              number: 80
EOF
```

Espere alguns minutos para que o Application Load Balancer (ALB) conclua o provisionamento. Monitore o status do recurso de entrada para confirmar se o ALB foi provisionado:

```
kubectl get ingress gpunet-model-ingress
```

A saída esperada deve ter a seguinte aparência (com o campo ENDEREÇO preenchido):

```
NAME                   CLASS   HOSTS   ADDRESS                                         PORTS   AGE
gpunet-model-ingress   alb     *       k8s-default-gpunetmo-183de3f819-516310036.us-east-1.elb.amazonaws.com   80      6m58s
```

Extraia e exporte o nome do host de ALB do status do Ingress para uso em testes subsequentes:

```
export ALB_HOSTNAME=$(kubectl get ingress gpunet-model-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
```

### Teste do serviço de modelo
<a name="_test_the_model_service"></a>

Valide o endpoint de inferência exposto enviando uma solicitação POST com um exemplo de URL de imagem (por exemplo, do conjunto de dados COCO), simulando a previsão em tempo real:

```
curl -X POST "http://${ALB_HOSTNAME}/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
```

A saída esperada deve ser uma resposta JSON com as 5 principais previsões, semelhante a esta (rótulos e probabilidades reais podem variar um pouco com base na precisão da imagem e do modelo):

```
{"predictions":[{"label":"desk","probability":0.2888975441455841},{"label":"laptop","probability":0.2464350312948227},{"label":"notebook","probability":0.08554483205080032},{"label":"library","probability":0.030612602829933167},{"label":"monitor","probability":0.029896672815084457}]}
```

Opcionalmente, é possível continuar testando outras imagens em uma nova solicitação POST. Por exemplo:

```
http://images.cocodataset.org/test-stuff2017/000000024309.jpg
http://images.cocodataset.org/test-stuff2017/000000028117.jpg
http://images.cocodataset.org/test-stuff2017/000000006149.jpg
http://images.cocodataset.org/test-stuff2017/000000004954.jpg
```

## Conclusão
<a name="_conclusion"></a>

Neste guia, você configurou um cluster do Amazon EKS otimizado para workload de inferência em tempo real aceleradas por GPU. Você provisionou um cluster com [instâncias G5 do EC2](https://aws.amazon.com/ec2/instance-types/g5/), instalou o [driver CSI Mountpoint S3](https://docs.aws.amazon.com/eks/latest/userguide/s3-csi.html), o [agente de Identidade de Pods do EKS](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html), o [agente de monitoramento de nós do EKS](https://docs.aws.amazon.com/eks/latest/userguide/node-health.html), a [AMI do Bottlerocket](https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami-bottlerocket.html), o [AWS Load Balancer Controller (LBC)](https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html) e o [Karpenter](https://karpenter.sh/) para gerenciar NodePools de CPUs e GPUs. Você usou o plug-in de dispositivo NVIDIA para habilitar a programação da GPU e configurou o S3 com PersistentVolume e PersistentVolumeClaim para acesso ao modelo. Você validou a configuração implantando um pod de GPU de exemplo, configurando o acesso ao modelo [GPUnet-0](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/dle/models/gpunet_0_pyt_ckpt) da NVIDIA no [Amazon S3](https://aws.amazon.com/s3/), habilitando a inicialização do pod e expondo o serviço de inferência por meio do Application Load Balancer. Para utilizar totalmente seu cluster, configure o [Agente de monitoramento de nós do EKS](https://docs.aws.amazon.com/eks/latest/userguide/node-health.html) com reparo automático. Certifique-se de realizar testes de benchmark, incluindo avaliações de performance, latência e throughput da GPU, para otimizar os tempos de resposta. Para saber mais, consulte [Uso de ferramentas de monitoramento e observabilidade para suas workloads de IA/ML](https://docs.aws.amazon.com/eks/latest/best-practices/aiml-observability.html#_using_monitoring_and_observability_tools_for_your_aiml_workloads).

## Limpeza
<a name="_clean_up"></a>

Para evitar cobranças futuras, você precisará excluir manualmente a pilha associada do CloudFormation para excluir todos os recursos criados durante este guia, incluindo a rede VPC.

Exclua a pilha do CloudFormation usando o sinalizador `--wait` com o eksctl:

```
eksctl delete cluster --region ${AWS_REGION} --name ${EKS_CLUSTER_NAME} --wait
```

Após a conclusão, você verá a seguinte saída de resposta:

```
2025-07-29 13:03:55 [✔]  all cluster resources were deleted
```

Exclua o bucket do Amazon S3 criado durante este guia usando o [console do Amazon S3](https://console.aws.amazon.com/s3/home).