View a markdown version of this page

Configuración del clúster de Amazon EKS para cargas de trabajo de IA y ML con la CLI - Amazon EKS

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.

Configuración del clúster de Amazon EKS para cargas de trabajo de IA y ML con la CLI

sugerencia

Regístrese en los próximos talleres de IA/ML de Amazon EKS.

En esta sección, se explican los pasos necesarios para crear la infraestructura necesaria para ejecutar cargas de trabajo de formación o inferencia en Amazon EKS mediante comandos de la CLI. Los pasos incluyen la creación de un clúster de EKS, nodos habilitados para GPU con el modo automático de EKS o Karpenter, una pila de supervisión con Prometheus y Grafana y almacenamiento en Amazon S3 para el peso de los modelos.

Consulte la documentación del modo automático de EKS y Karpenter para obtener más información sobre cómo esas características aprovisionan y escalan automáticamente las instancias de EC2 en los clústeres de EKS.

Arquitectura y flujo de trabajo de alto nivel

Arquitectura de alto nivel que muestra el clúster de EKS con NodeClass y NodePool de Karpenter, la pila de supervisión de Grafana y Prometheus que se escribe en Amazon Managed Service para Prometheus, un bucket de Amazon S3 para los pesos de los modelos y los pasos numerados del flujo de trabajo

En el diagrama se muestra la arquitectura de alto nivel de AWS para la configuración de esta sección. Los pasos numerados de la derecha indican el orden en el que se completa la configuración en los pasos siguientes.

Requisitos previos

  • kubectl >= 1.35. Para obtener instrucciones de configuración, consulte Configuración de kubectl y eksctl.

  • AWS CLI >= 2.27. Para obtener instrucciones de configuración, consulte Instalación.

  • Helm >= 3.14. Para obtener instrucciones de configuración, consulte Configuración de Helm.

  • jq. Para obtener instrucciones de configuración, consulte Descarga de jq.

  • eksctl >= 0.227.0. Para obtener instrucciones de configuración, consulte Instalación en la documentación de eksctl.

Verifique la versión de eksctl:

eksctl version

Si tiene una versión anterior a la 0.227.0, siga la guía de instalación de eksctl para actualizar a la versión más reciente.

Configuración de las variables de entorno

Mantenga la coherencia entre el nombre del clúster y la región de AWS siguientes a lo largo de estos pasos. Si se cambia, es posible que los siguientes comandos se dirijan al clúster de EKS incorrecto.

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

El uso de todas las AZ disponibles mejora la tolerancia a errores y aumenta las posibilidades de obtener la capacidad de GPU:

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

Las zonas de disponibilidad use1-az3, usw1-az2 y cac1-az3 están excluidas porque Amazon EKS no admite la colocación de planos de control en esas zonas. La creación de un clúster con subredes en cualquiera de estas zonas da como resultado UnsupportedAvailabilityZoneException.

Resultado previsto:

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

Las AZ de la salida variarán según la región. En este ejemplo, se muestran las AZ disponibles para la región us-east-2.

Creación de NodePool de GPU y clúster

En esta sección, se proporcionan dos rutas para crear el clúster de EKS y los nodos habilitados para GPU, como se muestra en el siguiente diagrama. Elija solo una opción a lo largo de la guía.

  • Modo automático de EKS: además de los complementos de redes, almacenamiento y equilibrio de carga principales, el modo automático de EKS incluye y administra las siguientes capacidades para las cargas de trabajo de entrenamiento e inferencia: agente de supervisión de nodos de EKS, reparación automática de nodos, capturador de instantáneas SOCI para extraer contenedores rápidamente y preparación de la GPU para la NodeClass predeterminada. El complemento para dispositivos de NVIDIA se incluye en la AMI acelerada de Bottlerocket que utiliza el modo automático de EKS para los nodos habilitados para GPU.

  • Karpenter autoadministrado: en un clúster de EKS sin el modo automático de EKS, es su responsabilidad instalar y configurar los componentes necesarios para las cargas de trabajo de entrenamiento e inferencia. Esto incluye complementos de red (CNI de VPC, CoreDNS, kube-proxy), Karpenter, el agente de supervisión de nodos de EKS, el complemento para dispositivos de NVIDIA y el capturador de instantáneas SOCI para extraer contenedores rápidamente.

Opciones de clústeres de EKS: modo automático de EKS y Karpenter autoadministrado

Comparación en paralelo de las dos opciones de clústeres: un clúster del modo automático de EKS con NodePool y un clúster estándar de EKS con Karpenter, CoreDNS, CNI de VPC, complemento para dispositivos de NVIDIA, agente de Pod Identity de EKS, agente de supervisión de nodos, kube-proxy y NodeClass y NodePool

