Cómo desplegar un modelo de IA en Kubernetes, paso a paso
En mi homelab sirvo un modelo de IA local con una API compatible con OpenAI, corriendo en Kubernetes sobre una GPU de consumo. En este post te enseño a montarlo desde cero, con los mismos pasos (y los mismos tropiezos) que seguí yo.
Qué vamos a construir
Un Deployment de llama.cpp sirviendo un modelo en formato GGUF (en mi caso, Gemma 4 12B), expuesto como API /v1/chat/completions. Cualquier app que hable con OpenAI podrá hablar con tu cluster.
Requisitos
- Un cluster Kubernetes. Yo uso K3s en un solo nodo: detecta solo el runtime de NVIDIA si tienes
nvidia-container-toolkitinstalado. - Una GPU con VRAM suficiente (luego hacemos las cuentas) y sus drivers.
- El NVIDIA device plugin desplegado: es quien anuncia
nvidia.com/gpucomo recurso reservable.
Paso 1: elige modelo y cuantización
La regla rápida para un GGUF cuantizado a 4 bits (Q4_K_M): ~0,6 GB de VRAM por cada mil millones de parámetros, más el KV cache (depende del contexto). Un 12B en Q4 son ~7,5 GB de pesos; en mi RTX 5060 Ti de 16 GB cabe con 128K de contexto y sobra espacio.
Descarga el GGUF (Hugging Face: busca el repo oficial o los de la comunidad como bartowski) y déjalo en un disco del nodo, por ejemplo /data/modelos/.
Paso 2: PersistentVolume para los modelos
Los GGUF pesan gigas: no van dentro de la imagen. Un PV local apuntando a la carpeta del disco, y su PVC:
apiVersion: v1
kind: PersistentVolume
metadata:
name: llm-models
spec:
capacity: { storage: 30Gi }
accessModes: [ReadWriteOnce]
storageClassName: local-hdd
local: { path: /data/modelos }
nodeAffinity: # un PV local vive en UN nodo concreto
required:
nodeSelectorTerms:
- matchExpressions:
- { key: kubernetes.io/hostname, operator: In, values: [mi-nodo] }
Paso 3: el Deployment con llama.cpp
La pieza central. Lo importante: la imagen server-cuda de llama.cpp, el recurso nvidia.com/gpu: 1, y los flags del servidor.
apiVersion: apps/v1
kind: Deployment
metadata: { name: llm }
spec:
replicas: 1
strategy: { type: Recreate } # la GPU no se comparte entre 2 réplicas a la vez
template:
spec:
runtimeClassName: nvidia
containers:
- name: llama-server
image: ghcr.io/ggml-org/llama.cpp:server-cuda # pínala a digest en producción
args:
- --model
- /models/gemma-4-12B-it-Q4_K_M.gguf
- --host
- "0.0.0.0"
- --port
- "8000"
- --n-gpu-layers
- "99" # todas las capas a la GPU
- --ctx-size
- "32768"
- --flash-attn
- "on"
env:
- name: LLAMA_API_KEY # protege el endpoint
valueFrom: { secretKeyRef: { name: llm-secrets, key: API_KEY } }
resources:
limits: { memory: 8Gi, nvidia.com/gpu: "1" }
readinessProbe:
httpGet: { path: /health, port: 8000 }
initialDelaySeconds: 30 # cargar gigas desde disco tarda
volumeMounts:
- { name: models, mountPath: /models, readOnly: true }
volumes:
- name: models
persistentVolumeClaim: { claimName: llm-models }
Detalles que importan:
- Probes con margen: el servidor tarda en cargar el modelo desde disco; si el
initialDelaySecondses muy corto, Kubernetes matará el pod antes de que termine. - API key como Secret, nunca en el manifiesto.
--ctx-sizedefine el contexto y el tamaño del KV cache: más contexto = más VRAM. Empieza conservador y sube midiendo.
Paso 4: Service y verificación
Un Service ClusterIP en el puerto 8000 y a probar:
kubectl exec -it deploy/cualquier-pod -- \
curl http://llm.mi-namespace.svc:8000/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"model":"local","messages":[{"role":"user","content":"Hola"}]}'
Y vigila la VRAM real con nvidia-smi: es tu métrica de la verdad para ajustar contexto y cuantización.
Tropiezos de los que aprendí
- La GPU se puede compartir: con time-slicing en el device plugin, el mismo chip sirve el LLM y transcodifica vídeo (Jellyfin). La VRAM no se particiona: haz las cuentas tú.
- Cada modelo tiene su sampling: migré de Qwen a Gemma y las respuestas degeneraban en bucles — Gemma necesita
--temp 1.0. Lee la ficha del modelo. - GitOps también aquí: mi manifiesto vive en Git y ArgoCD lo aplica. Cambiar de modelo es editar dos líneas y hacer push.
¿Dudas montando el tuyo? Escríbeme desde la página de inicio — y si quieres ver este setup en acción, el chat de esta web corre exactamente así.