Salta al contenuto principale

08 - Workload AI e GPU su Kubernetes: Device Plugin e Job di Training

Workload AI e GPU su Kubernetes: Device Plugin e Job di Training Nel 2026 il 66% dei cluster di inferenza AI gira su Kubernetes (CNCF Survey 2026).

Cosa ne pensi di questo articolo?

08 - Workload AI e GPU su Kubernetes: Device Plugin e Job di Training
Kubernetes AvanzatoFoto:panumas nikhomkhaiPexels

In Breve

Workload AI e GPU su Kubernetes: Device Plugin e Job di Training Nel 2026 il 66% dei cluster di inferenza AI gira su Kubernetes (CNCF Survey 2026).

Workload AI e GPU su Kubernetes: Device Plugin e Job di Training

Nel 2026 il 66% dei cluster di inferenza AI gira su Kubernetes (CNCF Survey 2026). La ragione e semplice: Kubernetes risolve i problemi operativi piu difficili dei workload AI - scheduling intelligente delle GPU, scaling elastico dei job di training, integrazione con storage distribuito per i dataset, retry automatico in caso di node failure. Ma configurare Kubernetes per i workload GPU richiede competenze specifiche che vanno ben oltre il deployment di una normale applicazione web.

In questo articolo vedremo come configurare il NVIDIA Device Plugin per esporre le GPU al cluster, come schedulare Job di training distribuito con PyTorch e TensorFlow, come usare Karpenter per fare spot scaling delle GPU (riducendo i costi del 40-70%), e i pattern per ottimizzare l'utilizzo GPU nei workload di inferenza in produzione.

Cosa Imparerai

  • Installazione e configurazione del NVIDIA Device Plugin per Kubernetes
  • Schedulare Pod con richiesta GPU (nvidia.com/gpu resource)
  • Training distribuito con PyTorchJob e TFJob (Kubeflow Training Operator)
  • Karpenter NodePool per provisioning automatico di nodi GPU spot
  • GPU time-slicing per condividere GPU tra piu Pod
  • MIG (Multi-Instance GPU) per partizione di GPU A100/H100
  • Monitoring GPU con DCGM Exporter e Grafana
  • Pattern per inferenza ad alta throughput con TorchServe su K8s

Architettura GPU su Kubernetes

Kubernetes non conosce nativamente le GPU. Le GPU vengono esposte al cluster tramite il Device Plugin Framework: un DaemonSet che gira su ogni nodo con GPU, si registra presso il kubelet, e gestisce l'allocazione delle GPU ai container. Il NVIDIA Device Plugin e l'implementazione piu diffusa di questo framework.

Installazione NVIDIA Device Plugin

# Pre-requisiti: NVIDIA GPU drivers installati sui nodi
# Verifica driver sui nodi
kubectl get nodes -l accelerator=nvidia
kubectl describe node gpu-node-1 | grep -i nvidia

# Installa NVIDIA Device Plugin con Helm
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update

helm install nvdp nvdp/nvidia-device-plugin \
  --namespace kube-system \
  --version 0.16.0 \
  --set failOnInitError=false

# Oppure con manifest diretto
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.16.0/deployments/static/nvidia-device-plugin.yml

# Verifica che le GPU siano visibili nel cluster
kubectl get nodes -o json | jq '.items[].status.allocatable | select(."nvidia.com/gpu" != null)'
# Output: { "nvidia.com/gpu": "8" }  per un nodo con 8 GPU A100

Installazione NVIDIA GPU Operator (Approccio Consigliato)

Per cluster in produzione, il GPU Operator di NVIDIA gestisce automaticamente tutti i componenti necessari: driver, device plugin, container runtime, DCGM Exporter per monitoring:

# Installa GPU Operator
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator \
  --create-namespace \
  --version v24.9.0 \
  --set driver.enabled=true \
  --set mig.strategy=single \
  --set dcgmExporter.enabled=true \
  --set dcgmExporter.serviceMonitor.enabled=true