En cada uno de los pasos siguientes, elija una ruta (modo automático de EKS o Karpenter) y sígala en todo momento. Después de completar los pasos de la ruta elegida, dispondrá de un clúster de EKS con NodePool de GPU listo para programar las cargas de trabajo de la GPU.

Paso 1: crear el clúster

Comience por crear su clúster de EKS e instalar los componentes del clúster necesarios para las cargas de trabajo de GPU.

Con el modo automático de EKS, un solo comando eksctl create cluster --enable-auto-mode proporciona un clúster de EKS listo para las cargas de trabajo de la GPU.

En el caso de Karpenter autoadministrado, el comando eksctl create cluster proporciona los complementos principales de red y, a continuación, es necesario adoptar medidas adicionales para permitir la reparación automática de los nodos mediante una puerta de la característica de Karpenter, instalar el agente de supervisión de nodos de EKS e instalar el complemento para dispositivo de NVIDIA.

EKS Auto Mode

Creación de un clúster del modo automático de EKS

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

Este comando tarda varios minutos en completarse. Una vez finalizado, eksctl actualiza automáticamente el archivo kubeconfig para que funcione con el clúster recién aprovisionado. Compruebe que el clúster esté en funcionamiento:

kubectl get pods --all-namespaces

Resultado previsto:

NAMESPACE NAME READY STATUS RESTARTS AGE kube-system metrics-server-55cf976ddd-cz2mw 1/1 Running 0 3m kube-system metrics-server-55cf976ddd-wrjvv 1/1 Running 0 3m

En el modo automático de EKS, el CNI de VPC, kube-proxy y CoreDNS se ejecutan como componentes administrados y no aparecen como pods en kube-system.

Self-managed Karpenter

Autenticación de Helm en ECR público

eksctl extrae el gráfico de Helm de Karpenter de Amazon ECR público. Autentique antes de crear el clúster para evitar un error 403 en el paso de instalación de Helm:

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

ECR público es un servicio global alojado en us-east-1. Utilice --region us-east-1 aquí independientemente de la región en la que se encuentre su clúster de EKS.

Resultado previsto: Login Succeeded

Creación de un clúster de EKS con Karpenter

Almacene su versión de Karpenter en una variable de entorno para usarla más adelante. Para obtener las versiones más recientes de Karpenter, consulte los lanzamientos de Karpenter en GitHub.

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

Este comando tarda alrededor de 15 minutos en completarse. Crea un clúster de EKS con un grupo de nodos administrado dedicado a alojar complementos y el controlador de Karpenter. Karpenter se instala con la cola de interrupciones de spot habilitada, por lo que puede gestionar las recomendaciones de interrupción y reequilibrio de spot. La configuración autoModeConfig.enabled: false hace explícito que este clúster no usa el modo automático de EKS, por lo que los componentes de Karpenter instalados en esta ruta son responsables de la administración de los nodos.

El clúster también instala el agente de Pod Identity de EKS y el agente de supervisión de nodos de EKS como complementos de EKS. Pod Identity de EKS se utiliza más adelante en la guía. El agente de supervisión de nodos de EKS se ejecuta en todos los nodos y lee los registros del kernel para establecer las condiciones de los nodos, como AcceleratedHardwareReady, KernelReady y NetworkingReady, que utiliza la reparación automática de nodos de Karpenter para decidir cuándo sustituir un nodo en mal estado.

Compruebe que el clúster esté en funcionamiento:

kubectl get pods --all-namespaces

La salida esperada incluye Karpenter, CoreDNS, kube-proxy, aws-node (CNI de VPC), el agente de Pod Identity de EKS y el agente de supervisión de nodos de EKS.

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

Habilitación de la reparación automática de nodos

El modo automático de EKS permite la reparación automática de nodos de forma predeterminada. En Karpenter autoadministrado, la reparación automática de nodos se efectúa por detrás de la puerta de característica de NodeRepair=true y debe habilitarse de forma explícita. El siguiente comando parchea la implementación de Karpenter para agregar la puerta de característica de NodeRepair=true. Al actualizar el entorno de implementación, se activa la implementación de los pods de Karpenter:

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

Resultado previsto:

deployment.apps/karpenter env updated

Espere a que se implementen los módulos de Karpenter:

kubectl rollout status deployment/karpenter -n karpenter

Instalación del complemento para dispositivos de NVIDIA

