Scalare ML pe Kubernetes: implementare și orchestrare în producție
Modelul dvs. de învățare automată a trecut toate testele offline, valorile sunt excelente, servirea prin FastAPI funcționează perfect local. Apoi vine momentul critic: trebuie să te descurci 10.000 de solicitări pe secundă, scalate dinamic în funcție de încărcare, asigură o disponibilitate ridicată și zero timpi de nefuncționare în timpul actualizărilor. Un singur recipient nu mai este suficient.
Kubernetes și a devenit standardul de facto pentru orchestrarea sarcinilor de lucru ML în producție, 78% dintre organizațiile întreprinderilor îl folosesc pentru implementarea modelelor conform CNCF Survey 2025. Cu toate acestea, nu este suficient să puneți modelul într-un Pod: trebuie gestionat Programare GPU, scalare automată bazată pe evenimente, cote de resurse, implementări Canary și monitorizare specializat pentru inferenţă. Piața MLOps, care va valora 4,38 miliarde USD în 2026, cu un CAGR de 39,8%, are principalul motor de scalare în Kubernetes.
În acest articol, explorăm arhitectura completă pentru aducerea modelelor ML în Kubernetes producție: de la KServe și Seldon Core pentru servirea inferențelor, la programarea GPU cu dispozitivul NVIDIA Pluginuri, la autoscaling inteligent cu HPA, VPA și KEDA, până la monitorizare cu Prometheus și Grafana.
Ce vei învăța
- de ce Kubernetes este standardul pentru ML în producție și când să-l folosească
- Programarea și partajarea GPU cu NVIDIA Device Plugin și MIG
- KServe: implementare InferenceService cu lansare canary și scalare la zero
- Seldon Core v2: conducte de deservire componabile și cu mai multe modele
- Autoscaling avansat: HPA, VPA și KEDA pentru sarcini de lucru ML bazate pe evenimente
- Managementul resurselor: cereri, limite, clase de prioritate și afinitate la nod
- Monitorizare cu Prometheus + Grafana specializată pentru inferența ML
- Optimizarea costurilor și cele mai bune practici pentru clusterele GPU
de ce Kubernetes pentru ML în producție
Înainte de a te scufunda în configurația tehnică, este esențial să înțelegem de ce Kubernetes S-a impus ca platformă de referință pentru sarcinile de lucru ML, depășind soluții precum bare metal, VM-uri dedicate sau servicii cloud proprietare.
Sarcinile de lucru ML au caracteristici unice în comparație cu aplicațiile web tradiționale. Antrenamentul necesită GPU-uri masive pentru ore sau zile, apoi resursele trebuie eliberate. Deducerea are vârfuri imprevizibile și latență critică. Modelele trebuie actualizate fără timp de nefuncționare. Seturile de date pot fi uriași și necesită depozitare specializată. Kubernetes abordează toate aceste scenarii cu primitive native: programare pod pe anumite noduri GPU, volume persistente per set de date, joburi pentru antrenament pe lot, HorizontalPodAutoscaler pentru scalarea inferenței.
Kubernetes vs alternativă gestionată în cloud
Kubernetes autogestionat sau gestionat (EKS, GKE, AKS): control complet,
portabilitate multi-cloud, costuri optimizabile, dar complexitate operațională ridicată.
SageMaker / Vertex AI / Azure ML: configurare rapidă, integrare nativă în cloud,
dar blocarea furnizorului, costuri mai mari pe termen lung și mai puțină flexibilitate pentru arhitecturile personalizate.
Regula generală: echipa <5 persoane sau buget limitat? Începeți cu ML gestionat.
Echipa >5 cu mai multe modele în producție? Kubernetes vă plătește investiția în 6-12 luni.
Arhitectură de referință
Un cluster Kubernetes pentru ML în producție este de obicei structurat pe trei straturi distincte, fiecare cu responsabilități bine definite:
- Stratul de infrastructură: Noduri CPU pentru servire ușoară și orchestrare, noduri GPU pentru antrenament și inferență grea, noduri de stocare pentru seturi de date și artefacte. Pool-urile GPU sunt separate cu etichete de noduri specifice.
- Stratul platformei: KServe sau Seldon Core pentru servirea inferenței, Kubeflow pentru canal de instruire, MLflow pentru urmărirea experimentală (vezi articolul 4), Argo Workflows pentru orchestrație complexă.
- Stratul de observabilitate: Prometheus pentru metrici, Grafana pentru tablouri de bord, Jaeger pentru urmărirea distribuită, Loki pentru agregarea jurnalelor.
# Namespace structure per ML cluster
# Separare ambienti e responsabilità
kubectl create namespace ml-training # Job di training
kubectl create namespace ml-serving # Inference services
kubectl create namespace ml-monitoring # Prometheus, Grafana
kubectl create namespace mlflow # Experiment tracking
kubectl create namespace kubeflow # Pipeline orchestration
# Label nodi per GPU scheduling
kubectl label nodes gpu-node-1 accelerator=nvidia-a100
kubectl label nodes gpu-node-2 accelerator=nvidia-t4
kubectl label nodes cpu-node-1 workload=inference-cpu
Programare și partajare GPU
GPU-urile sunt cea mai scumpă resursă a clusterului ML. A le gestiona prost înseamnă a irosi zeci de
mii de euro pe lună. Kubernetes expune GPU-urile ca resurse programabile prin intermediul
Plugin-uri pentru dispozitive NVIDIA, un DaemonSet care detectează automat GPU-urile pe noduri
și le înregistrează ca nvidia.com/gpu în kubelet.
# Installazione NVIDIA Device Plugin via Helm
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
helm upgrade -i nvdp nvdp/nvidia-device-plugin \
--namespace kube-system \
--set failOnInitError=false
# Verifica GPU disponibili sui nodi
kubectl describe nodes | grep -A5 "Allocatable:"
# Output atteso:
# nvidia.com/gpu: 8
# cpu: 96
# memory: 768Gi
# Pod che richiede 1 GPU intera
apiVersion: v1
kind: Pod
metadata:
name: ml-training-job
spec:
containers:
- name: trainer
image: pytorch/pytorch:2.5.0-cuda12.4-cudnn9-runtime
resources:
limits:
nvidia.com/gpu: 1 # Richiede 1 GPU intera
cpu: "8"
memory: "32Gi"
requests:
nvidia.com/gpu: 1
cpu: "4"
memory: "16Gi"
nodeSelector:
accelerator: nvidia-a100 # Forza scheduling su A100
Pentru sarcinile de lucru de inferență care nu necesită un GPU complet, NVIDIA oferă două strategii Partajare GPU: Tăierea timpului e GPU cu mai multe instanțe (MIG).
# Configurazione Time-Slicing (per GPU T4/V100, sharing software)
# Ogni GPU fisica viene divisa in N repliche logiche
apiVersion: v1
kind: ConfigMap
metadata:
name: time-slicing-config
namespace: kube-system
data:
any: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
replicas: 4 # 4 pod condividono 1 GPU fisica
failRequestsGreaterThanOne: false
# Apply al device plugin
kubectl patch clusterpolicies/cluster-policy \
-n gpu-operator --type merge \
-p '{"spec": {"devicePlugin": {"config": {"name": "time-slicing-config"}}}}'
# Configurazione MIG per A100/H100 (hardware isolation)
# Partiziona A100 in 7 istanze MIG da 10GB ciascuna
nvidia-smi mig -cgi 9,9,9,9,9,9,9 -C
# Pod che usa una slice MIG
resources:
limits:
nvidia.com/mig-1g.10gb: 1 # Usa 1 istanza MIG da 10GB
Time-slicing vs MIG: Când să folosiți care
Timp de tăiere: potrivit pentru sarcini de lucru cu inferență ușoară (modele VRAM <2GB), introduce latența de la schimbarea contextului. Funcționează pe orice GPU NVIDIA. MIG: izolare hardware completă, memorie dedicată garantată, zero interferențe între sarcinile de lucru. Disponibil numai pe A100, A30, H100. Ideal pentru SLA-uri de latență stricte. Nu combina niciodată cele două abordări pe același nod.
KServe: Servire de inferență nativă pe Kubernetes
KServe (fost KFServing) și standardul CNCF pentru servirea inferențelor pe Kubernetes,
născut din colaborarea dintre Google, IBM, Bloomberg și alții. Oferă o abstractizare
InferenceService care ascunde complexitatea implementării, gestionând automat
lansare canary, scalare la zero, scalare automată bazată pe cereri și suport pentru mai multe cadre
(PyTorch, TensorFlow, scikit-learn, XGBoost, ONNX, Hugging Face).
# Installazione KServe (versione 0.13+)
kubectl apply -f https://github.com/kserve/kserve/releases/download/v0.13.0/kserve.yaml
# Verifica installazione
kubectl get pods -n kserve
# kserve-controller-manager-xxx Running
# kserve-gateway-xxx Running
# InferenceService per modello scikit-learn
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "churn-predictor"
namespace: ml-serving
annotations:
serving.kserve.io/enable-prometheus-scraping: "true"
spec:
predictor:
minReplicas: 1
maxReplicas: 10
scaleTarget: 50 # Target: 50 req/sec per replica
scaleMetric: rps # Scala in base a requests-per-second
sklearn:
storageUri: "gs://my-ml-bucket/models/churn-model/v3"
runtimeVersion: "1.5.2"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
Unul dintre punctele forte ale KServe este suportul nativ pentru i Lansare Canary: puteți trimite un procent din trafic către noua versiune a șablonului în timp ce restul continua sa il folosesti pe cel stabil, exact ca si testarea A/B pe care am vazut-o in articol precedentul din această serie.
# Canary Rollout: 20% traffico alla nuova versione
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "churn-predictor"
namespace: ml-serving
spec:
predictor:
# Versione stabile (80% traffico)
minReplicas: 2
maxReplicas: 8
sklearn:
storageUri: "gs://my-ml-bucket/models/churn-model/v3"
runtimeVersion: "1.5.2"
canaryTrafficPercent: 80
# Versione canary (20% traffico)
predictor:
# NOTA: nella specifica KServe, il canary si gestisce con
# l'annotation traffic-split
# Ecco la sintassi corretta tramite Knative revisions:
---
# Alternativa: InferenceGraph per traffic splitting esplicito
apiVersion: "serving.kserve.io/v1alpha1"
kind: "InferenceGraph"
metadata:
name: "churn-ab-split"
namespace: ml-serving
spec:
nodes:
root:
routerType: WeightedEnsemble
routes:
- serviceName: churn-predictor-v3
weight: 80
- serviceName: churn-predictor-v4
weight: 20
# Test dell'endpoint
curl -X POST \
http://churn-predictor.ml-serving.svc.cluster.local/v1/models/churn-predictor:predict \
-H 'Content-Type: application/json' \
-d '{"instances": [[35, 12000, 2, 1, 0.8, 3]]}'
Caracteristica de scară la zero de KServe (bazat pe Knative Serving) e deosebit de valoroasă pentru modelele folosite ocazional: podul se oprește după o perioadă timp de inactivitate configurabil și repornește automat la prima solicitare, cu pornire la rece de obicei, mai puțin de 30 de secunde pentru modelele pre-cache.
# Configurazione scale-to-zero con timeout personalizzato
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "batch-analyzer"
namespace: ml-serving
annotations:
# Scale-to-zero dopo 5 minuti di inattivita
autoscaling.knative.dev/scaleToZeroGracePeriod: "300s"
# Window per calcolo scale-up
autoscaling.knative.dev/window: "60s"
# Utilization target (in percentuale)
autoscaling.knative.dev/targetUtilizationPercentage: "70"
spec:
predictor:
minReplicas: 0 # Abilita scale-to-zero
maxReplicas: 5
pytorch:
storageUri: "gs://my-ml-bucket/models/analyzer/v1"
runtimeVersion: "2.5.0"
Seldon Core v2: Composable ML Pipelines
În timp ce KServe excelează în deservirea modelelor individuale, Seldon Core v2 strălucește în gestionarea arhitecturilor ML complexe: conducte în mai multe etape, ansambluri de modele, Rutare A/B cu logica de afaceri și integrare cu Kafka pentru procesarea fluxului. Seldon v2 folosește MLServer ca timp de rulare a inferenței, compatibil cu protocolul V2 (KFServing Inference Protocol) și acceptă nativ PyTorch, scikit-learn, Hugging Face și modele personalizate.
# Installazione Seldon Core v2 via Helm
helm repo add seldonio https://storage.googleapis.com/seldon-charts
helm install seldon-core-v2 seldonio/seldon-core-v2 \
--namespace seldon-mesh \
--create-namespace \
--set controller.clusterwide=true
# Model: singolo modello XGBoost
apiVersion: mlops.seldon.io/v1alpha1
kind: Model
metadata:
name: churn-xgb
namespace: ml-serving
spec:
storageUri: "gs://my-ml-bucket/models/churn-xgb/v2"
requirements:
- xgboost
memory: 100Mi
# Pipeline: preprocessing + prediction + postprocessing
apiVersion: mlops.seldon.io/v1alpha1
kind: Pipeline
metadata:
name: churn-pipeline
namespace: ml-serving
spec:
steps:
- name: preprocessor
inputs:
- churn-pipeline.inputs
- name: churn-xgb
inputs:
- preprocessor.outputs
- name: postprocessor
inputs:
- churn-xgb.outputs
output:
steps:
- postprocessor
# Autoscaling basato su RPS
replicas: 1
scaling:
replicas:
minReplicas: 1
maxReplicas: 8
metric: rps
target: 100
KServe vs Seldon Core: pe care să alegeți
- KServe: cel mai bun pentru modele individuale, integrare cu Knative și Istio, scala nativă la zero, comunitatea CNCF. Ideal pentru echipele care încep cu K8s ML.
- Seldon Core v2: cel mai bun pentru conducte complexe, ansambluri, integrare Kafka, servire cu mai multe modele. Ideal pentru arhitecturi ML avansate cu rutare logica de afaceri.
- Ambele: suport protocol V2, monitorizare Prometheus, Triton pentru servirea GPU. Nu se exclud reciproc, unele organizații le folosesc împreună pentru diferite cazuri de utilizare.
Autoscaling inteligent: HPA, VPA și KEDA
Scalarea sarcinilor de lucru ML este mai complexă decât aplicațiile web tradiționale. Valorile CPU și memoriei nu reflectă adesea cu exactitate sarcina reală a unui model: un model legat de GPU poate satura placa grafică în timp ce procesorul este la 80% inactiv. Kubernetes oferă trei mecanisme de autoscaling complementare care, utilizate corect împreună, acestea acoperă toate scenariile ML.
# HPA (Horizontal Pod Autoscaler): scala il numero di repliche
# Configurazione per inference service basata su custom metrics
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: churn-predictor-hpa
namespace: ml-serving
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: churn-predictor
minReplicas: 2
maxReplicas: 20
metrics:
# Scala su CPU (fallback)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# Scala su custom metric: inference latency P99
- type: Pods
pods:
metric:
name: inference_request_duration_p99
target:
type: AverageValue
averageValue: "500m" # 500ms P99 latency target
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # Reazione rapida al traffico
policies:
- type: Percent
value: 100
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300 # Lento a rimuovere pod (warm models)
# VPA (Vertical Pod Autoscaler): ottimizza resources requests/limits
# NOTA: non usare VPA e HPA sulle stesse metriche CPU/Memory!
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: batch-trainer-vpa
namespace: ml-training
spec:
targetRef:
apiVersion: batch/v1
kind: Job
name: model-training
updatePolicy:
updateMode: "Off" # Solo raccomandazioni, non applica auto (Off | Initial | Recreate)
resourcePolicy:
containerPolicies:
- containerName: trainer
minAllowed:
cpu: "1"
memory: 4Gi
maxAllowed:
cpu: "16"
memory: 128Gi
controlledResources: ["cpu", "memory"]
# Leggi le raccomandazioni VPA
kubectl describe vpa batch-trainer-vpa
# Output:
# Recommendation:
# Container Recommendations:
# Container Name: trainer
# Lower Bound: cpu: 2, memory: 8Gi
# Target: cpu: 6, memory: 32Gi
# Upper Bound: cpu: 12, memory: 64Gi
KEDA (Kubernetes Event-Driven Autoscaler, proiect absolvit CNCF) și iată Cel mai puternic instrument pentru sarcinile de lucru ML bazate pe evenimente: scalați podurile pe baza evenimentelor din cozile de mesaje, baze de date, metrici Prometheus sau declanșatoare HTTP, permițând de asemenea scalare la zero pentru procesarea în lot.
# KEDA: scala i worker ML in base alla coda di inference requests
# Installazione
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
# ScaledObject per batch ML processing da RabbitMQ
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: ml-batch-processor-scaler
namespace: ml-serving
spec:
scaleTargetRef:
name: batch-ml-processor
minReplicaCount: 0 # Scale-to-zero quando coda vuota
maxReplicaCount: 30 # Max 30 worker per GPU cluster
pollingInterval: 15 # Controlla la coda ogni 15 secondi
cooldownPeriod: 60 # Aspetta 60s prima di scale-down
triggers:
- type: rabbitmq
metadata:
host: amqp://rabbitmq.ml-serving.svc.cluster.local
queueName: inference-requests
mode: QueueLength
value: "10" # 1 pod ogni 10 messaggi in coda
# Trigger alternativo: Prometheus metric
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc.cluster.local:9090
metricName: inference_queue_depth
query: sum(inference_queue_depth{namespace="ml-serving"})
threshold: "50" # 1 replica ogni 50 richieste pendenti
Autoscaling model pentru ML: Recomandare
Combină cele trei mecanisme cu responsabilități distincte:
- HPA pe latență/RPS pentru inferență online (reactiv, rapid)
- VPA în modul Oprit pentru a optimiza cererile de locuri de muncă de formare (consultați și actualizați manual)
- KEDA pentru procesare în loturi și conducte bazate pe evenimente (scale la zero inclus)
Nu utilizați HPA și VPA pe aceeași resursă (CPU/Memorie) în același timp: conflicte
scalarea cauzează fluctuații imprevizibile și risipă de resurse.
Managementul resurselor și clasele prioritare
Pe un cluster partajat între diferite echipe ML, gestionarea resurselor este esențială împiedică o muncă de formare să blocheze inferența în producție sau un experiment consumați toate GPU-urile disponibile. Kubernetes oferă trei instrumente: Cota de resurse, LimitRange e PriorityClass.
# ResourceQuota: limita risorse per namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: ml-serving-quota
namespace: ml-serving
spec:
hard:
requests.cpu: "40"
requests.memory: 160Gi
limits.cpu: "80"
limits.memory: 320Gi
requests.nvidia.com/gpu: "4" # Max 4 GPU per inference namespace
limits.nvidia.com/gpu: "4"
pods: "50"
---
# LimitRange: imposta defaults e limiti per singolo container
apiVersion: v1
kind: LimitRange
metadata:
name: ml-container-limits
namespace: ml-serving
spec:
limits:
- type: Container
default: # Default limits se non specificati
cpu: "2"
memory: 4Gi
defaultRequest: # Default requests se non specificati
cpu: "500m"
memory: 1Gi
max: # Massimo per container
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "2"
min: # Minimo per container
cpu: "100m"
memory: 256Mi
---
# PriorityClass: garantisce che l'inferenza non venga preemptata dal training
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: ml-inference-critical
value: 1000000 # Alta priorità per serving
globalDefault: false
description: "Inference services critici - non preemptabili"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: ml-training-batch
value: 100000 # Bassa priorità per training
preemptionPolicy: PreemptLowerPriority
description: "Training jobs - preemptabili se necessario"
Il Afinitatea nodului si eu Vizualizări/Tolerări vă permit să controlați cu precizie pe ce noduri sunt programate sarcinile de lucru ML, asigurând că locurile de muncă de formare nu concurează cu inferența pe aceleași GPU-uri:
# Taint nodi GPU dedicati all'inferenza
kubectl taint nodes gpu-inference-1 dedicated=inference:NoSchedule
kubectl taint nodes gpu-inference-2 dedicated=inference:NoSchedule
# Solo pod con toleration possono usare questi nodi
# InferenceService spec con affinity e toleration
spec:
predictor:
tolerations:
- key: dedicated
operator: Equal
value: inference
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator
operator: In
values:
- nvidia-a100
- nvidia-h100
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: churn-predictor
topologyKey: kubernetes.io/hostname
# Distribuisce repliche su host diversi per HA
Monitorizare cu Prometheus și Grafana pentru ML
Monitorizarea unui sistem ML pe Kubernetes necesită valori la două niveluri: valori Infrastructura standard Kubernetes (CPU, memorie, rețea) și metrici specifice de inferență ML (latența la model, debitul, rata de eroare, semnalul de deriva de date). KServe și Seldon expun automat valorile Prometheus în format standard.
# Configurazione Prometheus per scraping KServe metrics
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kserve-inference-monitor
namespace: ml-monitoring
labels:
release: prometheus
spec:
namespaceSelector:
matchNames:
- ml-serving
selector:
matchLabels:
serving.kserve.io/inferenceservice: "true"
endpoints:
- port: metrics
interval: 15s
path: /metrics
honorLabels: true
# Metriche KServe esposte automaticamente:
# kserve_inference_request_total{model_name, namespace, status_code}
# kserve_inference_request_duration_seconds{model_name, quantile}
# kserve_inference_request_size_bytes{model_name}
# kserve_inference_response_size_bytes{model_name}
# PrometheusRule: alert per latenza elevata
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ml-inference-alerts
namespace: ml-monitoring
spec:
groups:
- name: ml-inference.rules
rules:
- alert: HighInferenceLatency
expr: |
histogram_quantile(0.99,
rate(kserve_inference_request_duration_seconds_bucket[5m])
) > 1.0
for: 5m
labels:
severity: warning
annotations:
summary: "P99 latency > 1s per {{ $labels.model_name }}"
description: "Modello {{ $labels.model_name }} ha latenza P99 di {{ $value }}s"
- alert: ModelErrorRateHigh
expr: |
rate(kserve_inference_request_total{status_code!="200"}[5m])
/ rate(kserve_inference_request_total[5m]) > 0.05
for: 2m
labels:
severity: critical
annotations:
summary: "Error rate > 5% per {{ $labels.model_name }}"
# Dashboard Grafana: query principali per ML inference monitoring
# (da importare come JSON o configurare manualmente)
# 1. Throughput modello (req/sec)
rate(kserve_inference_request_total[5m])
# 2. Latenza P50, P95, P99
histogram_quantile(0.50, rate(kserve_inference_request_duration_seconds_bucket[5m]))
histogram_quantile(0.95, rate(kserve_inference_request_duration_seconds_bucket[5m]))
histogram_quantile(0.99, rate(kserve_inference_request_duration_seconds_bucket[5m]))
# 3. GPU Utilization per nodo (richiede NVIDIA DCGM Exporter)
DCGM_FI_DEV_GPU_UTIL{namespace="ml-serving"}
# 4. GPU Memory in uso
DCGM_FI_DEV_FB_USED{namespace="ml-serving"} /
DCGM_FI_DEV_FB_TOTAL{namespace="ml-serving"} * 100
# 5. Numero repliche attive per modello
kube_deployment_status_replicas_available{
namespace="ml-serving",
deployment=~".*-predictor.*"
}
# 6. Scaling events (utile per debug autoscaler)
kube_horizontalpodautoscaler_status_desired_replicas{
namespace="ml-serving"
}
Optimizarea costurilor pentru Cluster ML
GPU-urile sunt elementul de cost dominant al unui cluster ML: un NVIDIA A100 SXM4 costă aproximativ 2-3 USD/oră pe nor, un H100 4-5 USD/oră. Pe un cluster de 20 de GPU, costul lunar poate depășește 100.000 USD. Optimizarea costurilor nu este opțională.
Strategii de optimizare a costurilor
- Instanțe spot/preemptibile pentru antrenament: economii de 60-80% pe loc de muncă tolerant la întreruperi. Utilizați puncte de control frecvente și fluxuri de lucru Argo pentru reluarea automată.
- Scalare la zero pentru modele ocazionale: KServe cu minReplicas=0 resetări costul GPU-urilor atunci când modelul nu primește trafic.
- GPU Time-slicing pentru inferență ușoară: 4-8 modele per GPU fizic redus costul pe model de 4-8x.
- Cluster Autoscaler cu pool-uri de noduri mixte: Noduri GPU adăugate/eliminate automat pe baza încărcării actuale a clusterului.
- Consolidarea nodurilor cu Karpenter: consolidați mai devreme pod-urile pe mai puține noduri pentru a termina nodurile goale (20-40% economii la clustere cu utilizare variabilă).
# Cluster Autoscaler per node pool GPU
# (esempio GKE, ma concettualmente uguale per EKS/AKS)
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-autoscaler-config
namespace: kube-system
data:
config.yaml: |
nodeGroups:
- name: gpu-a100-pool
minSize: 0 # Scala a zero se nessun workload GPU
maxSize: 10 # Max 10 nodi A100
machineType: a2-highgpu-1g
expander: least-waste # Usa il nodo che spreca meno risorse
scaleDownUnneededTime: 10m # Rimuovi nodo inutilizzato dopo 10 min
scaleDownUtilizationThreshold: 0.5 # Scala down se utilizzo < 50%
skipNodesWithSystemPods: false
# Cost tracking con labels obbligatorie su tutti i workload
# Ogni Job/Deployment deve avere questi labels per tracking
metadata:
labels:
cost-center: "data-science"
project: "churn-prediction"
environment: "production"
model-version: "v3"
Implementare completă: conductă end-to-end
Să vedem cum să integrăm toate componentele într-o implementare Helm completă pentru un model producție, combinând monitorizarea KServe, Prometheus și autoscaling cu KEDA:
# Helm Chart structure per ML service completo
# charts/ml-inference-service/
# ├── Chart.yaml
# ├── values.yaml
# └── templates/
# ├── inference-service.yaml
# ├── hpa.yaml
# ├── service-monitor.yaml
# └── network-policy.yaml
# values.yaml
model:
name: "churn-predictor"
version: "v3"
storageUri: "gs://my-ml-bucket/models/churn-model/v3"
framework: "sklearn"
runtimeVersion: "1.5.2"
scaling:
minReplicas: 2
maxReplicas: 20
targetRPS: 50
targetLatencyP99: "500m"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
monitoring:
enabled: true
prometheusNamespace: "ml-monitoring"
grafanaDashboard: true
canary:
enabled: false
weight: 0
# Deploy
helm upgrade --install churn-predictor ./charts/ml-inference-service \
--namespace ml-serving \
--values production-values.yaml \
--wait --timeout 10m
# Verifica deployment
kubectl get inferenceservice -n ml-serving
# NAME URL READY PREV LATEST ...
# churn-predictor http://churn-predictor True 0 100 ...
Cele mai bune practici și anti-modele
După ce am văzut implementarea tehnică, iată lecțiile învățate din implementarea ML Kubernetes în medii de întreprindere:
Cele mai bune practici
- Specificați întotdeauna solicitările de resurse ȘI limitele: fara cereri, the Planificatorul nu poate plasa podurile corect. Fără limite, un model OOM poate destabiliza întregul nod.
- Utilizați sonde de pregătire specifice ML: un recipient poate fi „funcționează”, dar modelul încă se încarcă. Sonda de pregătire trebuie să verifice că modelul este de fapt gata să fie servit.
- Imagini de pre-tragere pe nodurile GPU: Imagini PyTorch cu CUDA depășesc adesea 10 GB. Utilizați DaemonSet sau pre-caching-ul imaginii pentru a evita pornirile mari la rece.
- Separați spațiile de nume după medii: montare și producție pe diferite spații de nume cu ResourceQuota distinctă evită interferența accidentală.
- Întrerupător de circuit al aparatului: dacă modelul are o rată de eroare > 10%, opriți trafic automat cu Istio sau un proxy sidecar.
- Versiune explicită a modelului: fiecare InferenceService trebuie să aibă o etichetă de versiune în nume sau etichete. Nu utilizați niciodată „cele mai recente” în producție.
Anti-modele de evitat
- Antrenament asupra nodurilor de inferență: locurile de muncă de formare saturează CPU/GPU e provoacă vârfuri de latență pe modelele de producție. Folosiți întotdeauna bazine de noduri separate cu impurități.
- HPA pe CPU pentru modelele legate de GPU: procesorul poate fi scăzut în timp ce GPU și saturate. Utilizați întotdeauna valori personalizate (latență, RPS, utilizare GPU) pentru sarcinile de lucru GPU.
-
Fără închidere grațioasă: Pod-urile ML trebuie să completeze solicitările în
curs înainte de a termina. Configurați întotdeauna
terminationGracePeriodSeconds>= 30s. - Modele în imaginile Docker: includeți greutățile modelului în imagine Docker face actualizările lente și imaginile uriașe. Utilizați stocarea separată a modelului (S3, GCS).
-
Bugetul fără întreruperi: fără
PodDisruptionBudget, o actualizare a clusterului poate elimina toate replicile modelului simultan.
Bugetul IMM-urilor: Începeți cu mai puțin de 5.000 EUR/an
Nu aveți nevoie de un cluster de întreprindere de 100.000 USD/lună pentru a începe cu ML pe Kubernetes. Aici o stivă realistă pentru un IMM cu buget limitat:
- Cluster K3s pe cloud VM (2 noduri, 8 vCPU, 32 GB RAM): in jur de 150-200 EUR/luna. K3s este distribuția Kubernetes ușoară a lui Rancher, perfectă pentru grupuri mici.
- 1 nod GPU NVIDIA T4 (instanță spot): 0,35-0,50 EUR/oră, aproximativ 120-180 EUR/lună daca este folosit 12 ore/zi. Scalați la zero atunci când nu aveți nevoie.
- KServe + MLflow + Prometheus: toate gratuite, open-source, instalabile cu Helm în 30 de minute.
- Stocare compatibilă cu S3 (MinIO auto-găzduit): 0 costuri de licență, doar depozitare. 100 GB de modele și seturi de date: aproximativ 2-5 EUR/lună pentru stocarea obiectelor în cloud.
Total estimat: 300-400 EUR/lună, mai puțin de 5.000 EUR/an pentru un mediu gata de producție cu GPU, monitorizare completă și scalare automată. Pentru comparație, SageMaker cu o configurație echivalentă ar costa de 3-5 ori mai mult.
Concluzii
Kubernetes este standardul industrial pentru implementarea modelelor ML în producție dintr-un motiv: oferă combinația unică de programare GPU, scalare automată bazată pe evenimente, izolarea sarcinii de lucru și ecosistem de instrumente specializate (KServe, Seldon, KEDA) pe care nicio altă platformă nu le poate potrivire în ceea ce privește flexibilitatea și costul pe termen lung.
Calea optimă pentru începători: configurați un cluster K3s cu un nod GPU, instalați KServe pentru servirea primului model, adauga Prometheus si Grafana pentru monitorizare, si gata când clusterul crește dincolo de 5-10 modele în producție investește în KEDA și Seldon Core pentru arhitecturi mai complexe. Complexitatea Kubernetes se plătește numai atunci când volumul de volumul de muncă o justifică.
În următorul articol din serie vom explora Guvernarea ML: cum se garantează conformitatea cu AI Act EU, implementarea explicabilității cu SHAP și LIME, gestionarea pistelor de audit și corectitudinea modelelor în producție.
Articole similare din această serie
- Modele de servire: FastAPI + Uvicorn în producție - Modelul înainte de Kubernetes
- Testarea A/B a modelelor ML - Lansarea Canarelor și împărțirea traficului
- Guvernare ML: conformitate, audit, etică - Articolul următor
- Detectarea derivei modelului și reinstruire automată - Monitorizare avansată
Cross-Series
- Seria Advanced Deep Learning - Instruirea modelelor complexe pentru a fi implementate pe K8
- Seria Computer Vision - Șabloane de CV optimizate pentru inferența GPU