# Verifica installazione
kubectl get pods -n gpu-operator
# Attendi che tutti i pod siano Running
kubectl wait --for=condition=ready pod -l app=nvidia-device-plugin-daemonset -n gpu-operator --timeout=300s

# Verifica GPU allocabili
kubectl describe node gpu-node-1 | grep -A 5 "Allocatable:"
# nvidia.com/gpu: 8

Schedulare Pod con GPU

Una volta che il Device Plugin e attivo, puoi richiedere GPU nei manifest come qualsiasi altra risorsa Kubernetes. La differenza: non si usa requests, solo limits per le GPU (Kubernetes garantisce sempre esattamente il numero di GPU richiesto).

# pod-gpu-basic.yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-test
spec:
  restartPolicy: OnFailure
  containers:
    - name: inference
      image: nvcr.io/nvidia/pytorch:24.01-py3
      command: ["python3", "-c"]
      args:
        - |
          import torch
          print(f"CUDA available: {torch.cuda.is_available()}")
          print(f"GPU count: {torch.cuda.device_count()}")
          print(f"GPU name: {torch.cuda.get_device_name(0)}")
          x = torch.rand(1000, 1000).cuda()
          print(f"Tensor on GPU: {x.device}")
      resources:
        limits:
          nvidia.com/gpu: "1"    # richiedi 1 GPU
          memory: "16Gi"
          cpu: "4"
        requests:
          memory: "16Gi"
          cpu: "4"
      volumeMounts:
        - name: model-storage
          mountPath: /models
  volumes:
    - name: model-storage
      persistentVolumeClaim:
        claimName: model-pvc
  nodeSelector:
    accelerator: "nvidia-a100"  # schedule solo su nodi A100
  tolerations:
    - key: "nvidia.com/gpu"
      operator: "Exists"
      effect: "NoSchedule"

Training Distribuito con Kubeflow Training Operator

Il training di modelli grandi richiede spesso piu GPU su piu nodi. Il Kubeflow Training Operator gestisce i job di training distribuito con PyTorchJob, TFJob, MXJob e MPIJob. Installa prima l'operator:

# Installa Training Operator
kubectl apply -k "github.com/kubeflow/training-operator/manifests/overlays/standalone?ref=v1.8.0"

# Verifica
kubectl get pods -n kubeflow
kubectl get crd | grep kubeflow

PyTorchJob per Training Multi-GPU Multi-Node

# pytorch-distributed-training.yaml
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: llm-finetuning-job
  namespace: ml-training
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      restartPolicy: OnFailure
      template:
        spec:
          containers:
            - name: pytorch
              image: company.registry.io/training:llm-v2.1
              command:
                - python3
                - -m
                - torch.distributed.run
                - --nproc_per_node=8
                - --nnodes=4
                - --node_rank=$(RANK)
                - --master_addr=$(MASTER_ADDR)
                - --master_port=23456
                - train_llm.py
                - --model=llama-7b
                - --dataset=/data/training_set
                - --batch-size=32
                - --epochs=3
                - --output=/models/finetuned
              env:
                - name: NCCL_DEBUG
                  value: "INFO"
                - name: NCCL_SOCKET_IFNAME
                  value: "eth0"
              resources:
                limits:
                  nvidia.com/gpu: "8"
                  memory: "120Gi"
                  cpu: "32"
                requests:
                  memory: "120Gi"
                  cpu: "32"
              volumeMounts:
                - name: training-data
                  mountPath: /data
                - name: model-output
                  mountPath: /models
                - name: shm
                  mountPath: /dev/shm
          volumes:
            - name: training-data
              persistentVolumeClaim:
                claimName: training-dataset-pvc
            - name: model-output
              persistentVolumeClaim:
                claimName: model-output-pvc
            - name: shm
              emptyDir:
                medium: Memory
                sizeLimit: "64Gi"  # shared memory per NCCL
          nodeSelector:
            accelerator: "nvidia-a100-80gb"
          tolerations:
            - key: "nvidia.com/gpu"
              operator: "Exists"
              effect: "NoSchedule"
    Worker:
      replicas: 3  # 3 worker + 1 master = 4 nodi, 32 GPU totali
      restartPolicy: OnFailure
      template:
        spec: # stesso spec del Master...
          containers:
            - name: pytorch
              image: company.registry.io/training:llm-v2.1
              resources:
                limits:
                  nvidia.com/gpu: "8"
                  memory: "120Gi"
                  cpu: "32"