La AMI AL2023 optimizada para EKS no incluye el complemento para dispositivos de NVIDIA (a diferencia de la AMI de Bottlerocket que utiliza el modo automático de EKS). Instálela mediante Helm para poder utilizar los recursos de la GPU con los pods del clúster.

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin helm repo update
cat << 'EOF' > /tmp/nvdp-values.yaml mofedEnabled: false nodeSelector: amiFamily: al2023 gfd: enabled: true nfd: worker: tolerations: - operator: "Exists" EOF
helm install nvidia-device-plugin nvdp/nvidia-device-plugin \ --namespace kube-system \ -f /tmp/nvdp-values.yaml
  • mofedEnabled: false: deshabilita la comprobación de Mellanox OFED (InfiniBand), que no utiliza AWS

  • nodeSelector.amiFamily: al2023: limita el DaemonSet solo a nodos de AL2023 (Bottlerocket ya tiene el complemento incorporado)

  • gfd.enabled: true: habilita las etiquetas de detección de características de GPU (nvidia.com/gpu.product, nvidia.com/gpu.memory, etc.)

Verifique que el complemento para dispositivos de NVIDIA esté instalado. La expectativa es que no haya ningún pod de complemento para dispositivos hasta que se aprovisione un NodePool de GPU con la etiqueta correspondiente.

kubectl get daemonset nvidia-device-plugin -n kube-system

Resultado previsto:

NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE nvidia-device-plugin 0 0 0 0 0 amiFamily=al2023 2m5s
aviso

Tanto para las rutas del modo automático de EKS como las de Karpenter autoadministrado, la reparación automática de nodos se comporta de la misma manera en los nodos aprovisionados por NodePools. La reparación automática de nodos en el modo automático de EKS y Karpenter es un método de interrupción forzosa que evita PodDisruptionBudgets, la anotación karpenter.sh/do-not-disrupt y terminationGracePeriod. La reparación automática de nodos espera 10 minutos antes de sustituir un nodo con la condición AcceleratedHardwareReady establecida en False y 30 minutos si se trata de otras condiciones de reparación.

Paso 2: creación de NodePool de GPU dinámica

Defina un NodePool que aprovisione dinámicamente las instancias de GPU de la familia G con una generación superior a 4 mediante la capacidad de spot con un uso bajo demanda como alternativa. Tanto las rutas del modo automático de EKS como las de Karpenter utilizan la misma API de NodePool, con la única diferencia de la NodeClass a la que apunta. En el modo automático de EKS, la NodeClass default agrupada ya selecciona la AMI correcta y configura la extracción paralela de SOCI, por lo que NodePool es el único objeto que se crea. En Karpenter autoadministrado, también se necesita una EC2NodeClass personalizada que fije la AMI y ajuste SOCI.

EKS Auto Mode

En el modo automático de EKS, la NodeClass default agrupada selecciona automáticamente la AMI de Bottlerocket para las instancias de GPU, que incluye los controladores de NVIDIA preinstalados, el complemento para dispositivos de NVIDIA y la extracción paralela de SOCI. Solo necesita aplicar un NodePool que haga referencia a la NodeClass default:

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

Este NodePool aprovisiona instancias de GPU de la familia G con una generación superior a 4 (G5, G6e, G7e, etc.). La propiedad taint nvidia.com/gpu:NoSchedule garantiza que solo los pods aptos para GPU se programen en estos nodos.

Self-managed Karpenter

Karpenter administrador no incluye ninguna NodeClass predeterminada. Debe crear antes una EC2NodeClass que fije el alias de la AMI de AL2023 NVIDIA optimizada para EKS, habilite SOCI a través de la puerta de característica de FastImagePull y configure instanceStorePolicy: RAID0 para mover la caché de imagen de containerd a un NVMe local. A continuación, se crea el NodePool que hace referencia a él.

Creación de EC2NodeClass

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

instanceStorePolicy: RAID0 ensambla los discos NVMe locales en una matriz RAID-0. El alias de la AMI al2023@latest se convierte en la AMI de AL2023 optimizada para EKS. Cuando Karpenter lanza un tipo de instancia de GPU, selecciona automáticamente la variante acelerada AL2023_x86_64_NVIDIA, que incluye el controlador de NVIDIA preinstalado.

