Introducció
Executar càrregues de treball IA a Kubernetes introdueix reptes únics que difereixen significativament del desplegament d’aplicacions tradicional. La planificació de GPU, els patrons de càrrega de models i l’observabilitat requereixen enfocaments especialitzats. Aquest document recull les nostres decisions d’arquitectura i el raonament darrere d’elles.
Arquitectura Nucle
El nostre cluster K3s per a càrregues de treball IA segueix una arquitectura per capes:
┌──────────────────────┐
│ Traefik L7 │
│ Ingress / TLS │
└──────────┬───────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Hermes │ │ LiteLLM │ │ Cron Jobs │
│ Agents │ │ Gateway │ │ (scheduled│
│ (workloads│ │ │ │ tasks) │
└────────────┘ └────────────┘ └────────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────┐
│ K3s Cluster │
│ ┌──────────┐ ┌──────────┐ │
│ │ Control │ │ Worker │ (gamorcloud01)│
│ │ Plane │ │ Node │ │
│ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ GPU │ │ GPU │ (gx10-db4d) │
│ │ Worker │ │ Worker │ │
│ └──────────┘ └──────────┘ │
└────────────────────────────────────────────┘
Estratègia de Separació de Nodes
Dividem el cluster en dos nodes especialitzats:
Node del Plan de Control (gamorcloud01)
- Rol: servidor d’API, gestor de controladors, planificador
- CPU: ús general (AMD EPYC / Intel Xeon)
- RAM: 32 GB
- Emmagatzematge: 200 GB NVMe
- Càrregues de treball: components del pla de control, agents lleus, monitoratge
Node de GPU (gx10-db4d)
- Rol: càrregues de treball IA, servidors de models, inferència
- GPU: NVIDIA L4 (24 GB VRAM) o A100 (80 GB VRAM)
- RAM: 64 GB mínim
- Emmagatzematge: 500 GB NVMe per a pesos de models
- Càrregues de treball: LiteLLM, servidors de models, tasques intensives en GPU
Per Què Separar?
- Planificació de GPU: el NVIDIA GPU Operator utilitzadors selectors de node i tolerances
- Contenció de recursos: les càrregues de treball IA fan picos de memòria de GPU — no les compartiu amb el pla de control
- Eficiència de cost: els nodes de GPU són caros; mantingueu el pla de control en maquinari més econòmic
- Aïllament d’errors: la fallada d’un node de GPU no hauria de caure el pla de control
Planificació de GPU
NVIDIA GPU Operator
El NVIDIA GPU Operator automatitza la instal·lació del controlador de GPU, el plugin de dispositius i el DCGM:
# Install 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 \
--set toolkit.enabled=true \
--set driver.enabled=false \
--set devicePlugin.enabled=true \
--set dcgm.enabled=true
Etiquetes i Tolerances de Node
Els nodes de GPU reben etiquetes especials:
apiVersion: v1
kind: Node
metadata:
name: gx10-db4d
labels:
nvidia.com/gpu.deploy-gpu-feature: "true"
nvidia.com/gpu.deploy-container-engine-launcher: "true"
nvidia.com/gpu.deploy-dcgm: "true"
ai-workload: "true"
kubernetes.io/os: linux
taints:
- key: nvidia.com/gpu
effect: NoSchedule
value: present
Els pod que necessiten GPU afegeixen la tolerança corresponent:
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
Sol·licituds de Recursos
Cada pod d’IA ha de sol·licitar recursos de GPU:
resources:
requests:
nvidia.com/gpu: "1"
memory: "16Gi"
cpu: "4"
limits:
nvidia.com/gpu: "1"
memory: "24Gi"
cpu: "8"
Sense la sol·licitud nvidia.com/gpu, el pod no es planificarà en un node de GPU.
Arquitectura de Servidor de Models
Patró de Registre de Models
Emmagatzemeu els pesos de models en un directori compartit amb l’afinitat de node adequada:
apiVersion: v1
kind: PersistentVolume
metadata:
name: model-registry
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteOnce
storageClassName: nvidia-ai-store
local:
path: /data/model-registry
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- gx10-db4d
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-registry-pvc
namespace: ia-services-payed
spec:
accessModes:
- ReadWriteOnce
storageClassName: nvidia-ai-store
resources:
requests:
storage: 500Gi
volumeName: model-registry
Comparació de Servidors de Models
| Servidor | Memòria de GPU | Sol·licituds Concurrents | Temps d’Arrancada | Ideal per |
|---|---|---|---|---|
| vLLM | Alta | 30+ | Ràpida | Servidat d’alt rendiment |
| TGI (Text Generation) | Mitjana | 10-15 | Mitjà | Nadiu de codi obert |
| LiteLLM | N/A | N/A | Instantani | Gateway d’API, no inferència |
La Nosta Opció: vLLM + LiteLLM
Utilitzem vLLM per a la inferència real i LiteLLM com a gateway d’API:
# vLLM deployment (runs on GPU node)
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-serving
namespace: ia-services-payed
spec:
replicas: 1
selector:
matchLabels:
app: vllm
template:
spec:
containers:
- name: vllm
image: vllm/vllm-openai:v0.6.3
args:
- --model
- "nvidia/Nemotron-4-35B"
- --tensor-parallel-size
- "1"
- --max-model-len
- "8192"
resources:
limits:
nvidia.com/gpu: "1"
volumeMounts:
- name: model-cache
mountPath: /data/models
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: model-registry-pvc
Pila d’Observabilitat
Monitoratge de GPU amb DCGM
El DCGM exportava mètriques de GPU a Prometheus:
# Prometheus scrape config (added to prometheus.yaml)
- job_name: 'dcgm-exporter'
static_configs:
- targets: ['gpu-operator-dcgm-exporter.gpu-operator:9400']
Mètriques clau de DCGM:
| Mètrica | Descripció | Llindal d’Alerta |
|---|---|---|
DCGM_FI_DEV_POWER_USAGE |
Consum de potència de GPU (W) | > 350W |
DCGM_FI_DEV_TEMP_GPU |
Temperatura de GPU (°C) | > 85°C |
DCGM_FI_DEV_MEM_USED |
Memòria de GPU utilitzada (MiB) | > 90% |
DCGM_FI_DEV_XID_ERRORS |
Errors XID de GPU | > 0 |
Observabilitat de Nivell de Sol·licitud
Cada sol·licitud d’IA ha de ser rastrejada:
# Add tracing headers to requests
import requests
import uuid
request_id = str(uuid.uuid4())
headers = {
"X-Request-ID": request_id,
"X-Model": "nemotron-35b",
"X-Consumer": "hermes-agent",
}
response = requests.post(
"http://litellm.ia-services-payed.svc.cluster.local:4000/v1/completions",
headers=headers,
json=payload,
)
Registre Centralitzat
Tots els components fan registre en format JSON per a una anàlisi fàcil:
# Container log config
env:
- name: LOG_LEVEL
value: "info"
- name: LOG_FORMAT
value: "json"
- name: LOG_OUTPUT
value: "stdout"
Arquitectura de Xarxa
Configuració d’Ingress
Traefik gestiona la ruta externa amb TLS automàtic:
# TraefikMiddleware for rate limiting
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: ia-services-payed
spec:
rateLimit:
average: 100
burst: 200
period: 1s
---
# TraefikIngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: litellm-ingress
namespace: ia-services-payed
spec:
entryPoints:
- websecure
routes:
- match: Host(`intranet.cris-mora-cv.es`) && PathPrefix(`/api/litellm`)
kind: Rule
services:
- name: litellm
port: 4000
middlewares:
- name: rate-limit
- name: strip-prefix
tls:
secretName: intranet-tls
Consideracions de Malla de Serveis
Per a configuracions de múltiples clusters, considereu:
- Serveis integrats de K3s: suficients per a un sol cluster
- Istio/Linkerd: afegeix complexitat, no val la pena per la majoria de configuracions
- Traefik + cert-manager: la nostra opció — senzill, efectiu, nadiu de Kubernetes
Alta Disponibilitat
HA de Servidor de Models
Per a producció, executeu múltiples rèpliques darrere d’un servei:
apiVersion: apps/v1
kind: Deployment
metadata:
name: litellm-ha
namespace: ia-services-payed
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
Escalat Automàtic amb KEDA
Escaleu els servidors de models segons la profunditat de la cua de sol·licituds:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: litellm-autoscaler
namespace: ia-services-payed
spec:
scaleTargetRef:
name: litellm-ha
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-monitoring.svc.cluster.local:9090
query: |
sum(rate(litellm_proxy_request_count[2m]))
threshold: "50"
Consideracions de Cost
Optimització del Cost de GPU
| Estratègia | Estalvi | Esforç d’Implementació |
|---|---|---|
| Quantització | 2-3x reducció de VRAM | Baix |
| Batching de models | 3-5x augment de rendiment | Mitjà |
| Preció mixta (FP16) | 2x reducció de VRAM | Baix |
| Escalat programat | 40-60% reducció del cost de GPU | Alt |
Monitoratge de Cost
Segeu les hores de GPU i l’ús de tokens:
# GPU hours per day
sum(increase(nvidia_gpu_utilization[1d])) / 60 / 60
# Cost per model
sum(rate(litellm_proxy_token_usage[1h])) * model_price_per_token
Conclusió
Els principis d’arquitectura clau per a càrregues de treball IA a Kubernetes:
- Separeu la GPU del pla de control — eviteu la contenció de recursos
- Utilitzeu el GPU Operator — automatitza el controlador, el plugin de dispositius i el DCGM
- Sol·licitueu recursos de GPU explícitament — els pod sense sol·licituds de GPU no es planificaran
- Monitoratgeu-ho tot — mètriques de GPU, latència de sol·licitud, ús de tokens
- Comenceu petit, escaleu quan calgui — K3s + un node de GPU és suficient per començar
L’arquitectura escala des d’un sol Raspberry Pi fins a un cluster de múltiples nodes amb treballadors de GPU. Els primers de Kubernetes (Deployments, Services, PVs) es mantenen iguals independentment de l’escala.