Karpenter per Provisioning di Nodi GPU Spot

Le GPU sono la risorsa piu costosa in cloud. Le istanze GPU spot costano 60-70% in meno rispetto alle on-demand. Karpenter gestisce il provisioning automatico di nodi GPU spot, con fallback su on-demand in caso di interruzione:

# karpenter-gpu-nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-spot
spec:
  template:
    metadata:
      labels:
        role: gpu-worker
        accelerator: nvidia
    spec:
      nodeClassRef:
        apiVersion: karpenter.k8s.aws/v1
        kind: EC2NodeClass
        name: gpu-nodeclass
      requirements:
        # Tipologie di istanze GPU AWS
        - key: node.kubernetes.io/instance-type
          operator: In
          values:
            - p4d.24xlarge     # 8x A100 80GB
            - p3.8xlarge       # 4x V100
            - g5.12xlarge      # 4x A10G
            - g4dn.12xlarge    # 4x T4
        # Preferisci spot
        - key: karpenter.sh/capacity-type
          operator: In
          values:
            - spot
            - on-demand  # fallback
        - key: kubernetes.io/os
          operator: In
          values:
            - linux
      taints:
        - key: nvidia.com/gpu
          value: "true"
          effect: NoSchedule
  limits:
    nvidia.com/gpu: 256   # max 256 GPU totali nel cluster
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30m  # rimuovi nodi GPU spot quando il job finisce
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: gpu-nodeclass
spec:
  amiFamily: AL2
  role: KarpenterNodeRole
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  instanceStorePolicy: RAID0  # usa i dischi NVMe locali per storage temporaneo
  userData: |
    #!/bin/bash
    # Installa NVIDIA drivers al primo boot
    /etc/eks/bootstrap.sh my-cluster
    nvidia-smi  # verifica GPU disponibili

GPU Time-Slicing: Condividere una GPU tra Piu Pod

Per workload di inferenza leggeri o development, una GPU intera e spesso sprecata. Il GPU time-slicing permette di condividere una GPU fisica tra piu Pod, ognuno dei quali vede una "GPU virtuale" con una fetta del tempo di calcolo:

# gpu-time-slicing-config.yaml
# Configura il Device Plugin per time-slicing
apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        renameByDefault: false
        failRequestsGreaterThanOne: false
        resources:
          - name: nvidia.com/gpu
            replicas: 4   # ogni GPU fisica diventa 4 "GPU" logiche
---
# Applica il config all'operator
kubectl patch clusterpolicy gpu-cluster-policy \
  -n gpu-operator \
  --type merge \
  -p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config"}}}}'

# Verifica: ogni nodo con 1 GPU A100 ora mostra 4 GPU allocabili
kubectl describe node gpu-node-1 | grep nvidia.com/gpu
# Allocatable:
#   nvidia.com/gpu:  4

# Pod che usa 1/4 di GPU
apiVersion: v1
kind: Pod
metadata:
  name: inference-small
spec:
  containers:
    - name: model-server
      image: company.registry.io/inference:v1
      resources:
        limits:
          nvidia.com/gpu: "1"  # ottiene 1/4 della GPU fisica

MIG: Multi-Instance GPU per A100 e H100

Le GPU NVIDIA A100 e H100 supportano MIG (Multi-Instance GPU), che partiziona la GPU in istanze hardware isolate (non solo time-sharing). Ogni istanza MIG ha memoria e compute garantiti e non interferisce con le altre:

# Configura MIG sul nodo (eseguito sul nodo GPU, non da kubectl)
# Richiede: driver NVIDIA >= 525, GPU A100 o H100