La puerta de característica de FastImagePull habilita el modo de extracción paralela del capturador de instantáneas de SOCI, que descarga y desempaqueta capas de imágenes simultáneamente. Esto coincide con el comportamiento del modo automático de EKS en las familias de instancias G, P y Trn. El bloque containerd.config ajusta el capturador de instantáneas de SOCI para las imágenes alojadas de ECR:

  • max_concurrent_downloads_per_image: 20 permite descargar hasta 20 capas en paralelo por imagen. El valor predeterminado es 3 en Bottlerocket y 20 en AL2023. Valor recomendado para ECR.

  • concurrent_download_chunk_size: "16mb" divide cada capa en fragmentos de 16 MB descargados en paralelo mediante solicitudes de rango de HTTP. Se recomienda para registros que admiten GET de rango (ECR sí).

  • max_concurrent_unpacks_per_image: 12 desempaqueta hasta 12 capas a la vez. El valor predeterminado es 1 en Bottlerocket y 12 en AL2023.

  • discard_unpacked_layers: true elimina los blobs de capas comprimidas después de desempaquetarlos para ahorrar espacio en disco.

Para obtener más opciones de ajuste de SOCI (descargas simultáneas por imagen, tamaño de fragmento, etc.), consulte el esquema de SOCI de Karpenter.

Valide EC2NodeClass:

kubectl get ec2nodeclass gpu-inf

Resultado previsto: READY True. Si es False, ejecute kubectl describe ec2nodeclass gpu-inf y compruebe las condiciones para ver si faltan etiquetas de grupo de seguridad o subred.

Creación de NodePool de GPU

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

La etiqueta amiFamily: al2023 de la plantilla de nodos es la que utiliza el DaemonSet del complemento para dispositivos de NVIDIA para seleccionar estos nodos.

Valide que se haya creado el NodePool:

kubectl get nodepool gpu-inf

Resultado previsto:

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

En la ruta de Karpenter autoadministrado, la columna NODECLASS muestra gpu-inf en lugar de default.

Paso 3: prueba con un pod de muestra

Pruebe la configuración de NodePool de GPU con un pod nvidia-smi.

cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nvidia-smi labels: guide: ai-eks-docs spec: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" containers: - name: nvidia-smi image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1 restartPolicy: OnFailure EOF

Compruebe que el pod se haya programado y completado correctamente.

kubectl get pods

Resultado previsto:

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

Si aparece ESTADO: Completado, significa que el comando nvidia-smi se ejecutó y se cerró. Compruebe los registros del pod para ver la GPU detectada por el nodo.

kubectl logs nvidia-smi

Resultado previsto:

+-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.126.09 Driver Version: 580.126.09 CUDA Version: 13.0 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA RTX PRO 6000 Blac... On | 00000000:2B:00.0 Off | 0 | | N/A 30C P0 81W / 600W | 0MiB / 97887MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+

La salida muestra el modelo de GPU, la versión del controlador, la versión de CUDA y la memoria disponible. En este ejemplo, Karpenter aprovisionó una instancia G7e que tiene una GPU Blackwell NVIDIA RTX PRO 6000 con 96 GB de memoria. 30 °C es la temperatura actual de la GPU y P0 significa que la GPU se encuentra en su estado de máximo rendimiento (inactiva, pero lista). El valor de 81 W o 600 W muestra el consumo de energía actual en comparación con la capacidad máxima de energía, mientras que 0 MiB o 97 887 MiB muestra el consumo actual de la GPU en comparación con el total disponible. Como el pod acaba de ejecutar nvidia-smi y se ha cerrado, no hay carga de trabajo que utilice la GPU, por lo que la memoria está a 0 y la alimentación está inactiva. La versión del controlador de GPU de NVIDIA (580.126.09) proviene de la AMI de Bottlerocket, mientras que la versión de CUDA (13.0) proviene de la imagen del contenedor. El modelo de GPU y la memoria variarán según el tipo de instancia que seleccione Karpenter. Las instancias G5 tienen GPU NVIDIA A10G (24 GB), las instancias G6e tienen GPU NVIDIA L40S (48 GB) y las instancias G7e tienen GPU NVIDIA RTX PRO 6000 (96 GB).

Para entender cómo Karpenter y el programador de Kubernetes se coordinaron para aprovisionar un nodo y colocar el pod, compruebe los eventos del ciclo de vida del pod:

kubectl describe po nvidia-smi

Resultado previsto:

Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 60s default-scheduler 0/2 nodes are available: 2 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling. Normal Nominated 59s eks-auto-mode/compute Pod should schedule on: nodeclaim/gpu-inf-vxcnj Normal Scheduled 24s default-scheduler Successfully assigned default/nvidia-smi to i-0fb17a09bc4203164 Warning FailedCreatePodSandBox 21s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "7f85e25b220c8fb245187758dbbbc8efb3d40f3e49e13054404880daf4c3b2f0": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to setup network policy Normal Pulling 7s kubelet spec.containers{nvidia-smi}: Pulling image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" Normal Pulled 5s kubelet spec.containers{nvidia-smi}: Successfully pulled image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" in 1.237s (1.237s including waiting). Image size: 37442701 bytes. Normal Created 5s kubelet spec.containers{nvidia-smi}: Container created Normal Started 5s kubelet spec.containers{nvidia-smi}: Container started

