Contribuisci a migliorare questa pagina
Per contribuire a questa guida per l’utente, seleziona il link Edit this page on GitHub che si trova nel riquadro destro di ogni pagina.
Guida alle best practice per la configurazione dei cluster per l’inferenza in tempo reale su Amazon EKS
Introduzione
Questa guida offre un aiuto pratico per configurare un cluster Amazon Elastic Kubernetes Service (EKS) ottimizzato per carichi di lavoro di inferenza online in tempo reale, integrando le best practice curate dagli esperti AWS. Utilizza un’architettura EKS Quickstart autorevole, un set curato di driver, tipi di istanze e configurazioni in linea con le best practice AWS per modelli, acceleratori e scalabilità. Questo approccio consente di aggirare il compito di selezionare le impostazioni del cluster, consentendo di ottenere rapidamente un cluster funzionale e preconfigurato. Durante il processo, distribuiremo esempi di carichi di lavoro per convalidare la configurazione, spiegheremo i concetti chiave dell’architettura (come il disaccoppiamento delle attività legate alla CPU dai calcoli a uso intensivo della GPU), risponderemo alle domande comuni (ad esempio, perché scegliere le AMI Bottlerocket rispetto ad AL2023?) e delineeremo i passaggi successivi per estendere le funzionalità del cluster.
Progettata specificamente per ingegneri di machine learning (ML) e intelligenza artificiale (IA), amministratori di piattaforma, operatori e specialisti di dati/IA che non conoscono AWS e l’ecosistema EKS, questa guida presuppone una conoscenza di Kubernetes ma nessuna esperienza precedente con EKS. Infatti, ha l’obiettivo di aiutare a comprendere i passaggi e i processi necessari per rendere operativi i carichi di lavoro di inferenza online in tempo reale. La guida illustra gli elementi essenziali della creazione di un cluster di inferenza a nodo singolo, tra cui il provisioning di risorse GPU, l’integrazione dell’archiviazione per gli artefatti del modello, l’abilitazione dell’accesso sicuro ai servizi AWS e l’esposizione degli endpoint di inferenza. Nel complesso, si concentra sul design resiliente e a bassa latenza per applicazioni rivolte agli utenti come il rilevamento delle frodi, i chatbot in tempo reale e l’analisi del sentiment nei sistemi di feedback dei clienti.
In questa guida, ci concentriamo esclusivamente sulla configurazione di un punto di partenza fondamentale e prescrittivo utilizzando le istanze G5 di EC2. Se cerchi configurazioni di cluster specifici per AWS Inferentia o flussi di lavoro end-to-end, consulta Usa le istanze AWS Inferentia con Amazon EKS per il machine learning o i nostri workshop su Risorse per iniziare a utilizzare IA/ML su Amazon EKS.
Prima di iniziare
Prima di iniziare, esegui le seguenti attività:
Architettura
L’inferenza online in tempo reale si riferisce al processo di utilizzo di un modello di machine learning addestrato per generare previsioni oppure output sui flussi di dati in entrata con una latenza minima. Ad esempio, consente il rilevamento delle frodi in tempo reale, la classificazione delle immagini o la generazione di grafici di conoscenza in risposta agli input degli utenti. L’architettura di un sistema di inferenza online in tempo reale offre previsioni di machine learning a bassa latenza nelle applicazioni rivolte agli utenti, disaccoppiando la gestione del traffico web legato alla CPU dai calcoli di intelligenza artificiale a uso intensivo di GPU. Questo processo si trova in genere all’interno di un ecosistema applicativo più ampio e spesso include servizi di backend, frontend, vettoriali e di modello, con particolare attenzione a componenti specializzati per consentire scalabilità indipendente, sviluppo parallelo e resilienza ai guasti. L’isolamento delle attività di inferenza su hardware GPU dedicati e l’utilizzo di interfacce come API e WebSocket garantiscono un’elevata concorrenza, un’elaborazione rapida di modelli come i trasformatori e le interazioni degli utenti tramite il frontend. Tieni presente che, sebbene svolgano spesso un ruolo importante nei sistemi di inferenza in tempo reale, i database vettoriali e le pipeline di generazione potenziata da recupero dati (RAG) non sono trattati in questa guida. Un’architettura tipica spesso include almeno:
-
Servizio frontend: funge da interfaccia utente, gestisce la logica lato client, rende contenuti dinamici e facilita le interazioni in tempo reale, oltre a comunicare con il servizio di backend per avviare richieste di inferenza e visualizzare i risultati, spesso avviando richieste al servizio di backend che utilizza WebSockets per lo streaming di aggiornamenti o API per lo scambio di dati strutturati. In genere questo servizio non richiede un sistema di bilanciatore del carico dedicato, in quanto può essere ospitato su una rete di distribuzione di contenuti (CDN) come AWS CloudFront per risorse statiche o funzionare direttamente da server Web, con scalabilità gestita tramite gruppi Auto Scaling, se necessario, per contenuti dinamici.
-
Servizio backend: funge da orchestratore dell’applicazione, gestendo la logica di business come l’autenticazione degli utenti, la convalida dei dati e il coordinamento dei servizi (ad esempio, tramite API per endpoint RESTful o WebSockets per connessioni persistenti). Comunica con il servizio di inferenza, si adatta in modo indipendente su CPU e RAM multi-core per gestire un traffico web elevato senza fare affidamento sulle GPU e spesso richiede un sistema di bilanciatore del carico (come AWS Application Load Balancer o Network Load Balancer) per distribuire le richieste in entrata su più istanze, specialmente in scenari ad alta concorrenza. Un controller di ingresso può gestire ulteriormente l’accesso esterno e le regole di routing per migliorare la sicurezza e la modellazione del traffico.
-
Servizio di inferenza: funge da base per i calcoli di intelligenza artificiale, in esecuzione su GPU con VRAM sufficiente (ad esempio, 8-12 GB per modelli come DistilBERT) per eseguire embedding vettoriali, estrazione di conoscenze e inferenza del modello (ad esempio, esposto tramite API per richieste batch o WebSocket per lo streaming in tempo reale) utilizzando modelli personalizzati oppure open source. Questo isolamento previene i conflitti di dipendenza, consente di aggiornare i modelli senza tempo di inattività e agevola il dimensionamento orizzontale con bilanciamento del carico per più richieste simultanee. Per esporre in modo efficace il servizio del modello, in genere si basa su un sistema di bilanciatore del carico per distribuire i carichi di lavoro legati alla GPU tra istanze replicate, mentre una risorsa o un controller di ingresso (come ALB Ingress Controller in AWS) gestisce il routing esterno, la terminazione SSL e l’inoltro basato sul percorso per garantire un accesso sicuro ed efficiente senza sovraccaricare le singole GPU.
Panoramica soluzione
I sistemi di inferenza online in tempo reale richiedono un’architettura resiliente e ad alte prestazioni in grado di fornire una latenza estremamente bassa gestendo al contempo esplosioni di traffico imprevedibili e ad alto volume. Questa panoramica della soluzione spiega come i seguenti componenti AWS interagiscono nel cluster Amazon EKS che creeremo per garantire che il cluster sia in grado di ospitare e gestire modelli di machine learning che forniscono previsioni immediate su dati in tempo reale con un ritardo minimo per gli utenti finali.
-
Istanze Amazon G5 EC2
: per attività di inferenza a uso intensivo di GPU, utilizziamo i tipi di istanze G5 EC2 g5.xlarge e g5.2xlarge, che dispongono di una (1) singola GPU NVIDIA A10G con 24 GB di memoria (ad esempio, 8 miliardi di parametri in FP16). Queste GPU, basate sull’architettura NVIDIA Ampere, sono alimentate da GPU NVIDIA A10G Tensor Core e processori AMD EPYC di seconda generazione, supportano 4-8 vCPU, larghezza di banda della rete fino a 10 Gbps e 250-450 GB di archiviazione SSD NVMe locale, garantendo un rapido spostamento dei dati e potenza di calcolo per modelli complessi. Per questo motivo, sono ideali per attività di inferenza a bassa latenza e ad alto throughput. La scelta di un tipo di istanza EC2 è specifica dell’applicazione, dipende dal modello (ad esempio, immagine, video, modello di testo) e dai requisiti di latenza e throughput effettivo. Ad esempio, se utilizzi un modello di immagine e/o video, potresti voler utilizzare le istanze EC2 P5 per una latenza ottimale in tempo reale. Ti consigliamo di iniziare con le istanze G5 EC2 in quanto forniscono un buon punto di partenza per iniziare subito a funzionare, per poi valutare se sono la soluzione giusta per i tuoi carichi di lavoro attraverso test di benchmark delle prestazioni. Per casi più avanzati, prendi in considerazione le istanze G6 EC2 . -
Istanze Amazon EC2 M7g
: per attività che richiedono un uso intensivo della CPU come la pre-elaborazione dei dati, la gestione delle richieste API, l’hosting del controller Karpenter, componenti aggiuntivi e altri componenti di sistema, utilizziamo il tipo di istanza EC2 M7g m5.xlarge. Le istanze M7g sono basate su ARM che dispongono di 4 vCPU, 16 GB di memoria e larghezza di banda della rete fino a 12,5 Gbps e sono alimentata da processori AWS Graviton3. La scelta di un tipo di istanza EC2 è specifica dell’applicazione e dipende dai requisiti di calcolo, memoria e scalabilità del carico di lavoro. Per carichi di lavoro ottimizzati per il calcolo, potresti prendere in considerazione le istanze C7g EC2 , che utilizzano anch’esse processori Graviton3 ma sono ottimizzate per prestazioni di calcolo più elevate rispetto alle istanze M7g per determinati casi d’uso. In alternativa, le istanze C8g EC2 più recenti (se disponibili) offrono prestazioni di elaborazione migliori fino al 30% rispetto alle istanze C7g. Consigliamo di iniziare con le istanze M7g EC2 per la loro efficienza in termini di costi e la compatibilità con un’ampia gamma di carichi di lavoro (ad esempio, server di applicazioni, microservizi, server di gioco, archivi dati di medie dimensioni) per poi valutare se sono la soluzione giusta per i propri carichi di lavoro attraverso test di benchmark delle prestazioni. -
Driver CSI Mountpoint di Amazon S3: per carichi di lavoro su istanze a GPU singola in cui più pod condividono una GPU (ad esempio, più pod pianificati sullo stesso nodo per utilizzare le relative risorse GPU), utilizziamo il driver CSI Mountpoint di S3 per ottimizzare l’utilizzo della memoria, essenziale per attività come l’inferenza del modello di grandi dimensioni in configurazioni sensibili ai costi e a bassa complessità. Infatti, espone i bucket Amazon S3 come un file system simile a POSIX disponibile per il cluster Kubernetes, che consente ai pod di inferenza di leggere gli artefatti del modello (ad esempio, il peso) direttamente nella memoria senza doverli prima scaricare e di inserire i set di dati utilizzando operazioni standard sui file. Inoltre, S3 ha una capacità di archiviazione praticamente illimitata e accelera i carichi di lavoro di inferenza ad alta intensità di dati. La scelta di un driver CSI di archiviazione è specifica dell’applicazione e dipende dai requisiti di throughput e dalla latenza del carico di lavoro. Anche se il driver CSI FSx per OpenZFS offre una latenza inferiore al millisecondo per I/O casuali o volumi persistenti condivisi completamente conformi a POSIX tra i nodi, consigliamo di iniziare con il driver CSI Mountpoint di S3 per la sua scalabilità, i costi inferiori per set di dati di grandi dimensioni e l’integrazione con l’archiviazione di oggetti gestita da S3 per modelli di inferenza ad alta intensità di lettura (ad esempio, input del modello di streaming) e valutare successivamente se è la soluzione giusta per i propri carichi di lavoro attraverso test di benchmark delle prestazioni.
-
Agente Pod Identity di EKS: per consentire l’accesso ai servizi AWS, utilizziamo l’agente Pod Identity di EKS, che usa un unico servizio principale e facilita le associazioni di ruoli IAM a livello di pod all’interno del cluster Amazon EKS. Pod Identity di EKS offre un’alternativa semplificata al tradizionale approccio IAM Roles for Service Accounts (IRSA) utilizzando un unico principale del servizio (pods.eks.amazonaws.com) anziché affidarsi a singoli provider OIDC per ogni cluster, il che semplifica l’assegnazione delle autorizzazioni. Inoltre, consente il riutilizzo dei ruoli su più cluster e supporta funzionalità avanzate come IAM role session tags e Target IAM roles.
-
Agente di monitoraggio dei nodi di EKS: per garantire la disponibilità e l’affidabilità continue dei servizi di inferenza, utilizziamo l’agente di monitoraggio dei nodi di EKS con la riparazione automatica, che rileva e sostituisce automaticamente i nodi non integri riducendo al minimo il tempo di inattività. Monitora continuamente i nodi per rilevare problemi di hardware, kernel, rete e archiviazione utilizzando controlli dell’integrità avanzati (ad esempio, KernelReady, NetworkingReady). Per i nodi GPU, rileva i guasti specifici dell’acceleratore e avvia una correzione adeguata isolando i nodi non integri, attendendo 10 minuti per la risoluzione dei problemi transitori della GPU e sostituendo i nodi dopo 30 minuti in caso di guasti persistenti.
-
AMI Bottlerocket: per fornire una base sicura per il cluster EKS, utilizziamo l’AMI Bottlerocket, che include solo i componenti essenziali necessari per eseguire i container e offre tempi di avvio minimi per una scalabilità rapida. La scelta di un nodo AMI è specifica dell’applicazione e dipende dai requisiti di personalizzazione, sicurezza e scalabilità del carico di lavoro. L’AMI AL2023 offre sicuramente una maggiore flessibilità per le installazioni e le personalizzazioni a livello di host (ad esempio, specificando una directory cache dedicata in un PV/PVC senza configurazioni di nodi aggiuntive), ma consigliamo di iniziare con l’AMI Bottlerocket per il suo footprint ridotto e l’ottimizzazione integrata per carichi di lavoro containerizzati (ad esempio, microservizi, server di inferenza, API scalabili) e poi valutare se è la soluzione giusta per i propri carichi di lavoro attraverso test di benchmark delle prestazioni.
-
AWS Load Balancer Controller (LBC): per esporre gli endpoint di inferenza in tempo reale, utilizziamo il Load Balancer Controller di AWS, che fornisce e gestisce automaticamente gli Application Load Balancer (ALB) per il traffico HTTP/HTTPS e i Network Load Balancer (NLB) per il traffico TCP/UDP in base alle risorse Kubernetes Ingress and Service, abilitando l’integrazione di modelli di inferenza con client esterni. Inoltre, supporta funzionalità come il routing basato sul percorso per distribuire le richieste di inferenza su più pod o nodi, garantendo la scalabilità durante i picchi di traffico e riducendo al minimo la latenza attraverso ottimizzazioni native di AWS come il multiplexing delle connessioni e i controlli dell’integrità.
1. Creazione di un cluster EKS
In questo passaggio, creiamo un cluster con nodi CPU e un gruppo di nodi gestiti utilizzando un modello ClusterConfig
Per impostazione predefinita, eksctl creerà un VPC dedicato per il cluster con un intervallo CIDR di 192.168.0.0/16. Il VPC include tre sottoreti pubbliche e tre sottoreti private, ciascuna distribuita su tre diverse zone di disponibilità (o due nella regione us-east-1), il metodo ideale per l’implementazione dei carichi di lavoro Kubernetes. Il modello implementa anche un gateway Internet, che fornisce l’accesso a Internet alle sottoreti pubbliche tramite percorsi predefiniti nelle relative tabelle di routing e un singolo gateway NAT in una delle sottoreti pubbliche, con percorsi predefiniti nelle tabelle di routing delle sottoreti private che indirizzano il traffico in uscita attraverso il gateway NAT per l’accesso a Internet. Per ulteriori informazioni su questa configurazione, consulta Deploy Nodes to Private Subnets.
Verifica le credenziali
Verifica se le tue credenziali AWS CLI sono valide e possono autenticarsi con i servizi AWS:
aws sts get-caller-identity
In caso di successo, la CLI restituirà i dettagli sulla tua identità AWS (UserID, Account e ARN).
Verifica la disponibilità delle istanze
I tipi di istanze G5 sono disponibili in tutte le regioni. Controlla la regione più vicina. Per esempio:
aws ec2 describe-instance-types --instance-types g5.xlarge g5.2xlarge --region us-east-1
in caso di successo, il tipo di istanza G5 è disponibile nella regione specificata.
L’AMI Bottlerocket non è disponibile in tutte le regioni. Verifica tramite il recupero di un ID AMI Bottlerocket per la regione più vicina. Per esempio:
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
in caso di successo, l’AMI Bottlerocket è disponibile nella regione specificata.
Prepara un ambiente
Innanzitutto, imposta le seguenti variabili di ambiente in una nuova finestra di terminale. Nota: assicurati di sostituire i segnaposto di esempio con i tuoi valori univoci, tra cui il nome del cluster, la regione desiderata, la versione di rilascio di Karpenter
Suggerimento
Alcune variabili (come ${AWS_REGION} e ${K8S_VERSION}) vengono definite all’inizio del blocco e utilizzate nei comandi successivi per garantire coerenza ed evitare ripetizioni. Assicurati di eseguire i comandi in sequenza in modo che questi valori vengano esportati correttamente e siano disponibili per l’utilizzo nelle definizioni successive.
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/')"
Crea i ruoli e le policy richiesti
Karpenter necessita di ruoli e policy IAM specifici (ad esempio, il ruolo IAM del controller Karpenter, il profilo dell’istanza e le policy) per gestire le istanze EC2 come nodi worker Kubernetes. Utilizza questi ruoli per eseguire azioni come l’avvio e l’interruzione delle istanze EC2, l’etichettatura delle risorse e l’interazione con altri servizi AWS. Crea i ruoli e le policy di Karpenter utilizzando 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}"
AWS LBC necessita dell’autorizzazione per fornire e gestire i sistemi di bilanciatore del carico di AWS, ad esempio la creazione di ALB per le risorse Ingress o di NLB per servizi di tipo LoadBalancer. Specificheremo questa policy di autorizzazione durante la creazione del cluster. Durante la creazione del cluster, creeremo l’account di servizio con eksctl in ClusterConfig. Crea la policy IAM 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 il driver CSI Mountpoint di S3 è installato, i relativi pod DaemonSet sono configurati per utilizzare un account di servizio per l’esecuzione. Il driver CSI Mountpoint per Mountpoint S3 richiede l’autorizzazione per interagire con il bucket Amazon S3 che creerai più avanti in questa guida. Specificheremo questa policy di autorizzazione durante la creazione del cluster. Durante la creazione del cluster, creeremo l’account di servizio con eksctl in ClusterConfig. Crea la policy IAM di 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}/*\"]}]}"
Nota: se esiste già un ruolo con questo nome, assegna al ruolo un nome diverso. Il ruolo che creiamo in questo passaggio è specifico per il tuo cluster e il tuo bucket S3.
Creazione del cluster
In questo modello, eksctl crea automaticamente un account di servizio Kubernetes per Pod Identity EKS, l’agente di monitoraggio dei nodi, CoreDNS, Kubeproxy, il plug-in CNI del VPC. Ad oggi, il driver CSI Mountpoint di S3 non è disponibile per Pod Identity EKS, quindi creiamo un IRSA (IAM Roles for Service Account) e un endpoint OIDC. Inoltre, creiamo un account di servizio per il Load Balancer Controller (LBC) di AWS. Per l’accesso ai nodi Bottlerocket, eksctl collega automaticamente AmazonSSMManagedInstanceCore per Bottlerocket per consentire sessioni shell sicure tramite SSM.
Nello stesso terminale in cui imposti le variabili di ambiente, esegui il seguente blocco di comandi per creare il 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
Il processo richiede alcuni minuti. Se desideri monitorare lo stato, consulta la console AWS CloudFormation
2. Verifica dello stato del nodo del cluster e del pod
Eseguiamo alcuni controlli dell’integrità per assicurarci che il cluster sia pronto. Quando il comando precedente viene completato, visualizza i tipi di istanze e verifica che tutti i nodi del sistema CPU abbiano raggiunto lo stato Ready con il comando seguente:
kubectl get nodes -L node.kubernetes.io/instance-type
L’output previsto dovrebbe essere simile al seguente:
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
Verifica tutte le associazioni Pod Identity e il modo in cui associano un ruolo a un account di servizio in un namespace nel cluster con il comando seguente:
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
L’output dovrebbe mostrare i ruoli IAM per Karpenter (“karpenter”) e il LBC di AWS ("aws-load-balancer-controller").
Verifica che i DaemonSet siano disponibili:
kubectl get daemonsets -n kube-system
L’output previsto dovrebbe essere simile al seguente:
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
Verifica che tutti i componenti aggiuntivi siano installati nel cluster:
eksctl get addons --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
L’output previsto dovrebbe essere simile al seguente:
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. Installa Karpenter
Installa il controller Karpenter sui nodi worker della CPU (cpu-worker) per ottimizzare i costi e conservare le risorse della GPU. Lo installeremo nel namespace “kube-system” e specificheremo l’account di servizio “karpenter” che abbiamo definito durante la creazione del cluster. Inoltre, questo comando configura il nome del cluster e una coda di interruzione delle istanze spot per i nodi della CPU. Karpenter utilizzerà IRSA per assumere questo ruolo 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
L’output previsto dovrebbe essere simile al seguente:
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
Verifica che Karpenter sia in esecuzione:
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
L’output previsto dovrebbe essere simile al seguente:
NAME READY STATUS RESTARTS AGE karpenter-555895dc-865bc 1/1 Running 0 5m58s karpenter-555895dc-j7tk9 1/1 Running 0 5m58s
4. Configura NodePools di Karpenter
In questo passaggio, configuriamo NodePools di Karpenterlimits nel NodePool specifico limita le risorse totali massime (ad esempio, CPU, memoria, GPU) che ogni NodePool può consumare su tutti i nodi su cui è stato eseguito il provisioning, impedendo il provisioning aggiuntivo dei nodi se questi limiti vengono superati. NodePools supporta ampie categorie di istanze (ad esempio, c, g), ma la specificazione di tipi di istanze
Configura il NodePool della GPU
In questo NodePool, impostiamo limiti di risorse per gestire il provisioning dei nodi con funzionalità GPU. Questi limiti sono progettati per limitare le risorse totali su tutti i nodi del pool, consentendo un massimo di 10 istanze in totale. Ogni istanza può essere g5.xlarge (4 vCPU, 16 GiB di memoria, 1 GPU) o g5.2xlarge (8 vCPU, 32 GiB di memoria, 1 GPU), purché le vCPU totali non superino 80, la memoria totale non superi 320 GiB e le GPU totali non siano più di 10. Ad esempio, il pool può fornire 10 istanze g5.2xlarge (80 vCPU, 320 GiB, 10 GPU) o 10 istanze g5.xlarge (40 vCPU, 160 GiB, 10 GPU) o un mix, come 5 g5.xlarge e 5 g5.2xlarge (60 vCPU, 240 GiB, 10 GPU), garantendo flessibilità in base alle richieste del carico di lavoro e rispettando al contempo i vincoli delle risorse.
Inoltre, specifichiamo l’ID della variante Nvidia dell’AMI Bottlerocket. Infine, abbiamo stabilito una policy di interruzioneconsolidateAfter: 30m) e abbiamo impostato una durata massima dei nodi di 30 giorni (expireAfter: 720h) per ottimizzare i costi e mantenere lo stato dei nodi per le attività che richiedono un uso intensivo della GPU. Per ulteriori informazioni, consulta Disable Karpenter Consolidation for interruption sensitive workloads e 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
L’output previsto dovrebbe essere simile al seguente:
nodepool.karpenter.sh/gpu-a10g-inference-g5 created ec2nodeclass.karpenter.k8s.aws/gpu-a10g-inference-ec2 created
Verifica che NodePool sia stato creato e sia funzionante:
kubectl get nodepool gpu-a10g-inference-g5 -o yaml
Cerca status.conditions come ValidationSucceeded: True, NodeClassReady: True e Ready: True per confermare che il NodePool sia integro.
Configura il NodePool della CPU
In questo NodePool, abbiamo impostato dei limiti per supportare circa 50 istanze, allineandoci con un carico di lavoro della CPU moderato (ad esempio, 100-200 pod) e quote vCPU AWS tipiche (ad esempio, 128-1152). I limiti vengono calcolati presupponendo che NodePool debba aumentare verticalmente fino a 50 istanze m7.xlarge: CPU (4 vCPU per istanza × 50 istanze = 200 vCPU) e memoria (16 GiB per istanza × 50 istanze = 800 GiB). Questi limiti sono progettati per limitare le risorse totali su tutti i nodi del pool, consentendo fino a 50 istanze da m7g.xlarge (ciascuna con 4 vCPU e 16 GiB di memoria), a condizione che le vCPU totali non superino 200 e la memoria totale non superi 800 GiB.
Inoltre, specifichiamo l’ID della variante standard dell’AMI Bottlerocket. Infine, abbiamo stabilito una policy di interruzioneconsolidateAfter: 60m) e abbiamo impostato una durata massima dei nodi di 30 giorni (expireAfter: 720h) per ottimizzare i costi e mantenere lo stato dei nodi per le attività che richiedono un uso intensivo della GPU. Per ulteriori informazioni, consulta Disable Karpenter Consolidation for interruption sensitive workloads e 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
L’output previsto dovrebbe essere simile al seguente:
nodepool.karpenter.sh/cpu-inference-m7gxlarge created ec2nodeclass.karpenter.k8s.aws/cpu-inference-m7gxlarge-ec2 created
Verifica che NodePool sia stato creato e sia funzionante:
kubectl get nodepool cpu-inference-m7gxlarge -o yaml
Cerca status.conditions come ValidationSucceeded: True, NodeClassReady: True e Ready: True per confermare che il NodePool sia integro.
5. Implementa un Pod GPU per esporre una GPU
È necessario il plug-in per dispositivi di Nvidia per consentire a Kubernetes di esporre i dispositivi GPU al cluster Kubernetes. In genere, è necessario distribuire il plug-in come DaemonSet; tuttavia, l’AMI Bottlerocket preinstalla il plug-in come parte dell’AMI. Ciò significa che, quando si utilizzano le AMI Bottlerocket, non è necessario implementare il plug-in per dispositivi Nvidia DaemonSet. Per saperne di più, consulta Kubernetes Device Plugin to expose GPUs.
Implementa un pod di esempio
Karpenter agisce in modo dinamico: effettua il provisioning dei nodi GPU quando un carico di lavoro (pod) richiede risorse GPU. Per verificare che i pod siano in grado di richiedere e utilizzare le GPU, implementa un pod che richieda la risorsa nvidia.com/gpu nei suoi limiti (ad esempio, nvidia.com/gpu: 1). Per ulteriori informazioni su queste etichette, consulta 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
L’output previsto dovrebbe essere simile al seguente:
pod/gpu-ndivia-smi created
Aspetta un minuto e controlla se il pod è nello stato "Pending", "ContainerCreating", "Running" e poi quindi "Completed":
kubectl get pod gpu-nvidia-smi -w
Verifica che il nodo per il pod appartenga al NodePool della 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"
L’output previsto dovrebbe essere simile al seguente:
Name Nodepool ip-192-168-83-245.ec2.internal gpu-a10g-inference-g5
Controlla i log del pod:
kubectl logs gpu-nvidia-smi
L’output previsto dovrebbe essere simile al seguente:
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. (Facoltativo) Prepara e carica gli artefatti del modello per l’implementazione
In questa fase, implementerai il servizio di un modello per la classificazione delle immagini in tempo reale, iniziando con il caricamento dei pesi dei modelli in un bucket Amazon S3. A titolo dimostrativo, stiamo utilizzando il modello di visione GPUNet-0
Configurazione dell’ambiente
Per scaricare i pesi dei modelli GPUNet-0 in questo passaggio, è necessario accedere al catalogo NGC di NVIDIA e al Docker
-
Crea un account NGC gratuito
e genera una chiave API dalla dashboard NGC (Icona utente > Configurazione > Genera chiave API > Genera chiave personale > Catalogo NGC). -
Scarica e installa la CLI NGC
(Linux/macOS/Windows) e configurala utilizzando: ngc config set. Inserisci la tua chiave API quando richiesto, poi imposta org sunvidiae premi Invio per accettare i valori predefiniti per gli altri. Se l’operazione viene eseguita correttamente, dovresti vedere qualcosa come:Successfully saved NGC configuration to /Users/your-username/.ngc/config.
Verifica le autorizzazioni dell’account di servizio
Prima di iniziare, controlla le autorizzazioni dell’account del servizio Kubernetes:
kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml
Durante la creazione del cluster, abbiamo collegato S3CSIDriverPolicy a un ruolo IAM e annotato l’account del servizio ("s3-csi-driver-sa"). I pod del driver CSI Mountpoint di S3 ereditano le autorizzazioni del ruolo IAM quando interagiscono con S3. L’output previsto dovrebbe essere simile al seguente:
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
Aggiungi una tolleranza
Il driver CSI di S3 funziona come DaemonSet su tutti i nodi. I pod utilizzano il driver CSI su quei nodi per montare i volumi S3. Per consentirgli di programmarsi sui nostri nodi GPU che presentano taint, aggiungi una tolleranza a 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"}}]'
L’output previsto dovrebbe essere simile al seguente:
daemonset.apps/s3-csi-node patched
Carica i pesi dei modelli su S3
In questo passaggio, creerai un bucket Amazon S3, scaricherai i pesi del modello GPUNet-0 da NVIDIA GPU Cloud (NGC) e li caricherai nel bucket. La nostra applicazione accederà a questi pesi in fase di runtime per scopi di inferenza.
Crea un bucket Amazon S3:
aws s3 mb s3://${S3_BUCKET_NAME} --region ${AWS_REGION}
Abilita il controllo delle versioni S3 per il bucket, per evitare che eliminazioni e sovrascritture accidentali causino una perdita di dati immediata e permanente:
aws s3api put-bucket-versioning --bucket ${S3_BUCKET_NAME} --versioning-configuration Status=Enabled
Applica una regola del ciclo di vita al bucket per eliminare le versioni degli oggetti sovrascritte o eliminate 14 giorni dopo che sono diventate non attuali e per rimuovere i marcatori di eliminazione scaduti e i caricamenti incompleti in più parti dopo 7 giorni. Per ulteriori informazioni, consulta Examples of S3 Lifecycle configurations.
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}}]}'
Scarica i pesi del modello GPUNet-0 da NGC. Ad esempio, su macOS:
ngc registry model download-version nvidia/dle/gpunet_0_pyt_ckpt:21.12.0_amp --dest ~/downloads
Nota
Potresti dover modificare questo comando di download per il sistema operativo in uso. Affinché questo comando funzioni su un sistema Linux, probabilmente è necessario creare la directory come parte del comando (ad esempio, mkdir ~/downloads).
L’output previsto dovrebbe essere simile al seguente:
{ "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]" }
Rinomina il file del checkpoint in modo che corrisponda alla denominazione prevista nel nostro codice dell’applicazione nei passaggi successivi (non è necessaria alcuna estrazione, poiché è un checkpoint PyTorch *.pth.tar standard contenente il dizionario dello stato del modello):
mv ~/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp/0.65ms.pth.tar gpunet-0.pth
Abilita il Runtime comune AWS
aws configure set s3.preferred_transfer_client crt
Carica i pesi del modello nel bucket S3:
aws s3 cp gpunet-0.pth s3://${S3_BUCKET_NAME}/gpunet-0.pth
L’output previsto dovrebbe essere simile al seguente:
upload: ./gpunet-0.pth to s3://eks-rt-inference-models-us-east-1-1752722786/gpunet-0.pth
Crea il servizio del modello
In questo passaggio, configurerai un’applicazione web FastAPI per la classificazione delle immagini con accelerazione GPU utilizzando il modello di visione GPUNet-0. L’applicazione scarica i pesi dei modelli da Amazon S3 in fase di runtime, recupera l’architettura del modello dal repository NVIDIA per la memorizzazione nella cache e scarica le etichette delle classi ImageNet tramite HTTP. L’applicazione include la preelaborazione, la trasformazione e l’esposizione di due endpoint: un GET root per il controllo dell’integrità e un endpoint /predict POST che accetta l’URL di un’immagine.
Serviamo il modello utilizzando FastAPI con PyTorch, caricando i pesi da Amazon S3 in fase di runtime in una configurazione containerizzata per la prototipazione rapida e l’implementazione di Kubernetes. Per altri metodi come il batching ottimizzato o i motori ad alto throughput, consulta Serving ML Models.
Creazione dell’applicazione
Crea una directory per i file dell’applicazione, ad esempio model-testing, quindi modifica le directory al suo interno e aggiungi il codice seguente a un nuovo file denominato 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))
Crea un Dockerfile
Il seguente Dockerfile crea un’immagine container per la nostra applicazione utilizzando il modello GPUNet dal repository GitHub di NVIDIA Deep Learning Examples for Tensor Cores
Riduciamo le dimensioni dell’immagine del container utilizzando una base PyTorch solo in fase di runtime, installando solo pacchetti essenziali con pulizia della cache, pre-memorizzando nella cache il codice del modello ed evitando di "caricare" pesi nell’immagine del container per consentire estrazioni e aggiornamenti più rapidi. Per ulteriori informazioni, consulta Reducing Container Image Sizes.
Nella stessa directory di app.py, crea il 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"]
Eseguire il test dell’applicazione
Dalla stessa directory in cui si trovano app.py e Dockerfile, crea l’immagine del container per l’applicazione di inferenza, puntando all’architettura AMD64:
docker build --platform linux/amd64 -t gpunet-inference-app .
Imposta le variabili di ambiente per le tue credenziali AWS e, facoltativamente, un token di sessione AWS. Per esempio:
export AWS_REGION="us-east-1" export AWS_ACCESS_KEY_ID=ABCEXAMPLESCUJFEIELSMUHHAZ export AWS_SECRET_ACCESS_KEY=123EXAMPLEMZREoQXr8XkiicsOgWDQ5TpUsq0/Z
Esegui il container localmente, inserendo le credenziali AWS come variabili di ambiente per l’accesso a S3. Per esempio:
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
L’output previsto dovrebbe essere simile al seguente:
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)
In una nuova finestra del terminale, verifica l’endpoint di inferenza inviando un esempio di richiesta POST con un URL di immagine pubblica come parametro di query:
curl -X POST "http://localhost:8080/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
L’output previsto dovrebbe essere una risposta JSON con le prime 5 previsioni, simile a questa (le etichette e le probabilità effettive possono variare leggermente in base alla precisione dell’immagine e del modello):
{"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}]}
Esci dall’applicazione usando "Ctrl + C".
Invia il container ad Amazon ECR
In questa fase, carichiamo l’immagine del container per il servizio del modello GPUNet-0 su Amazon Elastic Container Registry (ECR), rendendola disponibile per l’implementazione su Amazon EKS. Questo processo prevede la creazione di un nuovo repository ECR per archiviare l’immagine, l’autenticazione con ECR e poi l’etichettatura e l’invio dell’immagine del container nel nostro registro.
Innanzitutto, torna alla directory in cui hai impostato le variabili di ambiente all’inizio di questa guida. Per esempio:
cd ..
Crea un repository in Amazon ECR:
aws ecr create-repository --repository-name gpunet-inference-app --region ${AWS_REGION}
Accedi ad 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
L’output previsto dovrebbe essere simile al seguente:
Login Succeeded
Tagga l’immagine:
docker tag gpunet-inference-app:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Invia l’immagine al tuo repository Amazon ECR:
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Per il completamento di questa fase finale possono essere necessari alcuni minuti.
7. (Facoltativo) Esponi il servizio del modello
In questa fase, esporrai il tuo servizio di modello di inferenza in tempo reale esternamente su Amazon EKS utilizzando il Load Balancer Controller (LBC) di AWS. Questo comporta la configurazione dell’LBC, il montaggio dei pesi del modello da Amazon S3 come volume persistente utilizzando il driver CSI Mountpoint di S3, la distribuzione di un pod applicativo accelerato da GPU, la creazione di un servizio e un ingresso per il provisioning di un Application Load Balancer (ALB) e il test dell’endpoint.
Innanzitutto, verifica l’associazione Pod Identity per LBC di AWS, controllando che l’account del servizio sia collegato correttamente al ruolo IAM richiesto:
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --namespace kube-system --service-account-name aws-load-balancer-controller
L’output previsto dovrebbe essere simile al seguente:
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
Assegna tag a un gruppo di sicurezza del cluster
Il Load Balancer Controller di AWS supporta solo un singolo gruppo di sicurezza con la chiave del tag karpenter.sh/discovery: "${EKS_CLUSTER_NAME}" per la selezione del gruppo di sicurezza di Karpenter. Durante la creazione di un cluster con eksctl, il gruppo di sicurezza del cluster predefinito (che ha il tag "kubernetes.io/cluster/<cluster-name>: owned") non viene automaticamente etichettato con tag karpenter.sh/discovery. Questo tag è essenziale per permettere a Karpenter di scoprire e collegare questo gruppo di sicurezza ai nodi che fornisce. Il collegamento di questo gruppo di sicurezza garantisce la compatibilità con Load Balancer Controller (LBC) di AWS, consentendogli di gestire automaticamente le regole del traffico in entrata per i servizi esposti tramite Ingress, come il servizio del modello in questi passaggi.
Individua l’ID VPC del cluster:
CLUSTER_VPC_ID="$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query cluster.resourcesVpcConfig.vpcId --output text)"
Esporta il gruppo di sicurezza predefinito per il 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)"
Aggiungi il tag karpenter.sh/discovery al gruppo di sicurezza del cluster predefinito. Questo consentirà ai nostri selettori EC2NodeClass per CPU e GPU di utilizzarlo:
aws ec2 create-tags --resources ${CLUSTER_SG_ID} --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}
Verifica che il tag sia stato aggiunto:
aws ec2 describe-security-groups --group-ids ${CLUSTER_SG_ID} --query "SecurityGroups[].Tags"
Tra i risultati, dovresti vedere quanto segue con il tag e il nome del cluster. Per esempio:
{ "Key": "karpenter.sh/discovery", "Value": "eks-rt-inference-us-east-1" }
Configura il Load Balancer Controller (LBC) di AWS
LBC di AWS è essenziale per gestire il traffico in ingresso verso i carichi di lavoro di IA/ML su Amazon EKS, garantendo l’accesso agli endpoint di inferenza o alle pipeline di elaborazione dei dati. Integrandosi con AWS Application Load Balancer (ALB) e Network Load Balancer (NLB), LBC indirizza dinamicamente il traffico verso applicazioni containerizzate, come quelle che eseguono modelli linguistici di grandi dimensioni, modelli di visione artificiale o servizi di inferenza in tempo reale. Poiché abbiamo già creato l’account del servizio e la Pod Identity Association durante la creazione del cluster, impostiamo serviceAccount.name in modo che corrisponda a quanto definito nella nostra configurazione del cluster (aws-load-balancer-controller).
Aggiungi il repository del grafico Helm eks-charts di proprietà di AWS:
helm repo add eks https://aws.github.io/eks-charts
Aggiorna i tuoi repository Helm locali con i grafici più recenti:
helm repo update eks
Implementa AWS LBC utilizzando Helm, specificando il nome del cluster EKS e facendo riferimento all’account di servizio precreato:
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
L’output previsto dovrebbe essere simile al seguente:
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!
Monta il modello in un volume persistente
In questo passaggio, monterai i pesi dei modelli dal tuo bucket Amazon S3 utilizzando un PersistentVolume (PV) supportato dal driver CSI Mountpoint per Amazon S3. Questo consente ai pod Kubernetes di accedere agli oggetti S3 come file locali, eliminando i download a uso intensivo di risorse verso container di archiviazione temporanei su pod o container init, ideali per modelli di pesi di grandi dimensioni da più gigabyte.
Il PV monta l’intera root del bucket (nessun percorso specificato in volumeAttributes), supporta l’accesso simultaneo in sola lettura da più pod ed espone file come il peso dei modelli (/models/gpunet-0.pth) all’interno del container per l’inferenza. Ciò garantisce che il fallback "download" nella nostra applicazione (app.py) non si attivi perché il file esiste tramite il mount. Disaccoppiando il modello dall’immagine del container, viene consentito l’accesso condiviso e gli aggiornamenti indipendenti delle versioni del modello senza ricostruire l’immagine.
Crea il PersistentVolume (PV)
Crea una risorsa PersistentVolume (PV) per montare il bucket S3 contenente i pesi del modello, abilitando l’accesso in sola lettura a più pod senza scaricare file in fase di 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
Crea il PersistentVolumeClaim (PVC)
Crea un PersistentVolumeClaim (PVC) da associare al PV, richiedendo l’accesso in sola lettura ai dati del modello S3 montato:
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
Distribuzione dell’applicazione
Implementa l’applicazione di inferenza come implementazione Kubernetes, montando il volume persistente supportato da S3 per l’accesso al modello, applicando i selettori e le tolleranze dei nodi GPU e impostando le variabili di ambiente per il percorso del modello. Questa implementazione imposta il percorso del modello (var amb di "/models/gpunet-0.pth"), quindi la nostra applicazione (in app.py) utilizzerà questo percorso per impostazione predefinita. Con il montaggio del volume di implementazione su /models (sola lettura), il download del modello non si attiva se il file è già presente tramite il 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
Karpenter impiegherà alcuni minuti per eseguire il provisioning di un nodo GPU se non è già disponibile. Verifica che il pod di inferenza sia nello stato "Running":
kubectl get pods -l app=gpunet-inference-app
L’output previsto dovrebbe essere simile al seguente:
NAME READY STATUS RESTARTS AGE gpunet-inference-app-5d4b6c7f8-abcde 1/1 Running 0 2m
Esponi il servizio con Ingress e bilanciatore del carico
Crea un servizio ClusterIP per esporre l’implementazione dell’inferenza internamente nel cluster EKS, mirando alla porta dell’applicazione:
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
Crea una risorsa Ingress per il provisioning di un Application Load Balancer (ALB) con accesso a Internet tramite AWS LBC, indirizzando il traffico esterno al servizio di inferenza:
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
Application Load Balancer (ALB) completerà il provisioning nell’arco di alcuni minuti. Monitora lo stato delle risorse Ingress per confermare che sia stato eseguito il provisioning dell’ALB:
kubectl get ingress gpunet-model-ingress
L’output previsto dovrebbe essere simile al seguente (con il campo ADDRESS compilato):
NAME CLASS HOSTS ADDRESS PORTS AGE gpunet-model-ingress alb * k8s-default-gpunetmo-183de3f819-516310036.us-east-1.elb.amazonaws.com 80 6m58s
Estrai ed esporta il nome host dell’ALB dallo stato Ingress per utilizzarlo nei test successivi:
export ALB_HOSTNAME=$(kubectl get ingress gpunet-model-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
Prova il servizio del modello
Convalida l’endpoint di inferenza esposto inviando una richiesta POST con un URL di immagine di esempio (ad esempio, dal set di dati COCO), simulando la previsione in tempo reale:
curl -X POST "http://${ALB_HOSTNAME}/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
L’output previsto dovrebbe essere una risposta JSON con le prime 5 previsioni, simile a questa (le etichette e le probabilità effettive possono variare leggermente in base alla precisione dell’immagine e del modello):
{"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}]}
Facoltativamente, puoi continuare a testare altre immagini in una nuova richiesta POST. Per esempio:
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
Conclusioni
In questa guida, configurerai un cluster Amazon EKS ottimizzato per carichi di lavoro di inferenza in tempo reale accelerati da GPU. Hai effettuato il provisioning di un cluster con istanze G5 EC2
Eliminazione
Per evitare di incorrere in addebiti futuri, è necessario eliminare manualmente lo stack CloudFormation associato per eliminare tutte le risorse create durante questa guida, inclusa la rete VPC.
Elimina lo stack CloudFormation usando il flag --wait con eksctl:
eksctl delete cluster --region ${AWS_REGION} --name ${EKS_CLUSTER_NAME} --wait
Al momento del completamento, dovresti vedere il seguente messaggio di risposta:
2025-07-29 13:03:55 [✔] all cluster resources were deleted
Elimina il bucket Amazon S3 creato durante questa guida utilizzando la console Amazon S3