Ayude a mejorar esta página
Para contribuir a esta guía del usuario, elija el enlace Edit this page on GitHub que se encuentra en el panel derecho de cada página.
Guía de prácticas recomendadas para la configuración de clústeres para la inferencia en tiempo real en Amazon EKS
Introducción
Esta guía ofrece un tutorial práctico para configurar un clúster de Amazon Elastic Kubernetes Service (EKS) optimizado para cargas de trabajo de inferencia en línea en tiempo real, e incorpora las prácticas recomendadas seleccionadas por expertos de todo el equipo de AWS. Utiliza una sofisticada arquitectura Quickstart de EKS: un conjunto seleccionado de controladores, tipos de instancias y configuraciones alineados con las prácticas recomendadas de AWS en materia de modelos, aceleradores y escalado. Este enfoque le ayuda a evitar la tarea de seleccionar la configuración del clúster, lo que le permite poner en marcha rápidamente un clúster funcional y preconfigurado. A lo largo del camino, implementaremos ejemplos de cargas de trabajo para validar su configuración, explicaremos los conceptos clave sobre arquitectura (como desvincular las tareas vinculadas a la CPU de los cálculos con uso intensivo de la GPU), abordaremos preguntas comunes (por ejemplo, ¿por qué elegir la AMI de Bottlerocket en lugar de AL2023?) y describiremos los próximos pasos para ampliar las capacidades de su clúster.
Esta guía fue diseñada específicamente para ingenieros de machine learning (ML) e inteligencia artificial (IA), administradores de plataformas, operadores y especialistas en datos/IA que son nuevos en el ecosistema de AWS y EKS, por lo que se asume que están familiarizados con Kubernetes, pero que no tienen experiencia previa en EKS. Está diseñada para ayudarle a comprender los pasos y procesos necesarios para poner en marcha las cargas de trabajo de inferencia en línea en tiempo real. La guía muestra los aspectos básicos para crear un clúster de inferencia de un solo nodo, como el aprovisionamiento de los recursos de la GPU, la integración del almacenamiento para los artefactos del modelo, la habilitación del acceso seguro a los servicios de AWS y la exposición de los puntos de conexión de inferencia. En todo momento, se hace hincapié en el diseño resiliente y de baja latencia para aplicaciones orientadas al usuario, como la detección de fraudes, los chatbots en tiempo real y el análisis de opiniones de los clientes.
En esta guía, nos centramos exclusivamente en establecer un punto de partida fundamental y prescriptivo mediante instancias G5 de EC2. Si busca configuraciones de clústeres de AWS específicas para inferencia o flujos de trabajo integrales, consulte Uso de instancias de AWS Inferentia con Amazon EKS para machine learning o nuestros talleres en Recursos para empezar a utilizar la IA/ML en Amazon EKS.
Antes de empezar
Antes de comenzar, asegúrese de haber realizado las siguientes tareas:
Arquitectura
La inferencia en línea en tiempo real se refiere al proceso de utilizar un modelo de machine learning entrenado para generar predicciones o resultados sobre los flujos de datos entrantes con una latencia mínima. Por ejemplo, permite la detección de fraude en tiempo real, la clasificación de imágenes o la generación de gráficos de conocimiento en respuesta a las entradas de los usuarios. La arquitectura de un sistema de inferencia en línea en tiempo real ofrece predicciones de machine learning de baja latencia en aplicaciones orientadas al usuario al desvincular la gestión del tráfico web vinculado a la CPU de los cálculos de IA que utilizan mucha GPU. Por lo general, este proceso se encuentra dentro de un ecosistema de aplicaciones más amplio y, a menudo, incluye servicios de backend, frontend, vectores y modelos, con un enfoque en componentes especializados para permitir el escalado independiente, el desarrollo paralelo y la resiliencia ante los fallos. El aislamiento de las tareas de inferencia en un hardware de GPU dedicado y el aprovechamiento de interfaces como las API y los WebSockets garantizan una alta simultaneidad, un procesamiento rápido de modelos como los transformadores y las interacciones de los usuarios a través del frontend. Tenga en cuenta que, si bien las bases de datos vectoriales y los canales de generación aumentada por recuperación (RAG) suelen desempeñar un papel importante en los sistemas de inferencia en tiempo real, esta guía no contiene información sobre ellos. Como mínimo, una arquitectura típica suele incluir lo siguiente:
-
Servicio de frontend: sirve como interfaz orientada al usuario, gestiona la lógica del lado del cliente, reproduce contenido dinámico y facilita las interacciones en tiempo real. Se comunica con el servicio de backend para iniciar solicitudes de inferencia y mostrar los resultados, a menudo iniciando solicitudes al servicio de backend, que utiliza WebSockets para transmitir actualizaciones o API para el intercambio de datos estructurados. Por lo general, este servicio no requiere un equilibrador de carga dedicado, ya que puede alojarse en redes de entrega de contenido (CDN), como AWS CloudFront, para activos estáticos o servirse directamente desde servidores web, y el escalado se gestiona mediante grupos de escalado automático si es necesario para el contenido dinámico.
-
Servicio de backend: actúa como el orquestador de la aplicación y gestiona la lógica empresarial, como la autenticación de usuarios, la validación de datos y la coordinación de servicios (por ejemplo, mediante API para puntos de conexión RESTful o WebSockets para conexiones persistentes). Se comunica con el servicio de inferencia, escala de forma independiente en CPU multinúcleo y RAM para gestionar un tráfico web elevado sin depender de las GPU y, a menudo, requiere un equilibrador de carga (como el equilibrador de carga de aplicación o el equilibrador de carga de red de AWS) para distribuir las solicitudes entrantes entre varias instancias, especialmente en situaciones de alta concurrencia. Un controlador de ingreso puede administrar aún más las reglas de acceso y enrutamiento externas para mejorar la seguridad y la configuración del tráfico.
-
Servicio de inferencia: sirve como núcleo para los cálculos de IA y se ejecuta en GPU con suficiente VRAM (por ejemplo, de 8 a 12 GB para modelos como DistilBERT) para realizar incrustaciones de vectores, extracción de conocimientos e inferencia del modelo (por ejemplo, expuestos a través de API para solicitudes por lotes o WebSockets para transmisión en tiempo real) mediante modelos personalizados o de código abierto. Este aislamiento evita los conflictos de dependencias, permite actualizar el modelo sin tiempo de inactividad y permite el escalado horizontal con equilibrio de carga para múltiples solicitudes simultáneas. Para dar a conocer el servicio del modelo de forma eficaz, normalmente se basa en un equilibrador de carga para distribuir las cargas de trabajo vinculadas a la GPU entre las instancias replicadas, mientras que un recurso o controlador de entrada (como el controlador de Ingress del ALB en AWS) gestiona el enrutamiento externo, la terminación de SSL y el reenvío basado en rutas para garantizar un acceso seguro y eficiente sin sobrecargar las GPU individuales.
Descripción de la solución
Los sistemas de inferencia en línea en tiempo real requieren una arquitectura resiliente y de alto rendimiento que pueda ofrecer una latencia ultrabaja y, al mismo tiempo, gestionar ráfagas de tráfico impredecibles y de gran volumen. Esta descripción general de la solución explica cómo funcionan juntos los siguientes componentes de AWS en el clúster de Amazon EKS que crearemos para garantizar que nuestro clúster pueda alojar y administrar modelos de machine learning que proporcionen predicciones inmediatas sobre datos en tiempo real con un retraso mínimo para los usuarios finales.
-
Instancias G5 de Amazon EC2
: para las tareas de inferencia con uso intensivo de la GPU, utilizamos los tipos de instancias G5 de EC2 g5.xlarge y g5.2xlarge, que cuentan con una (1) sola GPU NVIDIA A10G con 24 GB de memoria (por ejemplo, 8 mil millones de parámetros en FP16). Basadas en la arquitectura Ampere de NVIDIA, estas GPU funcionan con GPU NVIDIA A10G Tensor Core y procesadores AMD EPYC de segunda generación, admiten de 4 a 8 vCPU, un ancho de banda de la red de hasta 10 Gbps y entre 250 y 450 GB de almacenamiento SSD NVMe local, lo que garantiza un movimiento de datos y una potencia de cálculo rápidos para modelos complejos, lo que las hace ideales para tareas de inferencia de baja latencia y alto rendimiento. La elección de un tipo de instancia de EC2 depende de la aplicación y del modelo (por ejemplo, modelo de imagen, video o texto) y de sus requisitos de latencia y rendimiento. Por ejemplo, si utiliza un modelo de imagen o video, es posible que desee utilizar instancias P5 de EC2 para obtener una latencia óptima en tiempo real. Recomendamos empezar con las instancias G5 de EC2 , ya que son un buen punto de partida para ponerlas en marcha rápidamente y, después, evaluar si son las más adecuadas para sus cargas de trabajo mediante pruebas comparativas de rendimiento. Para casos más avanzados, considere las instancias G6 de EC2 . -
Instancias M7g de Amazon EC2
: para tareas que requieren un uso intensivo de la CPU, como el preprocesamiento de datos, la gestión de solicitudes de API, el alojamiento del controlador de Karpenter, los complementos y otros componentes del sistema, utilizamos el tipo de instancia M7g m5.xlarge de EC2. Las instancias M7g son instancias basadas en ARM que cuentan con 4 vCPU, 16 GB de memoria y un ancho de banda de red de hasta 12,5 Gbps, y funcionan con procesadores Graviton3 de AWS. La elección de un tipo de instancia de EC2 depende de la aplicación y de los requisitos de cómputos, memoria y escalabilidad de la carga de trabajo. Para las cargas de trabajo optimizadas para cálculos, puede considerar las instancias C7 de EC2g , que también utilizan procesadores Graviton3, pero están optimizadas para ofrecer un rendimiento de cómputo superior al de las instancias M7g en determinados casos de uso. Como alternativa, las instancias C8g de EC2 más recientes (cuando estén disponibles) ofrecen un rendimiento de cómputo hasta un 30 % mejor que las instancias C7g. Recomendamos empezar con las instancias M7g de EC2 por su rentabilidad y compatibilidad con una amplia gama de cargas de trabajo (p. ej., servidores de aplicaciones, microservicios, servidores de juegos o almacenes de datos de tamaño medio) y, después, evaluar si son las adecuadas para sus cargas de trabajo mediante pruebas comparativas de rendimiento. -
Controlador de CSI Mountpoint de Amazon S3: para las cargas de trabajo en instancias de una sola GPU en las que varios pods comparten una GPU (por ejemplo, varios pods programados en el mismo nodo para utilizar los recursos de la GPU), utilizamos el controlador de CSI Mountpoint de S3 para optimizar el uso de la memoria, algo esencial para tareas como la inferencia de modelos grandes en configuraciones de baja complejidad y con poco margen de costos. Expone los buckets de Amazon S3 como un sistema de archivos tipo POSIX disponible para el clúster de Kubernetes, que permite a los pods de inferencia leer los artefactos del modelo (por ejemplo, los pesos del modelo) directamente en la memoria sin tener que descargarlos primero e introducir conjuntos de datos mediante operaciones de archivo estándar. Además, S3 tiene una capacidad de almacenamiento prácticamente ilimitada y acelera las cargas de trabajo de inferencia con un uso intensivo de datos. La elección de un controlador de CSI de almacenamiento depende de la aplicación y de los requisitos de rendimiento y latencia de la carga de trabajo. Si bien el controlador de CSI FSx para OpenZFS ofrece una latencia inferior a un milisegundo para E/S aleatorias o volúmenes persistentes compartidos totalmente compatibles con POSIX en todos los nodos, recomendamos empezar con el controlador de CSI Mountpoint de S3 debido a su escalabilidad, menores costos para conjuntos de datos de gran tamaño e integración incluida con el almacenamiento de objetos administrado por S3 para patrones de inferencia de lectura intensa (por ejemplo, entradas de modelos de transmisión) y, a continuación, evaluar si es el adecuado para sus cargas de trabajo mediante pruebas comparativas de rendimiento.
-
Agente de EKS Pod Identity: para permitir el acceso a los servicios de AWS, utilizamos el agente de EKS Pod Identity, que utiliza una única entidad principal de servicio y facilita las asociaciones de roles de IAM a nivel del pod dentro del clúster de Amazon EKS. EKS Pod Identity ofrece una alternativa simplificada al enfoque tradicional de roles de IAM para cuentas de servicio (IRSA), ya que utiliza una única entidad principal de servicio (pods.eks.amazonaws.com) en lugar de depender de proveedores de OIDC individuales para cada clúster, lo que facilita la asignación de permisos. Además, permite reutilizar los roles en varios clústeres y es compatible con características avanzadas, como las etiquetas de sesión de los roles de IAM y los roles de IAM de destino.
-
EKS Node Monitoring Agent: para garantizar la disponibilidad y fiabilidad continuas de los servicios de inferencia, utilizamos EKS Node Monitoring Agent con reparación automática, que detecta y reemplaza automáticamente los nodos en mal estado, lo que minimiza el tiempo de inactividad. Supervisa continuamente los nodos para detectar problemas de hardware, kernel, redes y almacenamiento mediante comprobaciones de estado mejoradas (por ejemplo, KernelReady o NetworkingReady). En el caso de los nodos de GPU, detecta los fallos específicos del acelerador e inicia una reparación adecuada acordonando los nodos en mal estado, esperando 10 minutos a que se resuelvan los problemas transitorios de la GPU y sustituyendo los nodos después de 30 minutos en caso de fallos persistentes.
-
AMI de Bottlerocket: para proporcionar una base de seguridad reforzada para nuestro clúster de EKS, utilizamos la AMI de Bottlerocket, que incluye solo los componentes esenciales necesarios para ejecutar los contenedores y ofrece tiempos de arranque mínimos para un escalado rápido. La elección de una AMI de nodo es específica de la aplicación y depende de los requisitos de personalización, seguridad y escalabilidad de la carga de trabajo. Si bien la AMI de AL2023 ofrece una mayor flexibilidad para las instalaciones y personalizaciones a nivel del host (por ejemplo, especificar un directorio de caché dedicado en un PV/PVC sin configuraciones de nodos adicionales), recomendamos empezar con la AMI de Bottlerocket, por su tamaño más reducido y su optimización integrada para cargas de trabajo en contenedores (por ejemplo, microservicios, servidores de inferencia, API escalables), y luego evaluar si es la adecuada para sus cargas de trabajo mediante pruebas comparativas de rendimiento.
-
Controlador del equilibrador de carga (LBC) de AWS: para exponer los puntos de conexión de inferencia en tiempo real, utilizamos el controlador del equilibrador de carga de AWS, que aprovisiona y administra automáticamente los equilibradores de carga de aplicación (ALB) para el tráfico HTTP/HTTPS y los equilibradores de carga de red (NLB) para el tráfico TCP/UDP en función de los recursos de Ingress y de servicio de Kubernetes, lo que permite la integración de modelos de inferencia con clientes externos. Además, admite características como el enrutamiento basado en rutas para distribuir las solicitudes de inferencia entre varios pods o nodos, lo que garantiza la escalabilidad durante los picos de tráfico y minimiza la latencia mediante optimizaciones nativas de AWS, como la multiplexación de conexiones y las comprobaciones de estado.
1. Creación de su clúster de EKS
En este paso, creamos un clúster con nodos de CPU y un grupo de nodos administrados mediante una plantilla eksctl ClusterConfig
De forma predeterminada, eksctl
creará una VPC dedicada para el clúster con un bloque de CIDR de 192.168.0.0/16
. La VPC incluye tres subredes públicas y tres subredes privadas, cada una distribuida en tres zonas de disponibilidad diferentes (o dos zonas de disponibilidad en la región us-east-1
), lo que constituye el método ideal para implementar cargas de trabajo de Kubernetes. La plantilla también implementa una puerta de enlace de Internet, que proporciona acceso a Internet a las subredes públicas a través de las rutas predeterminadas en sus tablas de enrutamiento y una sola puerta de enlace NAT en una de las subredes públicas, con rutas predeterminadas en las tablas de enrutamiento de las subredes privadas que dirigen el tráfico saliente a través de la puerta de enlace NAT para el acceso a Internet. Para obtener más información sobre esta configuración, consulte Deploy Nodes to Private Subnets.
Comprobación de sus credenciales
Compruebe si sus credenciales de la CLI de AWS son válidas y pueden autenticarse con los servicios de AWS:
aws sts get-caller-identity
Si se ejecuta correctamente, la CLI devolverá los detalles sobre su identidad de AWS (ID de usuario, cuenta y ARN).
Comprobación de la disponibilidad de la instancia
Los tipos de instancia G5 no están disponibles en todas las regiones. Compruebe la región más cercana. Por ejemplo:
aws ec2 describe-instance-types --instance-types g5.xlarge g5.2xlarge --region us-east-1
Si se ejecuta correctamente, el tipo de instancia G5 estará disponible en la región que especificó.
La AMI de Bottlerocket no está disponible en todas las regiones. Compruébelo recuperando un ID de la AMI de Bottlerocket para su región más cercana. Por ejemplo:
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
Si se ejecuta correctamente, la AMI de Bottlerocket estará disponible en la región que especificó.
Preparación del entorno
Primero, configure las siguientes variables de entorno en una nueva ventana de terminal. Nota: Asegúrese de sustituir los marcadores de posición de ejemplo por sus valores únicos, como el nombre del clúster, la región que desee, la versión de lanzamiento de Karpenter
sugerencia
Algunas variables (como ${AWS_REGION}
y ${K8S_VERSION}
) se definen al principio del bloque y luego se hace referencia a ellas en comandos posteriores para mantener la coherencia y evitar la repetición. Asegúrese de ejecutar los comandos en secuencia para que estos valores se exporten correctamente y estén disponibles para su uso en definiciones posteriores.
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/')"
Creación de los roles y las políticas necesarios
Karpenter necesita roles y políticas de IAM específicos (por ejemplo, el rol de IAM del controlador de Karpenter, el perfil de instancia y las políticas) para administrar las instancias de EC2 como nodos de trabajo de Kubernetes. Utiliza estos roles para realizar acciones como lanzar y terminar instancias de EC2, etiquetar recursos e interactuar con otros servicios de AWS. Cree los roles y las políticas de Karpenter utilizando el archivo 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}"
El LBC de AWS necesita permiso para aprovisionar y administrar equilibradores de carga de AWS, por ejemplo, la creación de ALB para los recursos de Ingress o NLB para servicios del tipo LoadBalancer
. Especificaremos esta política de permisos durante la creación del clúster. Durante la creación del clúster, crearemos la cuenta de servicio con eksctl en ClusterConfig. Creación de la política de IAM de 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)"
Cuando se instala el controlador de CSI Mountpoint de S3, se configuran sus pods DaemOnset para usar una cuenta de servicio para la ejecución. El Mountpoint para el controlador de CSI Mountpoint de S3 necesita permiso para interactuar con el bucket de Amazon S3 que creemos más adelante en esta guía. Especificaremos esta política de permisos durante la creación del clúster. Durante la creación del clúster, crearemos la cuenta de servicio con eksctl en ClusterConfig. Creación de la política de IAM de 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: Si ya existe un rol con este nombre, asígnele uno diferente. El rol que creamos en este paso es específico para su clúster y su bucket de S3.
Cree el clúster
En esta plantilla, eksctl crea automáticamente una cuenta de servicio de Kubernetes para EKS Pod Identity, Node Monitoring Agent, CoreDNS, Kubeproxy y el complemento CNI de la VPC. A día de hoy, el controlador de CSI Mountpoint de S3 no está disponible para EKS Pod Identity, por lo que hemos creado roles de IAM para cuenta de servicios (IRSA) y un punto de conexión de OIDC. Además, creamos una cuenta de servicio para el controlador del equilibrador de carga (LBC) de AWS. Para acceder a los nodos de Bottlerocket, eksctl adjunta automáticamente AmazonSSMManagedInstanceCore a Bottlerocket para permitir sesiones de shell seguras a través de SSM.
En la misma terminal en la que configuró las variables de entorno, ejecute el siguiente bloque de comandos para crear el clúster:
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
Este proceso tarda varios minutos en completarse. Si desea supervisar el estado, consulte la consola de AWS CloudFormation
2. Comprobación del estado del nodo del clúster y del pod
Realicemos algunas comprobaciones de estado para asegurarnos de que el clúster esté listo. Cuando se complete el comando anterior, observe los tipos de instancia y verifique, con el siguiente comando, que los nodos del sistema de CPU tengan el estado Ready
:
kubectl get nodes -L node.kubernetes.io/instance-type
El resultado esperado debe tener el siguiente aspecto:
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
Compruebe, con el siguiente comando, todas las asociaciones de Pod Identity y de qué manera se asigna un rol a una cuenta de servicio en un espacio de nombres del clúster:
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
El resultado debería mostrar los roles de IAM para Karpenter (“karpenter”) y el LBC de AWS (“aws-load-balancer-controller”).
Compruebe que los DaemonSets estén disponibles:
kubectl get daemonsets -n kube-system
El resultado esperado debe tener el siguiente aspecto:
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
Compruebe que todos los complementos estén instalados en el clúster:
eksctl get addons --cluster ${EKS_CLUSTER_NAME} --region ${AWS_REGION}
El resultado esperado debe tener el siguiente aspecto:
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. Instalación de Karpenter
Instale el controlador de Karpenter en los nodos de trabajo de la CPU (cpu-worker
) para optimizar los costos y conservar los recursos de la GPU. Lo instalaremos en el espacio de nombres “kube-system” y especificaremos la cuenta de servicio “karpenter” que definimos durante la creación del clúster. Además, este comando configura el nombre del clúster y una cola de interrupción de instancias de spot para los nodos de la CPU. Karpenter utilizará IRSA para asumir este rol de 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
El resultado esperado debe tener el siguiente aspecto:
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
Compruebe que Karpenter se esté ejecutando:
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
El resultado esperado debe tener el siguiente aspecto:
NAME READY STATUS RESTARTS AGE karpenter-555895dc-865bc 1/1 Running 0 5m58s karpenter-555895dc-j7tk9 1/1 Running 0 5m58s
4. Configuración de NodePools de Karpenter
En este paso, configuramos los NodePools de Karpenterlimits
de la especificación de NodePool restringe los recursos totales máximos (p. ej., CPU, memoria o GPU) que cada NodePool puede consumir en todos los nodos aprovisionados, lo que impide el aprovisionamiento de nodos adicionales si se superan estos límites. Si bien los NodePools admiten categorías de instancias amplias (por ejemplo, c
, g
), especificar tipos de instancias
Configuración del NodePool de GPU
En este NodePool, establecemos límites de recursos para administrar el aprovisionamiento de nodos con capacidades de GPU. Estos límites están diseñados para restringir los recursos totales en todos los nodos del grupo, por lo que se permite un máximo de 10 instancias en total. Cada instancia puede ser g5.xlarge (4 vCPU, 16 GiB de memoria, 1 GPU) o g5.2xlarge (8 vCPU, 32 GiB de memoria, 1 GPU), siempre que el total de vCPU no supere las 80, la memoria total no supere los 320 GiB y el total de GPU no supere las 10. Por ejemplo, el grupo puede aprovisionar 10 instancias g5.2xlarge (80 vCPU, 320 GiB, 10 GPU) o 10 instancias g5.xlarge (40 vCPU, 160 GiB, 10 GPU), o una combinación como, por ejemplo, 5 g5.xlarge y 5 g5.2xlarge (60 vCPU, 240 GiB, 10 GPU). Así, se garantiza la flexibilidad en función de las demandas de carga de trabajo y se respetan las limitaciones de recursos.
Además, especificamos el ID de la variante Nvidia de la AMI de Bottlerocket. Por último, establecemos una política de interrupcionesconsolidateAfter: 30m
) y establecemos una vida útil máxima de los nodos de 30 días (expireAfter: 720h
) para optimizar los costos y mantener el estado de los nodos para las tareas que requieren un uso intensivo de la GPU. Para obtener más información, consulte Disable Karpenter Consolidation for interruption sensitive workloads y 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
El resultado esperado debe tener el siguiente aspecto:
nodepool.karpenter.sh/gpu-a10g-inference-g5 created ec2nodeclass.karpenter.k8s.aws/gpu-a10g-inference-ec2 created
Compruebe que el NodePool esté creado y en buen estado:
kubectl get nodepool gpu-a10g-inference-g5 -o yaml
Busque status.conditions
como ValidationSucceeded: True
, NodeClassReady: True
, y Ready: True
para confirmar que el NodePool esté en buen estado.
Configuración del NodePool de la CPU
En este NodePool, establecemos límites para admitir aproximadamente 50 instancias, ajustándonos a una carga de trabajo de CPU moderada (por ejemplo, de 100 a 200 pods) y a las cuotas de vCPU de AWS típicas (por ejemplo, de 128 a 1152). Los límites se calculan suponiendo que NodePool debe escalar verticalmente hasta 50 instancias m7.xlarge: CPU (4 vCPU por instancia × 50 instancias = 200 vCPU) y memoria (16 GiB por instancia × 50 instancias = 800 GiB). Estos límites están diseñados para restringir los recursos totales en todos los nodos del grupo, por lo que se permiten hasta 50 instancias m7g.xlarge (cada una con 4 vCPU y 16 GiB de memoria), siempre que el total de vCPU no supere las 200 y la memoria total no supere los 800 GiB.
Además, especificamos el ID de la variante estándar de la AMI de Bottlerocket. Por último, establecemos una política de interrupcionesconsolidateAfter: 60m
) y establecemos una vida útil máxima de los nodos de 30 días (expireAfter: 720h
) para optimizar los costos y mantener el estado de los nodos para las tareas que requieren un uso intensivo de la GPU. Para obtener más información, consulte Disable Karpenter Consolidation for interruption sensitive workloads y 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
El resultado esperado debe tener el siguiente aspecto:
nodepool.karpenter.sh/cpu-inference-m7gxlarge created ec2nodeclass.karpenter.k8s.aws/cpu-inference-m7gxlarge-ec2 created
Compruebe que el NodePool esté creado y en buen estado:
kubectl get nodepool cpu-inference-m7gxlarge -o yaml
Busque status.conditions
como ValidationSucceeded: True
, NodeClassReady: True
, y Ready: True
para confirmar que el NodePool esté en buen estado.
5. Implementación de un pod de GPU para exponer una GPU
Necesita el complemento del dispositivo de Nvidia para que Kubernetes pueda exponer los dispositivos de GPU al clúster de Kubernetes. Normalmente, tendría que implementar el complemento como un DaemOnset; sin embargo, la AMI de Bottlerocket preinstala el complemento como parte de la AMI. Esto significa que, cuando se utilizan las AMI de Bottlerocket, no es necesario implementar el complemento DaemonSet del dispositivo Nvidia. Para obtener más información, consulte Kubernetes Device Plugin to expose GPUs.
Implementación de un pod de ejemplo
Karpenter actúa de forma dinámica: aprovisiona los nodos de la GPU cuando una carga de trabajo (pod) solicita recursos de la GPU. Para comprobar que los pods pueden solicitar y usar GPU, implemente un pod que solicite el recurso nvidia.com/gpu
dentro de sus límites (por ejemplo, nvidia.com/gpu: 1
). Para obtener más información sobre estas etiquetas, consulte 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
El resultado esperado debe tener el siguiente aspecto:
pod/gpu-ndivia-smi created
Espere un minuto y compruebe si el pod tiene los estados “Pendiente”, “Creando un contenedor”, “En ejecución” y, luego, “Completado”.
kubectl get pod gpu-nvidia-smi -w
Verifique que el nodo del pod pertenezca al NodePool de la 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"
El resultado esperado debe tener el siguiente aspecto:
Name Nodepool ip-192-168-83-245.ec2.internal gpu-a10g-inference-g5
Compruebe los registros del pod:
kubectl logs gpu-nvidia-smi
El resultado esperado debe tener el siguiente aspecto:
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) Preparación y carga de los artefactos del modelo para su implementación
En este paso, implementará un servicio de modelo para la clasificación de imágenes en tiempo real, empezando por cargar los pesos de los modelos en un bucket de Amazon S3. A modo de demostración, utilizamos el modelo de visión GPUnet-0
Configure su entorno
Para descargar los pesos del modelo GPUnet-0, en este paso, necesitará acceder al catálogo NGC de NVIDIA y al Docker
-
Cree una cuenta gratuita de NGC
y genere una clave de API desde el panel de control de NGC (Ícono de usuario > Configuración > Generar clave de API > Generar clave personal > Catálogo de NGC). -
Descargue e instale la CLI de NGC
(Linux/macOS/Windows) y configure la CLI mediante ngc config set
. Introduzca su clave de API cuando se le pida; defina org comonvidia
y pulse Aceptar para aceptar los valores predeterminados de los demás. Si se ejecuta con éxito, debería verse similar aSuccessfully saved NGC configuration to /Users/your-username/.ngc/config
.
Verificación de los permisos de cuentas de servicio
Antes de empezar, compruebe los permisos de la cuenta de servicio de Kubernetes:
kubectl get serviceaccount s3-csi-driver-sa -n kube-system -o yaml
Durante la creación del clúster, adjuntamos la S3csiDriverPolicy a un rol de IAM y anotamos la cuenta de servicio (“s3-csi-driver-sa”). Los pods del controlador de CSI Mountpoint de S3 heredan los permisos del rol de IAM al interactuar con S3. El resultado esperado debe tener el siguiente aspecto:
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
Adición de una tolerancia
El controlador de CSI de S3 se ejecuta como un DaemOnset en todos los nodos. Los pods utilizan el controlador de CSI en esos nodos para montar los volúmenes de S3. Para permitir que se programe en los nodos de nuestra GPU que tengan taints, añada una tolerancia al 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"}}]'
El resultado esperado debe tener el siguiente aspecto:
daemonset.apps/s3-csi-node patched
Carga de los pesos del modelo en S3
En este paso, creará un bucket de Amazon S3, descargará los pesos del modelo de GPUnet-0 de NVIDIA GPU Cloud (NGC) y los subirá al bucket. Nuestra aplicación accederá a estos pesos durante el tiempo de ejecución para realizar inferencias.
Cree su bucket de Amazon S3:
aws s3 mb s3://${S3_BUCKET_NAME} --region ${AWS_REGION}
Habilite el control de versiones en S3 para el bucket, a fin de evitar que las eliminaciones y sobrescrituras accidentales provoquen una pérdida de datos inmediata y permanente:
aws s3api put-bucket-versioning --bucket ${S3_BUCKET_NAME} --versioning-configuration Status=Enabled
Aplique una regla de ciclo de vida al bucket para eliminar las versiones de objetos sobrescritas o eliminadas 14 días después de que dejen de estar actualizadas, para eliminar los marcadores de eliminación caducados y para eliminar las cargas incompletas de varias partes transcurridos 7 días. Para obtener más información, consulte Ejemplos de configuraciones de S3 Lifecycle.
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}}]}'
Descargue los pesos del modelo GPUnet-0 de NGC. Por ejemplo, en macOS:
ngc registry model download-version nvidia/dle/gpunet_0_pyt_ckpt:21.12.0_amp --dest ~/downloads
nota
Es posible que tenga que ajustar este comando de descarga para su sistema operativo. Para que este comando funcione en un sistema Linux, es probable que tenga que crear el directorio como parte del comando (por ejemplo, mkdir ~/downloads
).
El resultado esperado debe tener el siguiente aspecto:
{ "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]" }
Cambie el nombre del archivo de puntos de control para que coincida con el nombre esperado en el código de nuestra aplicación en pasos posteriores (no es necesario extraerlo, ya que es un punto de control estándar de PyTorch *.pth.tar que contiene el diccionario de estados del modelo):
mv ~/downloads/gpunet_0_pyt_ckpt_v21.12.0_amp/0.65ms.pth.tar gpunet-0.pth
Habilite el tiempo de ejecución de AWS común
aws configure set s3.preferred_transfer_client crt
Cargue los pesos del modelo en el bucket de S3:
aws s3 cp gpunet-0.pth s3://${S3_BUCKET_NAME}/gpunet-0.pth
El resultado esperado debe tener el siguiente aspecto:
upload: ./gpunet-0.pth to s3://eks-rt-inference-models-us-east-1-1752722786/gpunet-0.pth
Creación del servicio de modelo
En este paso, configurará una aplicación web FastAPI para la clasificación de imágenes acelerada por la GPU mediante el modelo de visión GPUnet-0. La aplicación descarga los pesos del modelo de Amazon S3 en tiempo de ejecución, obtiene la arquitectura del modelo del repositorio de NVIDIA para almacenarla en caché y descarga las etiquetas de clase de ImageNet a través de HTTP. La aplicación incluye el preprocesamiento de imágenes, transforma y expone dos puntos de conexión: un punto de conexión GET raíz para comprobar el estado y un POST /predict
que acepta la URL de una imagen.
Servimos el modelo mediante FastAPI con PyTorch, cargando pesos desde Amazon S3 en tiempo de ejecución en una configuración en contenedores para la creación rápida de prototipos y la implementación de Kubernetes. Para ver otros métodos, como el procesamiento por lotes optimizado o los motores de alto rendimiento, consulte Serving ML Models.
Creación de la aplicación
Cree un directorio para los archivos de su aplicación, como model-testing
, cámbielo de directorio y añada el siguiente código a un nuevo archivo denominado 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))
Cree el Dockerfile.
El siguiente Dockerfile crea una imagen de contenedor para nuestra aplicación utilizando el modelo GPUnet del repositorio de GitHub NVIDIA Deep Learning Examples for Tensor Cores
Reducimos el tamaño de la imagen del contenedor utilizando una base PyTorch solo en tiempo de ejecución, instalando solo los paquetes esenciales con limpieza de caché, almacenando previamente en caché el código del modelo y evitando “sobrecargar” los pesos en la imagen del contenedor para permitir extracciones y actualizaciones más rápidas. Para obtener más información, consulte Reducing Container Image Sizes.
En el mismo directorio que app.py
, cree el 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"]
Pruebe la aplicación
Desde el mismo directorio que su app.py
y Dockerfile
, cree la imagen del contenedor para la aplicación de inferencia, orientada a la arquitectura de AMD64:
docker build --platform linux/amd64 -t gpunet-inference-app .
Defina variables de entorno para sus credenciales de AWS y, si lo desea, un token de sesión de AWS. Por ejemplo:
export AWS_REGION="us-east-1" export AWS_ACCESS_KEY_ID=ABCEXAMPLESCUJFEIELSMUHHAZ export AWS_SECRET_ACCESS_KEY=123EXAMPLEMZREoQXr8XkiicsOgWDQ5TpUsq0/Z
Ejecute el contenedor de forma local e inserte credenciales de AWS como variables de entorno para el acceso a S3. Por ejemplo:
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
El resultado esperado debe tener el siguiente aspecto:
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)
En una nueva ventana de terminal, pruebe el punto de conexión de inferencia enviando un ejemplo de solicitud POST con una URL de imagen pública como parámetro de consulta:
curl -X POST "http://localhost:8080/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
El resultado esperado debería ser una respuesta en JSON con las 5 mejores predicciones, similar a esta (las etiquetas y probabilidades reales pueden variar ligeramente según la imagen y la precisión del 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}]}
Cierre la aplicación presionando “Ctrl + C”.
Inserción del contenedor en Amazon ECR
En este paso, subimos la imagen del contenedor del servicio de modelo GPUnet-0 a Amazon Elastic Container Registry (ECR) para que esté disponible para su implementación en Amazon EKS. Este proceso implica crear un nuevo repositorio de ECR para almacenar la imagen, autenticarla con ECR y, a continuación, etiquetar y enviar la imagen del contenedor a nuestro registro.
En primer lugar, vuelva al directorio en el que configuró las variables de entorno al principio de esta guía. Por ejemplo:
cd ..
Cree un repositorio en Amazon ECR:
aws ecr create-repository --repository-name gpunet-inference-app --region ${AWS_REGION}
Inicie sesión en 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
El resultado esperado debe tener el siguiente aspecto:
Login Succeeded
Etiquete la imagen:
docker tag gpunet-inference-app:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Envíe la imagen a su repositorio de Amazon ECR:
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/gpunet-inference-app:latest
Este último paso demora varios minutos en completarse.
7. (Opcional) Exposición del servicio de modelo
En este paso, expondrá su servicio de modelo de inferencia en tiempo real de forma externa en Amazon EKS mediante el controlador del equilibrador de carga (LBC) de AWS. Esto implica configurar el LBC, montar los pesos del modelo de Amazon S3 como un volumen persistente mediante el controlador de CSI Mountpoint de S3, implementar un pod de aplicaciones acelerado por GPU, crear un servicio y una entrada para aprovisionar un equilibrador de carga de aplicación (ALB) y probar el punto de conexión.
En primer lugar, compruebe la asociación de Pod Identity con el LBC de AWS y confirme que la cuenta de servicio esté correctamente vinculada al rol de IAM requerido:
eksctl get podidentityassociation --cluster ${EKS_CLUSTER_NAME} --namespace kube-system --service-account-name aws-load-balancer-controller
El resultado esperado debe tener el siguiente aspecto:
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
Etiquetado del grupo de seguridad del clúster
El controlador del equilibrador de carga de AWS solo admite un grupo de seguridad único con la clave de etiqueta karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"
para la selección del grupo de seguridad de Karpenter. Al crear un clúster con eksctl, el grupo de seguridad del clúster predeterminado (que tiene la etiqueta "kubernetes.io/cluster/<cluster-name>: owned"
) no se etiqueta automáticamente con etiquetas karpenter.sh/discovery
. Esta etiqueta es esencial para que Karpenter detecte y adjunte este grupo de seguridad a los nodos que aprovisiona. La conexión de este grupo de seguridad garantiza la compatibilidad con el controlador del equilibrador de carga (LBC) de AWS, lo que le permite administrar automáticamente las reglas de tráfico entrante para los servicios expuestos a través de Ingress, como el servicio del modelo en estos pasos.
Exporte el ID de la VPC del clúster:
CLUSTER_VPC_ID="$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query cluster.resourcesVpcConfig.vpcId --output text)"
Exporte el grupo de seguridad predeterminado para el clúster:
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)"
Agregue la etiqueta karpenter.sh/discovery
al grupo de seguridad del clúster predeterminado. Esto permitirá que nuestros selectores EC2NodeClass de CPU y GPU lo utilicen:
aws ec2 create-tags --resources ${CLUSTER_SG_ID} --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}
Verifique que se haya agregado la etiqueta:
aws ec2 describe-security-groups --group-ids ${CLUSTER_SG_ID} --query "SecurityGroups[].Tags"
Entre los resultados, debería ver lo siguiente con la etiqueta y el nombre del clúster. Por ejemplo:
{ "Key": "karpenter.sh/discovery", "Value": "eks-rt-inference-us-east-1" }
Configuración del controlador del equilibrador de carga de AWS (LBC)
El LBC de AWS es esencial para administrar el tráfico de entrada a las cargas de trabajo de IA/ML en Amazon EKS, lo que garantiza el acceso a los puntos de conexión de inferencia o a los canales de procesamiento de datos. Al integrarse con los equilibradores de carga de aplicación (ALB) y los equilibradores de carga de red (NLB) de AWS, el LBC dirige dinámicamente el tráfico a aplicaciones en contenedores, como las que ejecutan modelos de lenguaje de gran tamaño, modelos de visión artificial o servicios de inferencia en tiempo real. Como ya creamos la cuenta de servicio y la asociación con Pod Identity durante la creación del clúster, configuramos el serviceAccount.name
para que coincidan con lo definido en la configuración de nuestro clúster (aws-load-balancer-controller
).
Añada el repositorio de gráficos de Helm eks-charts, propiedad de AWS:
helm repo add eks https://aws.github.io/eks-charts
Actualice sus repositorios locales de Helm con los gráficos más recientes:
helm repo update eks
Implemente el LBC de AWS con Helm, especificando el nombre del clúster de EKS y haciendo referencia a la cuenta de servicio creada previamente:
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
El resultado esperado debe tener el siguiente aspecto:
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!
Montaje del modelo en un volumen persistente
En este paso, montará los pesos del modelo desde su bucket de Amazon S3 mediante un PersistentVolume (PV) respaldado por el controlador de CSI Mountpoint para Amazon S3. Esto permite que los pods de Kubernetes accedan a los objetos de S3 como archivos locales, lo que elimina las descargas que consumen muchos recursos a contenedores de almacenamiento de pods efímeros o de inicio, lo que resulta ideal para modelos grandes de varios gigabytes de peso.
El PV monta toda la raíz del bucket (no se ha especificado ninguna ruta en volumeAttributes
), admite el acceso simultáneo de solo lectura desde varios pods y expone archivos como los pesos del modelo (/models/gpunet-0.pth
) dentro del contenedor para la inferencia. Esto garantiza que no se active la “descarga” alternativa en nuestra aplicación (app.py
) porque el archivo existe a través del montaje. Al desacoplar el modelo de la imagen del contenedor, se permite el acceso compartido y las actualizaciones independientes de las versiones del modelo sin necesidad de recompilar la imagen.
Creación del PersistentVolume (PV)
Cree un recurso PersistentVolume (PV) para montar el bucket de S3 que contenga los pesos del modelo, lo que permitirá el acceso de solo lectura a varios pods sin descargar archivos en tiempo de ejecución:
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
Creación del PersistentVolumeClaim (PVC)
Cree un PersistentVolumeClaim (PVC) para enlazarlo al PV y solicite acceso de solo lectura a los datos del modelo de 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
Implemente de la aplicación
Implemente la aplicación de inferencia como una implementación de Kubernetes, montando el volumen persistente respaldado por S3 para acceder al modelo, aplicando selectores y tolerancias de nodos de la GPU y configurando variables de entorno para la ruta del modelo. Esta implementación establece la ruta del modelo (var. del ent. de "/models/gpunet-0.pth"
), por lo que nuestra aplicación (en app.py
) la utilizará de forma predeterminada. Con el volumen de la implementación en /models
(solo lectura), la descarga del modelo no se activará si el archivo ya está presente en el 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 tardará unos minutos en aprovisionar un nodo de GPU si aún no hay uno disponible. Compruebe que el pod de inferencia tenga el estado “En ejecución”:
kubectl get pods -l app=gpunet-inference-app
El resultado esperado debe tener el siguiente aspecto:
NAME READY STATUS RESTARTS AGE gpunet-inference-app-5d4b6c7f8-abcde 1/1 Running 0 2m
Exposición del servicio con Ingress y el equilibrador de carga
Cree un servicio ClusterIP para exponer la implementación de inferencia internamente dentro del clúster de EKS, dirigido al puerto de la aplicación:
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
Cree un recurso de Ingress para aprovisionar un equilibrador de carga de aplicación (ALB) con acceso a Internet a través del LBC de AWS y enrute el tráfico externo al servicio de inferencia:
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 unos minutos para que el equilibrador de carga de aplicación (ALB) termine de aprovisionar. Supervise el estado de los recursos de Ingress para confirmar que el ALB se ha aprovisionado:
kubectl get ingress gpunet-model-ingress
El resultado esperado debería tener este aspecto (con el campo ADDRESS lleno):
NAME CLASS HOSTS ADDRESS PORTS AGE gpunet-model-ingress alb * k8s-default-gpunetmo-183de3f819-516310036.us-east-1.elb.amazonaws.com 80 6m58s
Extraiga y exporte el nombre de host del ALB del estado de Ingress para usarlo en pruebas posteriores:
export ALB_HOSTNAME=$(kubectl get ingress gpunet-model-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
Prueba del servicio de modelo
Valide el punto de conexión de inferencia expuesto enviando una solicitud POST con una URL de imagen de muestra (por ejemplo, del conjunto de datos COCO), simulando una predicción en tiempo real:
curl -X POST "http://${ALB_HOSTNAME}/predict?image_url=http://images.cocodataset.org/test-stuff2017/000000024309.jpg"
El resultado esperado debería ser una respuesta en JSON con las 5 mejores predicciones, similar a esta (las etiquetas y probabilidades reales pueden variar ligeramente según la imagen y la precisión del 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}]}
Si lo desea, puede continuar probando otras imágenes en una nueva solicitud POST. Por ejemplo:
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
Conclusión
En esta guía, configurará un clúster de Amazon EKS optimizado para cargas de trabajo de inferencia en tiempo real aceleradas por GPU. Usted ha aprovisionado un clúster con instancias G5 de EC2
Limpieza
Para evitar incurrir en cargos futuros, debe eliminar manualmente la pila de CloudFormation asociada para borrar todos los recursos creados durante esta guía, incluida la red de la VPC.
Elimine la pila de CloudFormation usando el indicador --wait
con eksctl:
eksctl delete cluster --region ${AWS_REGION} --name ${EKS_CLUSTER_NAME} --wait
Cuando se haya completado, debería ver la siguiente respuesta de salida:
2025-07-29 13:03:55 [✔] all cluster resources were deleted
Elimine el bucket de Amazon S3 creado durante esta guía desde la consola de Amazon S3