Estos eventos muestran la secuencia de programación del pod: el pod inicialmente no se programa porque no existen nodos de GPU (FailedScheduling), Karpenter nomina un nuevo NodeClaim (nominado), el programador asigna el pod una vez que el nodo está listo (programado) y, a continuación, se extrae e inicia la imagen del contenedor. El modo automático de EKS viene con la extracción paralela de SOCI (Seekable OCI) instalada y configurada de fábrica en las instancias G, P y Trn. Tenga en cuenta que, debido a la extracción paralela de SOCI, la imagen del contenedor se extrajo de ECR en menos de 2 segundos (1,237 s).

NodeClaim es una solicitud que Karpenter crea para aprovisionar un nodo específico. Muestra el tipo de instancia, el tipo de capacidad, la AZ y si el nodo está listo.

kubectl get nodeclaims

Salida esperada de NodeClaim:

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

El tipo de instancia y la AZ variarán. Todas las instancias de la familia G de generación superior a 4 son aptas.

La advertencia FailedCreatePodSandBox en kubectl describe pod nvidia-smi es transitoria y esperada. CNI de VPC se inicializa de forma asíncrona una vez que el nodo se une y el kubelet lo vuelve a intentar automáticamente. Si el pod permanece en ContainerCreating, compruebe los eventos del nodo con kubectl describe node <node-name>.

sugerencia

Si no aparece ningún nodo, compruebe si hay errores de capacidad insuficiente:

kubectl get events | grep InsufficientCapacityError

Karpenter almacena en caché las ofertas no disponibles durante 3 minutos. Ampliar los tipos de instancias y las zonas de disponibilidad permitidos en el NodePool aumenta las posibilidades de conseguir capacidad.

nota

Las instancias de spot lanzadas por Karpenter no aparecerán en la consola de solicitudes de spot de EC2. Karpenter utiliza la API CreateFleet de EC2 con type: instant. Las instancias aparecen en la consola de instancias de EC2 con un ciclo de vida de spot.

Paso 4: adición de capacidad reservada al NodePool (opcional)

Para utilizar primero la capacidad reservada con la reserva de spot o bajo demanda, cree un ODCR, adjúntelo a su NodeClass y, a continuación, actualice el NodePool dinámico del paso 2 para permitir también la capacidad reserved. La llamada a la API de reserva es la misma para ambas rutas; el adjunto de NodeClass es diferente porque el modo automático de EKS y Karpenter autoadministrado utilizan diferentes tipos de NodeClass.

aviso

El siguiente comando genera un cargo por el tipo de instancia reservada hasta que lo cancele con aws ec2 cancel-capacity-reservation --capacity-reservation-id <id>.

Cree la reserva de capacidad:

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

Si se produce un error InsufficientInstanceCapacity, cambie CR_AZ a una AZ diferente y vuelva a intentarlo.

Busque el ID de reserva de capacidad y guárdelo en una variable del intérprete de comandos para los siguientes pasos:

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

A continuación, aplique los cambios de NodeClass y NodePool a la ruta:

EKS Auto Mode

En el modo automático de EKS, la NodeClass default agrupada es de solo lectura, así que debe crear una NodeClass personalizada que haga referencia a la reserva y, a continuación, actualice el NodePool para que apunte a la NodeClass y agregue capacidad reserved a la lista capacity-type.

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

La etiqueta kubernetes.io/role/internal-elb: "1" garantiza que los nodos se lancen solo en subredes privadas.

Actualice el NodePool para usar la NodeClass basada en ODCR e incluya reserved como tipo de capacidad:

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

En el caso de Karpenter autoadministrado, vuelva a aplicar la EC2NodeClass que creó en el paso 2 en el que se agregó capacityReservationSelectorTerms. El nombre y la forma del campo coinciden con la NodeClass del modo automático de EKS que se muestra en la otra pestaña.

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

El único cambio con respecto al paso 2 es el nuevo campo capacityReservationSelectorTerms. Todos los demás campos siguen siendo los mismos.

Actualice el NodePool para incluir reserved como tipo de capacidad:

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

Karpenter considera reserved como la opción más rentable y la lanza primero. Una vez que la reserva esté completa, volverá a ser de spot o bajo demanda.

Tras aplicar los cambios, valide que Karpenter priorice la capacidad reservada y recurra a las opciones de spot o bajo demanda. Implemente una implementación de 2 réplicas que solicite 1 GPU por pod. El ODCR es para 1 instancia, por lo que el primer pod activa Karpenter para lanzar un nodo reservado. El segundo pod no cabe en el nodo reservado y hace que Karpenter lance otro nodo desde la capacidad de spot o bajo demanda.

cat << 'EOF' | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: gpu-overflow-test labels: guide: ai-eks-docs spec: replicas: 2 selector: matchLabels: app: gpu-overflow-test template: metadata: labels: app: gpu-overflow-test guide: ai-eks-docs spec: tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: nvidia-smi image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal command: ["sh", "-c", "nvidia-smi && sleep infinity"] resources: limits: nvidia.com/gpu: 1 EOF

A diferencia del pod de prueba nvidia-smi del paso 3 que se ejecutó y cerró, esta implementación mantiene los pods en ejecución (sleep infinity) para que mantengan la GPU y no lancen el nodo.

Verifique los pods programados en los distintos nodos:

kubectl get pods -l app=gpu-overflow-test -o wide

Resultado previsto:

NAME                                 READY   STATUS    RESTARTS   AGE     IP                NODE                  NOMINATED NODE   READINESS GATES
gpu-overflow-test-59b97944fb-lq56c   1/1     Running   0          2m42s   192.168.186.240   i-057692590480155da   <none>           <none>
gpu-overflow-test-59b97944fb-z4zcx   1/1     Running   0          2m42s   192.168.130.64    i-0521ecd1849fa0578   <none>           <none>

Los dos pods se están ejecutando, cada uno en un nodo diferente.

Compruebe NodeClaims para ver los tipos de capacidad:

kubectl get nodeclaims

Resultado previsto:

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

El nodo reservado se lanzó primero, seguido de un nodo de spot o bajo demanda una vez que se completó la reserva.

Elimine la implementación de prueba:

kubectl delete deployment gpu-overflow-test

Supervisión

Instale una pila de supervisión que recopile métricas de clústeres, nodos y GPU en Amazon Managed Service para Prometheus (AMP) y visualícelas con Grafana. El gráfico de Helm kube-prometheus-stack implementa Prometheus para raspar y escribir métricas de forma remota en AMP, además de Grafana autoadministrado para los paneles. El Exportador DCGM de NVIDIA agrega métricas específicas de la GPU (uso, memoria, temperatura, potencia, NVLink y actividad de los tensores).

Prometheus, Grafana y el operador aterrizan en nodos que no son de la GPU de forma predeterminada, ya que los nodos de la GPU incluyen el taint nvidia.com/gpu:NoSchedule. Tanto el exportador de nodos como el Exportador DCGM se ejecutan en nodos de GPU, por lo que podemos raspar las métricas del host y de la GPU de toda la flota.

Si abrió un terminal nuevo, establezca el nombre y la región del clúster:

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

Creación del espacio de trabajo de AMP

Cree un espacio de trabajo de AMP para almacenar las métricas:

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

Obtenga el ID de espacio de trabajo:

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

Obtenga el punto de conexión de escritura remota:

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

Creación de asociaciones de Pod Identity de EKS y política de IAM

Cree una política de IAM que permita a Prometheus escribir métricas de forma remota y a Grafana consultarlas:

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

Cree el espacio de nombres de supervisión y las cuentas de servicio para Prometheus y Grafana:

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

Cree asociaciones de Pod Identity de EKS para vincular las cuentas de servicio a la política de IAM:

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

Verifique que se hayan creado ambas asociaciones de Pod Identity de EKS:

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

La salida esperada debe incluir amp-iamproxy-ingest-service-account y grafana-sa en el espacio de nombres monitoring.

Instalación de kube-prometheus-stack

Agregue el repositorio de Helm:

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

Este archivo de valores omite un nodeSelector para Prometheus, Grafana y el operador: el taint nvidia.com/gpu:NoSchedule de los nodos de la GPU los mantiene alejados de los nodos de la GPU, por lo que aterrizan en el sistema o en un grupo de uso general de forma predeterminada. El exportador de nodos utiliza una tolerancia comodín, por lo que se ejecuta en todos los nodos (incluidos los nodos de la GPU) para recopilar las métricas de toda la flota.

Cree el archivo de valores:

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

Valide que las variables se hayan rellenado correctamente:

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

Debería ver la URL completa del punto de conexión de AMP (comienza por https://aps-workspaces…​), la región y la línea tolerations: del exportador de nodos. Si hay algo vacío, vuelva a exportar las variables y a crear el archivo.

Instale el gráfico:

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

Verifique que los pods se estén ejecutando:

kubectl get pods -n monitoring

Resultado previsto:

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

La pila incluye los siguientes componentes:

  • Prometheus (StatefulSet): raspa métricas y las escribe de forma remota en AMP

  • Grafana: paneles y visualización, preconfigurados con el origen de datos de AMP

  • kube-state-metrics: genera métricas sobre el estado de objetos de Kubernetes (estado del Pod, límites y solicitudes de recursos, estados de NodeClaim)

  • node-exporter (DaemonSet, uno por nodo): recopila métricas de host (CPU, memoria, disco, red)

  • operador: administra los recursos personalizados de Prometheus y Alertmanager

Alertmanager está deshabilitado en esta configuración.

Acceso a Grafana

Abra un terminal y un puerto de reenvío separados para acceder a Grafana:

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

Abra http://localhost:3000 en el navegador. Inicie sesión con el nombre de usuario admin y la contraseña con el siguiente comando:

kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo

Para comprobar que la canalización de métricas funciona de principio a fin:

  1. Vaya a Conexiones > Orígenes de datos y confirme que Amazon-Managed-Prometheus aparezca como origen de datos predeterminado.

    Validación del origen de datos de AMP en Grafana

    Página de conexiones de Grafana que muestra Amazon-Managed-Prometheus como origen de datos predeterminado
  2. Vaya a Desglose > Métricas y busque la métrica up. Debería ver los resultados de los objetivos de raspado del clúster.

    Validación de la métrica up en Grafana

    Página de métricas de desglose de Grafana que muestra la métrica ascendente con barras de estado verdes que indican los objetivos de raspado activos

Si up muestra resultados, la canalización (clúster → Prometheus → AMP → Grafana) está funcionando.

Implementación del Exportador DCGM para las métricas de la GPU

kube-prometheus-stack recopila métricas de CPU y memoria de nodo, pero no métricas de GPU. El Exportador DCGM de NVIDIA agrega el uso de GPU, el uso de memoria, la temperatura, el consumo de energía, el ancho de banda de NVLink y la actividad de los tensores.

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

Establezca la clave del selector de nodos de GPU para la ruta. El modo automático de EKS y Karpenter autoadministrado utilizan diferentes etiquetas según el fabricante de la GPU.

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

Cree el archivo de valores del Exportador DCGM:

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

El campo customMetrics anula el conjunto de métricas predeterminado del Exportador DCGM por uno ampliado que incluye el ancho de banda de NVLink, la actividad de los tensores, el rendimiento de PCIe, los errores de ECC y la limitación térmica. Para las cargas de trabajo de inferencia, esto le permite comprender si las unidades de computación de la GPU se utilizan al máximo, si la GPU está inactiva entre las solicitudes debido al bajo tamaño de los lotes, si la transferencia de datos entre la CPU y la GPU es un cuello de botella, si la limitación térmica provoca picos de latencia y cuánto espacio de memoria queda en la GPU para lotes más grandes.

Instale el Exportador DCGM:

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

Las tolerations permiten al exportador ejecutarse en los nodos con taint de GPU que aprovisionó en el paso 2. La opción serviceMonitor con la etiqueta release: kube-prometheus-stack garantiza que Prometheus lo detecte y raspe automáticamente.

Verificación del DaemonSet del Exportador DCGM:

kubectl get daemonset dcgm-exporter -n monitoring

Una vez que se ejecute un nodo de la GPU, debería ver un pod listo para usar. Para validar las métricas de DCGM, vaya a Desglose > Métricas en Grafana y busque DCGM_.

Validación de las métricas de DCGM en Grafana

Página de métricas desglosadas de Grafana filtrada por DCGM_ que muestra las métricas de GPU, como DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, DCGM_FI_DEV_ENC_UTIL, DCGM_FI_DEV_FB_FREE y DCGM_FI_DEV_FB_USED

Para ver el panel, vaya a Paneles > Supervisión de GPU > Panel del Exportador DCGM de NVIDIA.

Panel del Exportador DCGM de NVIDIA en Grafana

Panel del Exportador DCGM de NVIDIA de Grafana que muestra los paneles Uso de la GPU, Temperatura media de la GPU, Memoria de framebuffer de GPU usado y Potencia total de la GPU

Bucket de S3 de pesos de los modelos

Cree un bucket de Amazon S3 para almacenar los pesos de los modelos y configure una asociación de Pod Identity de EKS para que los pods de carga de trabajo puedan leer y escribir en él.

Si abrió un terminal nuevo, establezca el nombre y la región del clúster:

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

Crear el bucket de S3

Cree el bucket con un sufijo aleatorio para evitar colisiones de nombres:

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

Los buckets de S3 creados después de enero de 2023 tienen habilitados de forma predeterminada el cifrado del servidor (AES256) y el bloqueo del acceso público.

Configuración de Pod Identity de EKS para el acceso a S3

Cree una ServiceAccount model-storage-sa en el espacio de nombres default, una política de IAM que se ajuste al bucket de modelos y una asociación de Pod Identity de EKS que las vincule. Los pods de carga de trabajo que establezcan serviceAccountName: model-storage-sa podrán leer y escribir en el bucket.

kubectl create serviceaccount model-storage-sa

Cree la política de IAM:

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

Esta política concede s3:DeleteObject y s3:PutObject para el paso de validación. Para los pods de inferencia de producción que solo leen los pesos de los modelos, elimine s3:PutObject y s3:DeleteObject siga el privilegio mínimo.

Cree la asociación de Pod Identity de EKS. eksctl crea el rol de IAM con la política de confianza correcta y lo vincula a la ServiceAccount:

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

Verifique la asociación:

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

La salida debe incluir la asociación model-storage-sa en el espacio de nombres default.

Ejecute un pod único con la imagen de la AWS CLI, mediante la ServiceAccount model-storage-sa, para confirmar que Pod Identity de EKS esté conectado y que el acceso a S3 funcione:

cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: s3-test labels: guide: ai-eks-docs spec: serviceAccountName: model-storage-sa containers: - name: aws-cli image: public.ecr.aws/aws-cli/aws-cli:2.27.0 command: - sh - -c - | echo "=== Caller Identity ===" aws sts get-caller-identity echo "" echo "=== S3 Write Test ===" echo "pod identity works" | aws s3 cp - s3://${MODEL_BUCKET}/test.txt echo "" echo "=== S3 List Test ===" aws s3 ls s3://${MODEL_BUCKET}/ echo "" echo "=== S3 Delete Test ===" aws s3 rm s3://${MODEL_BUCKET}/test.txt restartPolicy: Never EOF

Espere a que el pod se complete y compruebe los registros:

kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/s3-test --timeout=300s kubectl logs s3-test

Resultado previsto:

=== Caller Identity ===
{
    "UserId": "AROA...:eks-ai-eks-docs-model-s-...",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/ai-eks-docs-model-storage-role/eks-ai-eks-docs-model-s-..."
}

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

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

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

La identidad de la persona que llama confirma que el pod asumió el rol ${CLUSTER_NAME}-model-storage-role a través de Pod Identity de EKS. Los comandos de S3 confirman el acceso de lectura y escritura.

Elimine el pod de prueba:

kubectl delete pod s3-test

Siguientes pasos

Con el clúster listo, puede pasar al modelo de carga y servicio para implementar un modelo de lenguaje de gran tamaño e interactuar con el punto de conexión de inferencia.

Eliminación

sugerencia

Si planea continuar con las siguientes secciones de esta guía, omita la limpieza completa. Ejecútela solo cuando haya terminado.

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

Si ha creado una ODCR, cancélelo primero:

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

La cancelación de una reserva no termina las instancias en ejecución. Se mantienen con las tasas bajo demanda estándar hasta que se terminen. Elimine antes la implementación para vaciar el nodo reservado antes de cancelarlo.

Busque el ARN de la política de IAM:

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

Busque el ID del espacio de trabajo de AMP:

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

Desinstale la versión de Helm del Exportador DCGM:

helm uninstall dcgm-exporter -n monitoring

Desinstale la versión de Helm de kube-prometheus-stack:

helm uninstall kube-prometheus-stack -n monitoring

Elimine la asociación de Pod Identity de EKS para la cuenta de servicio de ingesta de Prometheus:

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

Elimine la asociación de Pod Identity de EKS para la cuenta de servicio de Grafana:

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

Elimine la política de IAM utilizada por Prometheus y Grafana:

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

Elimine el espacio de trabajo de AMP:

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

Elimine el espacio de nombres de supervisión:

kubectl delete namespace monitoring

Busque el nombre del bucket del modelo:

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

Busque el ARN de la política de IAM:

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

Elimine el bucket del modelo S3 y todos sus objetos:

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

Elimine la asociación de Pod Identity de EKS:

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

Elimine la política de IAM:

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

Elimine la ServiceAccount de Kubernetes:

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