# Abilita MIG mode sulla GPU
sudo nvidia-smi -mig 1

# Crea 7 istanze MIG da 1/7 di A100 (1g.10gb)
sudo nvidia-smi mig -cgip -p 0,9  # Profile 9 = MIG 1g.10gb

# Verifica istanze create
sudo nvidia-smi mig -lgi
# +-------------------------------------------------------+
# | GPU instances:                                         |
# | GPU   Name             Profile  Instance   Placement  |
# |                        ID       ID         Start:Size |
# |=======================================================|
# |   0  MIG 1g.10gb       9        1          0:1        |
# |   0  MIG 1g.10gb       9        2          1:1        |
# |   0  MIG 1g.10gb       9        3          2:1        |
# ... (7 istanze totali)

# Nel cluster Kubernetes, configurare MIG Strategy nel GPU Operator
kubectl patch clusterpolicy gpu-cluster-policy \
  -n gpu-operator \
  --type json \
  -p '[{"op":"replace","path":"/spec/mig/strategy","value":"mixed"}]'

# Pod che richiede specifica istanza MIG
apiVersion: v1
kind: Pod
metadata:
  name: inference-mig
spec:
  containers:
    - name: model
      image: nvcr.io/nvidia/pytorch:24.01-py3
      resources:
        limits:
          nvidia.com/mig-1g.10gb: "1"  # richiedi 1 istanza MIG 1g.10gb

Monitoring GPU con DCGM Exporter

Il monitoring delle GPU e fondamentale per capire se i job di training sono efficienti e per il FinOps. DCGM Exporter espone metriche GPU a Prometheus:

# DCGM Exporter viene installato automaticamente con GPU Operator
# Verifica che le metriche siano disponibili
kubectl port-forward svc/gpu-operator-dcgm-exporter 9400:9400 -n gpu-operator &
curl -s localhost:9400/metrics | grep DCGM_FI

# Metriche chiave da monitorare:
# DCGM_FI_DEV_GPU_UTIL        - utilizzo GPU (0-100%)
# DCGM_FI_DEV_MEM_COPY_UTIL   - utilizzo memoria GPU
# DCGM_FI_DEV_FB_USED         - memoria GPU usata (MB)
# DCGM_FI_DEV_POWER_USAGE     - consumo energetico (W)
# DCGM_FI_DEV_SM_CLOCK        - clock streaming multiprocessor
# DCGM_FI_DEV_GPU_TEMP        - temperatura GPU

# Alert: GPU sottoutilizzata (< 50% per 30 minuti = spreco)
- alert: GPUUnderutilized
  expr: DCGM_FI_DEV_GPU_UTIL < 50
  for: 30m
  labels:
    severity: warning
  annotations:
    summary: "GPU {{ $labels.gpu }} sul nodo {{ $labels.Hostname }} utilization < 50%"
    description: "Valuta se il job puo essere terminato o ottimizzato"

# Dashboard Grafana: importa ID 12239 (NVIDIA DCGM Exporter Dashboard)

Deployment di Modelli per Inferenza con TorchServe

Per l'inferenza in produzione, serve un model server che gestisca il load balancing tra piu replica del modello, il batching delle richieste, e il versioning. TorchServe e la soluzione ufficiale PyTorch:

# torchserve-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: model-inference
  namespace: ml-inference
