Skalowanie uczenia maszynowego w Kubernetes: wdrażanie i orkiestracja w środowisku produkcyjnym
Twój model uczenia maszynowego przeszedł wszystkie testy offline, wskaźniki są doskonałe, obsługa poprzez FastAPI działa doskonale lokalnie. Potem nadchodzi krytyczny moment: musisz dać sobie radę 10 000 żądań na sekundę, dynamicznie skalowane w zależności od obciążenia, zapewniają wysoką dostępność i zerowe przestoje podczas aktualizacji. Pojedynczy pojemnik już nie wystarczy.
Kubernetes i stał się de facto standardem w zakresie orkiestracji obciążeń ML w produkcyjnej, a 78% organizacji korporacyjnych używa go do wdrażania modeli według badania CNCF Survey 2025. Jednak nie wystarczy umieścić model w Podu: trzeba nim zarządzać Planowanie GPU, automatyczne skalowanie sterowane zdarzeniami, przydziały zasobów, wdrożenia i monitorowanie typu canary wyspecjalizowane w wnioskowaniu. Rynek MLOps, który w 2026 roku będzie wart 4,38 miliarda dolarów, przy CAGR na poziomie 39,8%, ma swój główny silnik skalujący w Kubernetesie.
W tym artykule omawiamy pełną architekturę umożliwiającą wprowadzanie modeli uczenia maszynowego do Kubernetes produkcja: od KServe i Seldon Core do obsługi wnioskowania, po planowanie GPU za pomocą urządzenia NVIDIA Wtyczki, po inteligentne autoskalowanie za pomocą HPA, VPA i KEDA, aż po monitorowanie za pomocą Prometheus i Grafana.
Czego się nauczysz
- dlaczego Kubernetes jest standardem dla ML w środowisku produkcyjnym i kiedy go używać
- Planowanie i udostępnianie GPU za pomocą wtyczki NVIDIA Device Plugin i MIG
- KServe: wdrożenie InferenceService z wdrożeniem Canary i skalowaniem do zera
- Seldon Core v2: Potoki obsługujące komponowane i obsługujące wiele modeli
- Zaawansowane automatyczne skalowanie: HPA, VPA i KEDA dla obciążeń ML sterowanych zdarzeniami
- Zarządzanie zasobami: żądania, limity, klasy priorytetów i powinowactwo węzłów
- Monitorowanie za pomocą Prometheus + Grafana specjalizujące się w wnioskowaniu ML
- Optymalizacja kosztów i najlepsze praktyki dla klastrów GPU
dlaczego Kubernetes dla ML w produkcji
Zanim zagłębimy się w konfigurację techniczną, ważne jest, aby zrozumieć, dlaczego Kubernetes Ugruntowała swoją pozycję jako platforma referencyjna dla obciążeń ML, przewyższając rozwiązania takie jak bare metal, dedykowane maszyny wirtualne lub autorskie usługi w chmurze.
Obciążenia ML mają unikalne cechy w porównaniu z tradycyjnymi aplikacjami internetowymi. Szkolenie wymaga ogromnych procesorów graficznych przez wiele godzin lub dni, wówczas należy zwolnić zasoby. Wniosek ma nieprzewidywalne wartości szczytowe i krytyczne opóźnienia. Modele muszą być aktualizowane bez przestojów. Zbiory danych mogą być ogromne i wymagać specjalistycznego przechowywania. Kubernetes uwzględnia wszystkie te scenariusze z natywnymi elementami podstawowymi: planowanie podów na określonych węzłach GPU, woluminy trwałe na zestaw danych, zadania do uczenia wsadowego, HorizontalPodAutoscaler do skalowania wnioskowania.
Kubernetes kontra alternatywa zarządzana w chmurze
Kubernetes samodzielnie zarządzany lub zarządzany (EKS, GKE, AKS): pełna kontrola,
przenośność w wielu chmurach, optymalizacja kosztów, ale duża złożoność operacyjna.
SageMaker / Vertex AI / Azure ML: szybka konfiguracja, natywna integracja z chmurą,
ale uzależnienie od dostawcy, wyższe koszty długoterminowe i mniejsza elastyczność w przypadku architektur niestandardowych.
Praktyczna zasada: zespół <5 osób lub ograniczony budżet? Rozpocznij korzystanie z zarządzanego uczenia maszynowego.
Zespół > 5 z wieloma modelami w produkcji? Kubernetes zwróci Twoją inwestycję w ciągu 6-12 miesięcy.
Architektura referencyjna
Klaster Kubernetes dla uczenia maszynowego w środowisku produkcyjnym jest zwykle zbudowany z trzech odrębnych warstw, każda z dobrze określonymi obowiązkami:
- Warstwa infrastruktury: Węzły procesora do lekkiej obsługi i orkiestracji, węzły GPU do uczenia i intensywnego wnioskowania, węzły przechowywania zbiorów danych i artefaktów. Pule GPU są oddzielone określonymi etykietami węzłów.
- Warstwa platformy: KServe lub Seldon Core do obsługi wnioskowania, Kubeflow do potok szkoleniowy, MLflow do eksperymentalnego śledzenia (patrz artykuł 4), Argo Workflows dla złożona orkiestracja.
- Warstwa obserwowalności: Prometheus do metryk, Grafana do dashboardów, Jaeger do śledzenia rozproszonego, Loki do agregacji dzienników.
# 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
Planowanie i udostępnianie GPU
Procesory graficzne są najdroższym zasobem klastra ML. Złe zarządzanie nimi oznacza marnowanie dziesiątek
tysiące euro miesięcznie. Kubernetes udostępnia procesory graficzne jako zasoby, które można zaplanować za pośrednictwem
Wtyczki do urządzeń NVIDIA, zestaw DaemonSet, który automatycznie wykrywa procesory graficzne w węzłach
i zapisuje je jako nvidia.com/gpu w kubelecie.
# 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
W przypadku obciążeń wnioskowania, które nie wymagają pełnego procesora graficznego, NVIDIA oferuje dwie strategie Udostępnianie GPU: Krojenie czasu e Procesor graficzny z wieloma instancjami (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
Krojenie czasu a MIG: kiedy którego używać
Krojenie czasu: nadaje się do lekkich obciążeń wnioskowania (modele <2 GB VRAM), wprowadza opóźnienie wynikające z przełączania kontekstu. Działa na każdym procesorze graficznym NVIDIA. MIG: pełna izolacja sprzętu, gwarantowana dedykowana pamięć, zero zakłóceń pomiędzy obciążeniami. Dostępne tylko w A100, A30, H100. Idealny do rygorystycznych umów SLA dotyczących opóźnień. Nigdy nie łącz obu podejść w tym samym węźle.
KServe: Natywne udostępnianie wnioskowania w Kubernetesie
KServ (dawniej KFServing) i standard CNCF do obsługi wnioskowania na Kubernetesie,
powstał w wyniku współpracy Google, IBM, Bloomberg i innych. Dostarcza abstrakcji
InferenceService co ukrywa złożoność wdrożenia, zarządzając automatycznie
wdrażanie canary, skalowanie do zera, automatyczne skalowanie oparte na żądaniach i obsługa wielu platform
(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"
Jedną z mocnych stron KServe jest natywne wsparcie dla i Wdrożenie Kanarek: możesz wysłać procent ruchu do nowej wersji szablonu, a resztę kontynuuj używanie wersji stabilnej, dokładnie tak, jak w przypadku testów A/B, które widzieliśmy w artykule poprzedni z tej serii.
# 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]]}'
Cechą skalowanie do zera KServe (w oparciu o Knative Serving) e szczególnie przydatne w przypadku okazjonalnie używanych modeli: kapsuła wyłącza się po pewnym czasie konfigurowalny czas bezczynności i automatycznie uruchamia się ponownie przy pierwszym żądaniu, w przypadku zimnego startu zazwyczaj mniej niż 30 sekund w przypadku modeli wstępnie buforowanych.
# 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: Kompozytowalne potoki ML
Podczas gdy KServe specjalizuje się w obsłudze indywidualnych modeli, Seldon Core v2 świeci w zarządzaniu złożonymi architekturami ML: wieloetapowymi potokami, zespołami modeli, Routing A/B z logiką biznesową i integracja z Kafką do przetwarzania strumieni. Seldon v2 używa MLServer jako środowiska wykonawczego wnioskowania, kompatybilnego z protokołem V2 (KFServing Inference Protocol) i natywnie obsługuje PyTorch, scikit-learn, Przytulanie twarzy i modele niestandardowe.
# 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 kontra Seldon Core: który wybrać
- KServ: najlepsze dla pojedynczych modeli, integracja z Knative i Istio, natywna skala do zera, społeczność CNCF. Idealny dla zespołów zaczynających od K8s ML.
- Seldon Core v2: najlepsze dla złożonych potoków, zespołów, integracji Kafki i obsługi wielu modeli. Idealny dla zaawansowanych architektur ML z routingiem logiki biznesowej.
- Obydwa: obsługa protokołu V2, monitorowanie Prometheus, Triton do obsługi GPU. Nie wykluczają się one wzajemnie, niektóre organizacje używają ich razem w różnych przypadkach użycia.
Inteligentne autoskalowanie: HPA, VPA i KEDA
Skalowanie obciążeń ML jest bardziej złożone niż w przypadku tradycyjnych aplikacji internetowych. Metryki procesora i pamięci często nie odzwierciedlają dokładnie rzeczywistego obciążenia modelu: model związany z GPU może nasycić kartę graficzną, gdy procesor jest w 80% bezczynny. Kubernetes oferuje trzy uzupełniające się mechanizmy autoskalowania, które odpowiednio użyte łącznie, obejmują wszystkie scenariusze uczenia się.
# 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 (Autoskaler sterowany zdarzeniami Kubernetes, projekt dyplomowy CNCF) i lo Najpotężniejsze narzędzie do obciążeń ML sterowanych zdarzeniami: skalowanie zasobników na podstawie zdarzeń z kolejek komunikatów, baz danych, metryk Prometheusa lub wyzwalaczy HTTP, co również pozwala skalowanie do zera w przypadku przetwarzania wsadowego.
# 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
Automatyczne skalowanie wzorców dla ML: Zalecenie
Łączy trzy mechanizmy z odrębnymi obowiązkami:
- HPA na temat opóźnień/RPS dla wnioskowania online (responsywne, szybkie)
- VPA w trybie wyłączonym aby zoptymalizować żądania zadań szkoleniowych (konsultuj i aktualizuj ręcznie)
- KEDA do przetwarzania wsadowego i potoków sterowanych zdarzeniami (w tym skalowanie do zera)
Nie używaj HPA i VPA jednocześnie na tym samym zasobie (procesorze/pamięci): konflikty
skalowanie powoduje nieprzewidywalne wahania i marnotrawstwo zasobów.
Zarządzanie zasobami i zajęcia priorytetowe
W klastrze współdzielonym przez różne zespoły ML zarządzanie zasobami ma kluczowe znaczenie zapobiegaj blokowaniu przez zadanie szkoleniowe wnioskowania w produkcji lub eksperymencie zużywają wszystkie dostępne procesory graficzne. Kubernetes oferuje trzy narzędzia: Przydział zasobów, LimitZakresu e Klasa priorytetowa.
# 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 Powinowactwo węzła i ja Skażenia/tolerancje pozwolić ci na to dokładnie kontroluj, na których węzłach zaplanowane są obciążenia ML, zapewniając że zadania szkoleniowe nie konkurują z wnioskowaniem na tych samych procesorach graficznych:
# 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
Monitorowanie za pomocą Prometheus i Grafana dla ML
Monitorowanie systemu uczenia maszynowego w Kubernetes wymaga metryk na dwóch poziomach: metryk Standardowa infrastruktura Kubernetes (procesor, pamięć, sieć) i szczegółowe metryki wnioskowania ML (opóźnienie modelu, przepustowość, poziom błędów, sygnał dryfu danych). KServe i Seldon automatycznie udostępniają metryki Prometheusa w standardowym formacie.
# 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"
}
Optymalizacja kosztów dla Cluster ML
Procesory graficzne są dominującą pozycją kosztową klastra ML: NVIDIA A100 SXM4 kosztuje około 2-3 USD/godz. w chmurze, H100 4-5 USD/godz. W klastrze składającym się z 20 procesorów graficznych miesięczny koszt może wynosić ok przekroczyć 100 000 dolarów. Optymalizacja kosztów nie jest opcjonalna.
Strategie optymalizacji kosztów
- Instancje typu spot/wywłaszczania do szkolenia: oszczędność 60-80% na jedno miejsce pracy tolerancyjny na przerwy. Korzystaj z częstych punktów kontrolnych i przepływów pracy Argo, aby automatycznie wznawiać pracę.
- Skala do zera dla okazjonalnych modeli: KServe z minReplicas=0 resetuje koszt procesorów graficznych, gdy model nie otrzymuje ruchu.
- Dzielenie czasu GPU w celu uzyskania lekkiego wnioskowania: Redukcja 4-8 modeli na fizyczny procesor graficzny koszt za model 4-8x.
- Klaster automatycznego skalowania z mieszanymi pulami węzłów: Dodano/usunięto węzły GPU automatycznie na podstawie rzeczywistego obciążenia klastra.
- Konsolidacja węzłów za pomocą Karpentera: wcześniej skonsoliduj pody na mniejszej liczbie węzłów do zakończenia pustych węzłów (oszczędność 20-40% na klastrach o zmiennym obciążeniu).
# 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"
Pełne wdrożenie: kompleksowy potok
Zobaczmy, jak zintegrować wszystkie komponenty w kompletne wdrożenie Helm dla modelu produkcja, łącząc KServe, monitorowanie Prometheus i autoskalowanie z 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 ...
Najlepsze praktyki i anty-wzorce
Po obejrzeniu wdrożenia technicznego przedstawiamy wnioski wyciągnięte z wdrożenia ML Kubernetes w środowiskach korporacyjnych:
Najlepsze praktyki
- Zawsze określaj żądania zasobów ORAZ limity: bez próśb, Program planujący nie może poprawnie umieścić podów. Bez ograniczeń model OOM może zdestabilizować cały węzeł.
- Użyj sond gotowości specyficznych dla ML: może być pojemnik „uruchomiony”, ale model nadal się ładuje. Sonda gotowości musi sprawdzić że model rzeczywiście jest gotowy do służby.
- Obrazy przed pobraniem na węzłach GPU: Obrazy PyTorch z CUDA często przekraczają 10 GB. Użyj DaemonSet lub wstępnego buforowania obrazu, aby uniknąć częstego zimnego startu.
- Oddziel przestrzenie nazw według środowisk: staging i produkcja w różnych przestrzeniach nazw z odrębnym ResourceQuota pozwala uniknąć przypadkowych zakłóceń.
- Zastosuj wyłącznik automatyczny: jeśli model ma poziom błędów > 10%, zatrzymaj się ruch automatyczny za pomocą Istio lub bocznego serwera proxy.
- Jawne wersjonowanie modelu: każdy InferenceService musi mieć znacznik wersji w nazwie lub etykietach. Nigdy nie używaj „najnowszego” w produkcji.
Anty-wzorce, których należy unikać
- Trening na węzłach wnioskowania: zadania szkoleniowe obciążają procesor/GPU np powodować skoki opóźnień w modelach produkcyjnych. Zawsze używaj oddzielnych pul węzłów ze skażeniami.
- HPA na procesorze dla modeli z procesorem graficznym: procesor może być niski podczas GPU i nasycenie. Zawsze używaj niestandardowych wskaźników (opóźnienie, RPS, wykorzystanie procesora GPU) dla obciążeń GPU.
-
Brak łagodnego zamknięcia: Pody ML muszą realizować żądania w
kurs przed ukończeniem. Zawsze konfiguruj
terminationGracePeriodSeconds>= 30 s. - Modele w obrazach Dockera: uwzględnij ciężary modeli na obrazie Docker powoduje, że aktualizacje są powolne, a obrazy ogromne. Użyj osobnego magazynu modeli (S3, GCS).
-
Brak budżetu na zakłócenia: bez
PodDisruptionBudget, aktualizacja klastra może jednocześnie usunąć wszystkie repliki modelu.
Budżet MŚP: Zacznij od mniej niż 5000 EUR rocznie
Nie potrzebujesz klastra korporacyjnego o wartości 100 000 USD miesięcznie, aby rozpocząć korzystanie z uczenia maszynowego w Kubernetes. Tutaj realistyczny stos dla MŚP z ograniczonym budżetem:
- Klaster K3s na maszynie wirtualnej w chmurze (2 węzły, 8 vCPU, 32 GB RAM): około 150-200 EUR/miesiąc. K3s to lekka dystrybucja Kubernetes firmy Rancher, idealna dla małych klastrów.
- 1 węzeł GPU NVIDIA T4 (instancja punktowa): 0,35-0,50 EUR/godzinę, około 120-180 EUR/miesiąc jeśli jest używany 12 godzin dziennie. Skaluj do zera, gdy go nie potrzebujesz.
- KServe + MLflow + Prometeusz: wszystko bezpłatne, open source, do zainstalowania z Helmem za 30 minut.
- Pamięć kompatybilna z S3 (samodzielnie hostowane MinIO): 0 kosztów licencji, tylko przechowywanie. 100 GB modeli i zbiorów danych: około 2–5 EUR/miesiąc za przechowywanie obiektów w chmurze.
Szacunkowa suma: 300-400 EUR/miesiąc, poniżej 5000 EUR/rok dla środowiska gotowy do produkcji z procesorem graficznym, pełnym monitorowaniem i autoskalowaniem. Dla porównania SageMaker przy równoważnej konfiguracji kosztowałoby to 3-5 razy więcej.
Wnioski
Kubernetes jest standardem branżowym dotyczącym wdrażania modeli uczenia maszynowego w środowisku produkcyjnym nie bez powodu: oferuje unikalną kombinację planowania GPU, automatycznego skalowania sterowanego zdarzeniami i izolacji obciążenia i ekosystem wyspecjalizowanych narzędzi (KServe, Seldon, KEDA), których nie oferuje żadna inna platforma dorównują pod względem elastyczności i kosztów długoterminowych.
Optymalna ścieżka dla początkujących: Skonfiguruj klaster K3s z węzłem GPU, zainstaluj KServe do obsługi pierwszego modelu dodaję Prometheusa i Grafanę do monitoringu i tyle gdy klaster rozrośnie się powyżej 5-10 modeli w produkcji, zainwestuj w KEDA i Seldon Core bardziej złożone architektury. Złożoność Kubernetesa opłaca się tylko wtedy, gdy ilość obciążenie pracą to uzasadnia.
W kolejnym artykule z tej serii przyjrzymy się bliżej Zarządzanie systemem ml: jak zagwarantować zgodność z ustawą AI Act UE, wdrożenie wyjaśnialności za pomocą SHAP i LIME, zarządzanie ścieżkami audytu i uczciwość produkowanych modeli.
Powiązane artykuły z tej serii
- Obsługa modeli: FastAPI + Uvicorn w produkcji - Model sprzed Kubernetesa
- Testowanie A/B modeli ML - Wdrożenie Canary i podział ruchu
- Zarządzanie ML: zgodność, audyt, etyka - Następny artykuł
- Model Drift Detection e Retraining Automatico - Monitoring avanzato
Cross-Serie
- Serie Deep Learning Avanzato - Training di modelli complessi da deployare su K8s
- Serie Computer Vision - Modelli CV ottimizzati per GPU inference







