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 de melhores práticas para inferência em tempo real no Amazon EKS
Introdução
Este guia oferece um passo a passo prático para configurar um cluster de 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 EKS Quickstart, 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 descreva 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 de 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 ou nossos workshops em Recursos para começar a usar IA/ML no Amazon EKS.
Antes de começar
Antes de começar, certifique-se de ter realizado as seguintes tarefas:
Arquitetura
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 permite 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
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 de 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
: para tarefas de inferência com uso intensivo de GPU, estamos usando os tipos de instância G5.xlarge e g5.2xlarge G5 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 para obter uma latência ideal em tempo real. Recomendamos começar com as instâncias G5 do EC2 , 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 . -
Instâncias M7g do Amazon EC2
: 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 , 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 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: 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 de 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 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: 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 de Amazon EKS. A Identidade de Pods do EKS oferece uma alternativa simplificada à abordagem tradicional de perfis do IAM para contas de serviço (IRSA), 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 e perfis do IAM de destino.
-
Agente de monitoramento de nós do EKS: 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 isolando 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: 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 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.
-
Load Balancer Controller (LBC) da AWS: para expor endpoints de inferência em tempo real, estamos usando o Load Balancer Controller da AWS, 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
Nesta etapa, criamos um cluster com nós de CPU e um grupo de nós gerenciados usando um modelo eksctl ClusterConfig
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.
Verificação das suas credenciais
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
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
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
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
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
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.13.0/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
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. 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
2. Verificação da integridade do nó do cluster e do pod
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
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
Nesta etapa, configuraremos os NodePools do Karpenterlimits
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
Configuração do NodePool de GPU
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çãoconsolidateAfter: 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 e Uso de TTLSecondAfterFinished para lipeza automática de tarefas do Kubernetes.
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
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çãoconsolidateAfter: 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 e Uso de TTLSecondAfterFinished para lipeza automática de tarefas do Kubernetes.
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
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.
Implantação de um pod de exemplo
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.
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-ndivia-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
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
Configurar o ambiente
Para baixar os pesos do modelo GPUnet-0 nesta etapa, você precisará acessar o catálogo NGC da NVIDIA e ter o Docker
-
Inscreva-se em uma conta gratuita do NGC
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
(Linux/macOS/Windows) e configure a CLI usando: ngc config set
. Insira sua chave de API quando solicitada; defina org comonvidia
e pressione Enter para aceitar padrões para outras pessoas. Se tiver êxito, você deverá ver algo parecido com:Successfully saved NGC configuration to /Users/your-username/.ngc/config
.
Verificação de permissões de conta de serviço
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
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
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 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.
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
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
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.
Criar a aplicação
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
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
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.
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
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
Nesta etapa, carregamos a imagem do contêiner do serviço de modelo GPUnet-0 no Amazon Elastic Container Registry (ECR), 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
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
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, você deve 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)
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
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)
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)
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
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
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
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
Neste guia, você configurou um cluster de Amazon EKS otimizado para workload de inferência em tempo real aceleradas por GPU. Você provisionou um cluster com instâncias G5 do EC2
Limpeza
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