spec:
  replicas: 3   # 3 replica per alta disponibilita
  selector:
    matchLabels:
      app: model-inference
  template:
    metadata:
      labels:
        app: model-inference
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8082"   # TorchServe metrics port
    spec:
      containers:
        - name: torchserve
          image: pytorch/torchserve:0.11.0-gpu
          args:
            - torchserve
            - --start
            - --model-store=/models
            - --models=text-classifier=bert-classifier.mar
            - --ts-config=/config/config.properties
          ports:
            - containerPort: 8080  # inference API
            - containerPort: 8081  # management API
            - containerPort: 8082  # metrics
          resources:
            limits:
              nvidia.com/gpu: "1"
              memory: "16Gi"
              cpu: "4"
            requests:
              memory: "8Gi"
              cpu: "2"
          readinessProbe:
            httpGet:
              path: /ping
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /ping
              port: 8080
            initialDelaySeconds: 120
            periodSeconds: 30
          volumeMounts:
            - name: model-store
              mountPath: /models
            - name: ts-config
              mountPath: /config
      volumes:
        - name: model-store
          persistentVolumeClaim:
            claimName: model-store-pvc
        - name: ts-config
          configMap:
            name: torchserve-config
      tolerations:
        - key: "nvidia.com/gpu"
          operator: "Exists"
          effect: "NoSchedule"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: torchserve-config
  namespace: ml-inference
data:
  config.properties: |
    inference_address=http://0.0.0.0:8080
    management_address=http://0.0.0.0:8081
    metrics_address=http://0.0.0.0:8082
    number_of_gpu=1
    batch_size=32
    max_batch_delay=100   # ms: attendi fino a 100ms per fare batching
    max_response_size=6553500
    install_py_dep_per_model=true
---
# HPA basato su latenza con KEDA (event-driven autoscaling)
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: inference-scaler
  namespace: ml-inference
spec:
  scaleTargetRef:
    name: model-inference
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: torchserve_queue_latency_microseconds
        threshold: "100000"  # 100ms di coda = scala up
        query: avg(torchserve_queue_latency_microseconds{model_name="bert-classifier"})

Best Practices per Workload AI su Kubernetes

Ottimizzazione dei Costi GPU

  • Spot per training, on-demand per inferenza: Il training puo gestire interruzioni con checkpoint; l'inferenza deve essere sempre disponibile
  • Checkpoint frequenti: Salva checkpoint ogni 30 minuti per riprendere il training dopo un'interruzione spot
  • Time-slicing per development: Usa GPU time-slicing per i developer, MIG o GPU intere per produzione
  • Scale-to-zero: Karpenter rimuove i nodi GPU spot quando il training finisce - non paghi per GPU idle
  • Batching nella inferenza: TorchServe con batch_size=32 aumenta throughput 10-20x rispetto a richieste singole
  • Profile prima di deployare: Usa NVIDIA Nsight per profilare il job di training e identificare inefficienze

Errori Comuni con GPU su Kubernetes

  • Container senza taint toleration: I nodi GPU hanno il taint nvidia.com/gpu=true:NoSchedule; senza toleration il Pod non viene schedulato sul nodo GPU
  • Mancato isolamento della memoria: La GPU non isola la memoria tra container come fa la CPU. Se allochi 1 GPU ma il modello usa piu memoria di quanto disponibile, il job crasha con CUDA OOM
  • NCCL senza shared memory: Il training distribuito PyTorch usa NCCL che richiede /dev/shm grande (tipicamente 10-60GB); configura sempre un emptyDir con medium: Memory
  • Non monitorare GPU utilization: Una GPU a 20% di utilizzo e uno spreco enorme. Il DCGM dashboard dovrebbe essere il primo posto da guardare dopo ogni deploy

Conclusioni e Prossimi Passi

Kubernetes e diventato la piattaforma standard per i workload AI/ML non per caso: la sua astrazione delle risorse, il sistema di scheduling avanzato e l'ecosistema di operator (Kubeflow, Training Operator) lo rendono il contesto ideale per orchestrare sia il training che l'inferenza a scala. Con Karpenter che gestisce automaticamente il provisioning di nodi GPU spot, il costo di un job di training puo essere ridotto del 40-70% rispetto all'uso di istanze on-demand.

Il prossimo passo e integrare questi workload con una pipeline MLOps completa: registrazione dei modelli con MLflow, gestione dei dataset con DVC, CI/CD per il retraining automatico. L'articolo FinOps per Kubernetes (articolo 9 di questa serie) approfondisce come misurare e ottimizzare il costo totale dei workload GPU nel cluster.

Serie Correlate

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti