Kubernetes para IA: La Guía Definitiva 2025
En 2025, Kubernetes se ha consolidado como la plataforma estándar para desplegar modelos de inteligencia artificial en producción. Mientras Docker Compose puede funcionar para experimentos caseros, cuando necesitas escalabilidad real, compartir GPUs entre múltiples modelos, alta disponibilidad y reproducibilidad total, Kubernetes es la respuesta.
Esta guía te llevará desde los conceptos fundamentales hasta arquitecturas avanzadas de MLOps, con ejemplos prácticos, archivos YAML funcionales y soluciones a los 15 problemas más comunes que enfrentarás.
¿Por Qué Kubernetes para IA en 2025?
El ecosistema de IA ha evolucionado dramáticamente. Los modelos LLM como Llama 3, Mistral o DeepSeek requieren GPUs potentes, múltiples réplicas para manejar carga, y sistemas de failover automático. Kubernetes ofrece:
- Escalabilidad horizontal automática: De 1 a 100 pods según demanda
- GPU sharing avanzado: Time-slicing y Multi-Instance GPU (MIG) para optimizar recursos
- Reproducibilidad total: Infraestructura como código con GitOps
- Alta disponibilidad: Self-healing, rolling updates sin downtime
- Aislamiento y seguridad: Namespaces, Network Policies, RBAC
- Ecosistema maduro: KubeFlow, Seldon, KServe, Ray, MLFlow
¿Para Quién Es Esta Guía?
Esta guía está diseñada para:
- Data Scientists que necesitan llevar modelos a producción
- MLOps Engineers construyendo pipelines de ML escalables
- DevOps Engineers migrando workloads de IA a Kubernetes
- Homelabbers avanzados con GPU(s) queriendo infraestructura profesional
Prerequisitos: Conocimientos básicos de Docker, terminal Linux y conceptos de machine learning.
¿Qué Aprenderás?
- Fundamentos de Kubernetes aplicados a workloads de IA
- Instalación paso a paso (K3s, Minikube, cluster completo)
- Desplegar Ollama, VLLM, Stable Diffusion en Kubernetes
- GPU scheduling, time-slicing y optimización de recursos
- Herramientas del ecosistema: KubeFlow vs Seldon vs KServe
- Networking, Ingress, mTLS y seguridad
- Pipelines ML completos desde training hasta A/B testing
- Monitorización con Prometheus, Grafana y métricas GPU
- Troubleshooting: 15 problemas comunes y soluciones
- Optimización de costos y FinOps para GPUs
Fundamentos: Kubernetes para Workloads de IA
Kubernetes vs Docker Compose: ¿Cuándo Usar Cada Uno?
Muchos homelabbers empiezan con Docker Compose para su infraestructura de IA, y es una excelente opción para comenzar. Pero hay un punto de inflexión donde Kubernetes se vuelve necesario:
| Criterio | Docker Compose | Kubernetes |
|---|---|---|
| Servicios | 1-5 contenedores simples | 5+ servicios complejos |
| Escalabilidad | Manual (docker compose up –scale) | Automática (HPA) |
| GPU | 1 GPU por contenedor | Sharing, time-slicing, MIG |
| Alta disponibilidad | No nativa (requiere Swarm) | Nativa (self-healing) |
| Networking | Simple (bridge/host) | Avanzado (CNI, policies) |
| Storage | Volúmenes locales | PV, PVC, StorageClasses |
| Complejidad | Baja (YAML simple) | Alta (curva aprendizaje) |
| Recursos overhead | Mínimo (~100MB RAM) | Moderado (1-2GB RAM base) |
Regla de oro: Si tienes 3 o menos servicios de IA en un único servidor, Docker Compose es suficiente. Si necesitas múltiples nodos, múltiples GPUs compartidas, o equipos trabajando en paralelo, Kubernetes es tu camino.
Conceptos Clave de Kubernetes para IA
Pods: La Unidad Básica
Un Pod es el concepto más fundamental en Kubernetes. Es un grupo de uno o más contenedores que comparten almacenamiento y red. Para IA:
apiVersion: v1
kind: Pod
metadata:
name: ollama-simple
spec:
containers:
- name: ollama
image: ollama/ollama:latest
resources:
limits:
nvidia.com/gpu: 1 # Solicita 1 GPU completa
¿Cuándo usar Pods directamente? Casi nunca en producción. Los Pods son efímeros y no se auto-recrean si fallan. Usa Deployments o StatefulSets.
Deployments: Para Servicios Stateless
Los Deployments gestionan réplicas de Pods y permiten rolling updates. Ideal para:
- Servidores de inferencia (Ollama, VLLM, TensorFlow Serving)
- APIs de modelos sin estado
- Servicios que pueden reiniciarse sin perder datos
StatefulSets: Para Servicios con Estado
Los StatefulSets mantienen identidad de red estable y almacenamiento persistente. Usa para:
- Bases de datos vectoriales (Qdrant, Milvus, Weaviate)
- Sistemas de caché de embeddings
- Servicios que necesitan almacenamiento persistente garantizado
Services: Networking y Load Balancing
Un Service expone un conjunto de Pods como un servicio de red:
- ClusterIP: Solo accesible dentro del cluster (default)
- NodePort: Expone en puerto del nodo (30000-32767)
- LoadBalancer: Usa balanceador externo (cloud providers)
Jobs y CronJobs: Para Tareas de Training
Jobs ejecutan tareas hasta completarse (fine-tuning, batch inference). CronJobs los ejecutan en horarios (reentrenamientos nocturnos).
GPU Scheduling: El Corazón de K8s para IA
Kubernetes no soporta GPUs nativamente. Necesitas el NVIDIA Device Plugin que:
- Detecta GPUs disponibles en cada nodo
- Expone recursos como
nvidia.com/gpu - Permite al scheduler asignar GPUs a Pods
Solicitar GPU en un Pod
resources:
limits:
nvidia.com/gpu: 1 # Número de GPUs
memory: "16Gi"
cpu: "4"
requests:
nvidia.com/gpu: 1
memory: "8Gi"
cpu: "2"
Importante: Las GPUs solo se especifican en limits, pero Kubernetes automáticamente copia el valor a requests. No puedes hacer overcommit de GPUs por defecto.
Time-Slicing: Compartir GPU entre Múltiples Pods
El NVIDIA GPU Operator permite time-slicing: múltiples Pods comparten una GPU usando el mecanismo de time-sharing de NVIDIA CUDA. Ejemplo de configuración:
apiVersion: v1
kind: ConfigMap
metadata:
name: device-plugin-config
namespace: gpu-operator
data:
any: |
version: v1
sharing:
timeSlicing:
replicas: 4 # Divide cada GPU física en 4 GPUs lógicas
Ahora puedes solicitar nvidia.com/gpu: 1 y hasta 4 Pods compartirán la GPU física. Perfecto para modelos pequeños (7B-13B) que no saturan la GPU.
Multi-Instance GPU (MIG): Particionamiento Hardware
Para GPUs A100/H100, MIG particiona la GPU a nivel hardware en instancias aisladas:
# Configurar MIG en el nodo
sudo nvidia-smi -mig 1
sudo nvidia-smi mig -cgi 9,9,9 -C # Crea 3 instancias de 20GB
# En el Pod
resources:
limits:
nvidia.com/mig-1g.10gb: 1 # Solicita 1 instancia MIG de 10GB
MIG vs Time-Slicing: MIG ofrece aislamiento total y QoS garantizada, pero solo en A100/H100. Time-slicing funciona en cualquier GPU pero sin garantías de performance.
Storage para Modelos: PV, PVC y S3
Los modelos LLM pesan 4GB-140GB. Necesitas almacenamiento persistente eficiente:
PersistentVolume (PV) y PersistentVolumeClaim (PVC)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-storage
spec:
accessModes:
- ReadWriteMany # Múltiples Pods leen simultáneamente
resources:
requests:
storage: 500Gi
storageClassName: nfs-client # O local-path, ceph, etc.
AccessModes:
- ReadWriteOnce (RWO): Un solo nodo puede montar en read-write
- ReadOnlyMany (ROX): Múltiples nodos leen (ideal para modelos inmutables)
- ReadWriteMany (RWX): Múltiples nodos leen/escriben (requiere NFS, CephFS)
S3-Compatible Object Storage
Para producción, usa almacenamiento de objetos (MinIO, S3, GCS):
# En el código del modelo
import boto3
s3 = boto3.client('s3', endpoint_url='http://minio.default.svc.cluster.local:9000')
s3.download_file('models', 'llama-3-70b.gguf', '/tmp/model.gguf')
Ventajas: Versionado, replicación automática, acceso desde cualquier nodo, integración con MLFlow/DVC.
Instalación de Kubernetes para IA: 3 Opciones
Opción 1: K3s (Lightweight, Ideal Homelab)
K3s es Kubernetes completo pero optimizado, consumiendo ~512MB RAM vs 2GB+ de K8s vanilla. Perfecto para homelabs y edge computing.
Instalación Básica
# En el servidor principal (master + worker)
curl -sfL https://get.k3s.io | sh -
# Verificar instalación
sudo k3s kubectl get nodes
# Obtener kubeconfig para usar kubectl localmente
sudo cat /var/lib/rancher/k3s/server/node-token
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER ~/.kube/config
Habilitar GPU Support en K3s
# 1. Instalar NVIDIA Container Toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit nvidia-container-runtime
# 2. Configurar containerd (K3s usa containerd por defecto)
sudo nvidia-ctk runtime configure --runtime=containerd --set-as-default
sudo systemctl restart k3s
# 3. Instalar NVIDIA Device Plugin
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.16.2/deployments/static/nvidia-device-plugin.yml
# 4. Verificar GPUs detectadas
kubectl describe nodes | grep -A 10 "Capacity:"
# Deberías ver: nvidia.com/gpu: 1 (o el número de GPUs)
Añadir Nodos Worker Adicionales
# En el nodo worker (servidor con GPU adicional)
export K3S_URL="https://IP_DEL_MASTER:6443"
export K3S_TOKEN="CONTENIDO_DE_/var/lib/rancher/k3s/server/node-token"
curl -sfL https://get.k3s.io | sh -
# En el master, verifica el nuevo nodo
kubectl get nodes
Opción 2: Minikube (Desarrollo y Testing)
Minikube crea un cluster de un solo nodo en tu laptop, ideal para desarrollo y testing de manifiestos.
# Instalación (Linux)
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Iniciar con GPU support
minikube start --driver=docker --gpus all
# Instalar NVIDIA Device Plugin
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.16.2/deployments/static/nvidia-device-plugin.yml
# Habilitar addons útiles
minikube addons enable ingress
minikube addons enable metrics-server
Limitaciones: Un solo nodo, performance limitada, no recomendado para producción.
Opción 3: Cluster Completo (Producción Empresarial)
Para producción seria, usa kubeadm o distribuciones enterprise (RKE2, OpenShift, EKS, GKE, AKS).
Instalación con kubeadm (3 nodos: 1 control plane + 2 workers GPU)
# En TODOS los nodos
# 1. Deshabilitar swap
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# 2. Instalar containerd
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo systemctl restart containerd
# 3. Instalar kubeadm, kubelet, kubectl
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# EN EL NODO CONTROL PLANE
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# Configurar kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Instalar CNI (Flannel)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# EN LOS NODOS WORKER
# Copia y ejecuta el comando "kubeadm join" que apareció en la salida de kubeadm init
sudo kubeadm join IP:6443 --token TOKEN --discovery-token-ca-cert-hash sha256:HASH
Instalar NVIDIA GPU Operator (Recomendado para Producción)
El GPU Operator automatiza la instalación de drivers, toolkit, device plugin y monitoring:
# Añadir repo de Helm
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
# Instalar GPU Operator
helm install --wait --generate-name \
-n gpu-operator --create-namespace \
nvidia/gpu-operator \
--set driver.enabled=true
# Verificar
kubectl get pods -n gpu-operator
kubectl describe nodes | grep nvidia.com/gpu
Desplegar Modelos de IA en Kubernetes: 3 Casos Prácticos
Caso 1: Ollama en Kubernetes (LLM Local)
Ollama es la forma más simple de correr LLMs localmente. Vamos a desplegarlo en Kubernetes con GPU, auto-scaling y alta disponibilidad.
Deployment Completo de Ollama
apiVersion: v1
kind: Namespace
metadata:
name: ollama
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ollama-models
namespace: ollama
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Gi
storageClassName: local-path
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama
namespace: ollama
spec:
replicas: 2 # 2 réplicas para alta disponibilidad
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
containers:
- name: ollama
image: ollama/ollama:latest
ports:
- name: http
containerPort: 11434
protocol: TCP
env:
- name: OLLAMA_HOST
value: "0.0.0.0:11434"
resources:
limits:
nvidia.com/gpu: 1
memory: "16Gi"
cpu: "4"
requests:
memory: "8Gi"
cpu: "2"
volumeMounts:
- name: models
mountPath: /root/.ollama
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: models
persistentVolumeClaim:
claimName: ollama-models
nodeSelector:
kubernetes.io/hostname: nodo-con-gpu # Ajusta según tu nodo
---
apiVersion: v1
kind: Service
metadata:
name: ollama
namespace: ollama
spec:
type: ClusterIP
selector:
app: ollama
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
Descargar Modelo Automáticamente con Job
apiVersion: batch/v1
kind: Job
metadata:
name: ollama-pull-llama3
namespace: ollama
spec:
template:
spec:
containers:
- name: ollama-pull
image: ollama/ollama:latest
command:
- /bin/sh
- -c
- |
ollama pull llama3.2:3b
ollama pull mistral:7b
env:
- name: OLLAMA_HOST
value: "http://ollama.ollama.svc.cluster.local"
restartPolicy: OnFailure
backoffLimit: 3
Exponer Ollama con Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ollama-ingress
namespace: ollama
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
spec:
ingressClassName: nginx
rules:
- host: ollama.tu-dominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ollama
port:
number: 80
Usar Ollama desde otro Pod:
curl http://ollama.ollama.svc.cluster.local/api/generate -d '{
"model": "llama3.2:3b",
"prompt": "¿Por qué Kubernetes es ideal para IA?",
"stream": false
}'
Caso 2: vLLM (Servidor de Inferencia de Alto Rendimiento)
vLLM es el servidor de inferencia más rápido para LLMs, con PagedAttention y continuous batching. Ideal para producción con alta carga.
StatefulSet de vLLM con GPU
apiVersion: v1
kind: Namespace
metadata:
name: vllm
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vllm-config
namespace: vllm
data:
MODEL_NAME: "meta-llama/Llama-3.2-3B-Instruct"
TENSOR_PARALLEL_SIZE: "1"
MAX_MODEL_LEN: "4096"
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vllm
namespace: vllm
spec:
serviceName: vllm
replicas: 2
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command:
- python3
- -m
- vllm.entrypoints.openai.api_server
args:
- --model=$(MODEL_NAME)
- --tensor-parallel-size=$(TENSOR_PARALLEL_SIZE)
- --max-model-len=$(MAX_MODEL_LEN)
- --host=0.0.0.0
- --port=8000
envFrom:
- configMapRef:
name: vllm-config
env:
- name: HUGGING_FACE_HUB_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: token
ports:
- name: http
containerPort: 8000
resources:
limits:
nvidia.com/gpu: 1
memory: "24Gi"
cpu: "8"
requests:
memory: "16Gi"
cpu: "4"
volumeMounts:
- name: cache
mountPath: /root/.cache/huggingface
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 60
periodSeconds: 10
volumeClaimTemplates:
- metadata:
name: cache
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
name: vllm
namespace: vllm
spec:
type: ClusterIP
clusterIP: None # Headless service para StatefulSet
selector:
app: vllm
ports:
- port: 8000
targetPort: http
HorizontalPodAutoscaler para vLLM
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-hpa
namespace: vllm
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: vllm
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Usar vLLM con API compatible con OpenAI:
from openai import OpenAI
client = OpenAI(
base_url="http://vllm.vllm.svc.cluster.local:8000/v1",
api_key="no-se-necesita"
)
completion = client.chat.completions.create(
model="meta-llama/Llama-3.2-3B-Instruct",
messages=[{"role": "user", "content": "Explica Kubernetes"}]
)
print(completion.choices[0].message.content)
Caso 3: Stable Diffusion/FLUX (Generación de Imágenes)
Para generación de imágenes batch o bajo demanda con queue.
Job para Generación Batch
apiVersion: batch/v1
kind: Job
metadata:
name: stable-diffusion-batch
namespace: ai-generation
spec:
parallelism: 2 # 2 pods en paralelo
completions: 10 # Genera 10 imágenes totales
template:
spec:
containers:
- name: sd-generator
image: stabilityai/stable-diffusion:latest
command:
- python
- generate.py
args:
- --prompt="Kubernetes cluster with AI models, cyberpunk style"
- --output=/output
- --steps=30
- --guidance-scale=7.5
resources:
limits:
nvidia.com/gpu: 1
memory: "16Gi"
volumeMounts:
- name: output
mountPath: /output
restartPolicy: OnFailure
volumes:
- name: output
persistentVolumeClaim:
claimName: sd-output
Deployment con Queue (Redis + Worker)
apiVersion: apps/v1
kind: Deployment
metadata:
name: sd-worker
namespace: ai-generation
spec:
replicas: 3
selector:
matchLabels:
app: sd-worker
template:
metadata:
labels:
app: sd-worker
spec:
containers:
- name: worker
image: tu-registry/sd-worker:latest
env:
- name: REDIS_URL
value: "redis://redis.ai-generation.svc.cluster.local:6379"
- name: S3_ENDPOINT
value: "http://minio.storage.svc.cluster.local:9000"
resources:
limits:
nvidia.com/gpu: 1
memory: "16Gi"
El worker consume prompts de Redis, genera imágenes y las sube a S3/MinIO.
Ecosistema MLOps: KubeFlow vs Seldon vs KServe vs Ray
Kubernetes tiene un ecosistema rico de herramientas MLOps. Elegir la correcta depende de tu caso de uso.
Comparativa Detallada de Herramientas
| Herramienta | Propósito Principal | Complejidad | GPU Support | Curva Aprendizaje | Cuándo Usar |
|---|---|---|---|---|---|
| KubeFlow | Pipelines ML end-to-end | Alta | Sí | Empinada | Equipos grandes, workflow complejo |
| Seldon Core | Model serving avanzado | Media-Alta | Sí | Moderada | Producción, A/B testing, explainability |
| KServe | Serverless ML inference | Media | Sí | Moderada | Auto-scaling, multi-framework |
| Ray on K8s | Distributed training/inference | Alta | Sí | Empinada | Fine-tuning masivo, Ray Serve |
| MLFlow on K8s | Experiment tracking | Baja | No directo | Suave | Cualquier equipo, tracking básico |
| TensorFlow Serving | Serving TF models | Baja-Media | Sí | Suave | Solo TensorFlow/Keras |
| Triton Inference Server | Multi-framework serving | Media | Sí | Moderada | NVIDIA GPUs, TensorRT |
KubeFlow: La Plataforma Completa
KubeFlow es una plataforma end-to-end para ML en Kubernetes. Incluye:
- Kubeflow Pipelines: Orquestación de workflows ML
- Katib: Hyperparameter tuning automático
- Training Operators: TFJob, PyTorchJob, MPIJob
- Notebooks: JupyterLab integrado
- KServe: Model serving (anteriormente KFServing)
Instalación de KubeFlow
# Usando kustomize
kubectl apply -k "github.com/kubeflow/manifests/example?ref=v1.9.0"
# O usando distribuciond específica (más simple)
# MiniKF para testing local
# Charmed Kubeflow para producción
Cuándo usar KubeFlow:
- Equipos de 5+ data scientists
- Pipelines complejos con múltiples etapas
- Necesitas notebook environments gestionados
- Hyperparameter tuning automático
- Compliance y auditoría (multi-tenancy)
Cuándo NO usar KubeFlow:
- Solo necesitas inferencia (overkill)
- Equipo pequeño (1-3 personas)
- Cluster pequeño (<32GB RAM total)
Seldon Core: Serving Avanzado
Seldon Core se enfoca en model serving con features avanzadas:
- Inference Graphs: Combina múltiples modelos (routers, transformers, combiners)
- A/B Testing: Traffic splitting entre versiones
- Explainability: Integración con Alibi Explain
- Outlier Detection: Detecta inputs anómalos
- Métricas avanzadas: Prometheus out-of-the-box
Ejemplo: Desplegar Modelo SKLearn con Seldon
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: iris-model
namespace: seldon
spec:
predictors:
- name: default
replicas: 3
graph:
name: classifier
implementation: SKLEARN_SERVER
modelUri: s3://models/iris-model
envSecretRefName: s3-credentials
componentSpecs:
- spec:
containers:
- name: classifier
resources:
limits:
memory: "2Gi"
cpu: "1"
Ventajas de Seldon: Maduro, features ricas, documentación excelente, soporte comercial.
Desventajas: Más complejo que KServe, menor momentum de comunidad.
KServe: Serverless e Integración con Istio
KServe (antes KFServing) es lightweight y se integra con service mesh (Istio/Kourier):
- Serverless: Scale-to-zero cuando no hay tráfico
- Multi-framework: TensorFlow, PyTorch, SKLearn, XGBoost, ONNX
- Canary rollouts: Despliegues graduales
- Autoscaling: Basado en requests/sec (KPA)
Ejemplo: Desplegar Modelo PyTorch con KServe
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: pytorch-llm
namespace: kserve
spec:
predictor:
pytorch:
storageUri: s3://models/llama-3-8b
resources:
limits:
nvidia.com/gpu: 1
memory: 16Gi
requests:
memory: 8Gi
env:
- name: STORAGE_URI
value: s3://models/llama-3-8b
Ventajas de KServe: Simple, moderna, buena integración con cloud-native stack, scale-to-zero.
Cuándo elegir KServe sobre Seldon: Si priorizas simplicidad y serverless sobre features avanzadas.
Ray on Kubernetes: Distributed Training
Ray es framework para computación distribuida. Ray on Kubernetes permite:
- Distributed Training: Fine-tuning con múltiples GPUs/nodos
- Ray Serve: Serving de modelos con Ray
- Hyperparameter Tuning: Ray Tune
Desplegar Ray Cluster
# Usar KubeRay Operator
helm repo add kuberay https://ray-project.github.io/kuberay-helm/
helm install kuberay-operator kuberay/kuberay-operator
# Crear RayCluster
kubectl apply -f - <<EOF
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: raycluster-gpu
spec:
rayVersion: '2.9.0'
headGroupSpec:
serviceType: ClusterIP
replicas: 1
template:
spec:
containers:
- name: ray-head
image: rayproject/ray-ml:2.9.0-gpu
resources:
limits:
memory: "8Gi"
cpu: "4"
workerGroupSpecs:
- groupName: gpu-workers
replicas: 2
template:
spec:
containers:
- name: ray-worker
image: rayproject/ray-ml:2.9.0-gpu
resources:
limits:
nvidia.com/gpu: 1
memory: "16Gi"
EOF
Cuándo usar Ray: Fine-tuning distribuido de LLMs grandes (70B+), RL training, large-scale batch inference.
MLFlow on Kubernetes: Experiment Tracking
MLFlow no es específico de Kubernetes pero se despliega fácilmente:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mlflow
namespace: mlflow
spec:
replicas: 1
selector:
matchLabels:
app: mlflow
template:
metadata:
labels:
app: mlflow
spec:
containers:
- name: mlflow
image: ghcr.io/mlflow/mlflow:v2.18.0
command:
- mlflow
- server
- --host=0.0.0.0
- --port=5000
- --backend-store-uri=postgresql://user:pass@postgres.mlflow.svc.cluster.local/mlflow
- --default-artifact-root=s3://mlflow-artifacts/
ports:
- containerPort: 5000
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: s3-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: secret-key
Cuándo usar MLFlow: Cualquier equipo necesita tracking. Es simple, efectivo y universal.
Networking, Ingress y Seguridad
Ingress Controllers para Servicios ML
Un Ingress Controller expone servicios HTTP(S) externamente con routing basado en host/path.
NGINX Ingress (Más Popular)
# Instalación
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml
# Verificar
kubectl get pods -n ingress-nginx
Traefik (Cloud-Native, Dashboard)
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik -n traefik --create-namespace
Configuración Específica para LLMs
Los LLMs necesitan timeouts largos (streaming) y body size grande:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: llm-ingress
namespace: ai
annotations:
# NGINX
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-buffering: "off" # Para streaming
# Traefik
traefik.ingress.kubernetes.io/router.middlewares: default-ratelimit@kubernetescrd
spec:
ingressClassName: nginx
tls:
- hosts:
- llm.tu-dominio.com
secretName: llm-tls
rules:
- host: llm.tu-dominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vllm
port:
number: 8000
mTLS: Comunicación Segura entre Servicios
mTLS (mutual TLS) encripta y autentica tráfico entre servicios. Usa un service mesh:
Instalar Istio
curl -L https://istio.io/downloadIstio | sh -
cd istio-*
./bin/istioctl install --set profile=default -y
# Habilitar sidecar injection en namespace
kubectl label namespace ai istio-injection=enabled
Beneficios: Encriptación automática, observabilidad (Jaeger), retries y circuit breakers.
Costo: 100-200MB RAM extra por Pod (sidecar proxy).
Rate Limiting para APIs de Modelos
Protege tus modelos de abuso con rate limiting:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: ratelimit
namespace: default
spec:
rateLimit:
average: 100 # 100 requests por período
period: 1m
burst: 50 # Ráfaga máxima
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: llm-protected
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-ratelimit@kubernetescrd
spec:
# ... resto de config
Network Policies: Aislamiento de Tráfico
Controla qué Pods pueden comunicarse:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: llm-isolation
namespace: ai
spec:
podSelector:
matchLabels:
app: vllm
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: api-gateway
ports:
- protocol: TCP
port: 8000
egress:
- to:
- namespaceSelector:
matchLabels:
name: storage
ports:
- protocol: TCP
port: 9000 # MinIO
Esto permite que solo el API Gateway hable con vLLM, y vLLM solo hable con MinIO.
GPU Management Avanzado
NVIDIA GPU Operator: Automatización Total
El GPU Operator despliega y gestiona todos los componentes GPU:
- NVIDIA Driver
- NVIDIA Container Toolkit
- NVIDIA Device Plugin
- GPU Feature Discovery
- DCGM Exporter (métricas)
Instalación y Configuración
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install gpu-operator nvidia/gpu-operator \
-n gpu-operator --create-namespace \
--set driver.enabled=true \
--set toolkit.enabled=true \
--set devicePlugin.enabled=true \
--set dcgmExporter.enabled=true \
--set gfd.enabled=true
Time-Slicing: Múltiples Pods por GPU
Configura time-slicing para compartir GPUs:
apiVersion: v1
kind: ConfigMap
metadata:
name: device-plugin-config
namespace: gpu-operator
data:
any: |
version: v1
sharing:
timeSlicing:
replicas: 8 # Divide cada GPU en 8 slices
renameByDefault: false
failRequestsGreaterThanOne: false
Aplica el config:
kubectl create -f device-plugin-config.yaml
kubectl patch clusterpolicy/cluster-policy \
-n gpu-operator --type merge \
-p '{"spec": {"devicePlugin": {"config": {"name": "device-plugin-config"}}}}'
Ahora cada GPU física expone 8 recursos lógicos. Pods con nvidia.com/gpu: 1 obtienen 1/8 de GPU.
Multi-Instance GPU (MIG): Particionamiento Hardware
Para A100/H100, MIG ofrece aislamiento real:
Configurar MIG en Nodos
# En cada nodo con A100/H100
sudo nvidia-smi -mig 1 # Habilitar modo MIG
# Crear perfiles (ejemplo: 3 instancias de 20GB)
sudo nvidia-smi mig -cgi 9,9,9 -C
# Verificar instancias creadas
nvidia-smi mig -lgi
Etiquetar Nodo y Usar en Pod
# GPU Operator detecta MIG automáticamente y expone:
# nvidia.com/mig-1g.5gb (1 GPU compute, 5GB mem)
# nvidia.com/mig-2g.10gb
# nvidia.com/mig-3g.20gb
# nvidia.com/mig-4g.20gb
# nvidia.com/mig-7g.40gb
# En el Pod
resources:
limits:
nvidia.com/mig-3g.20gb: 1 # Solicita instancia MIG de 3 compute slices, 20GB
Ventajas MIG: Aislamiento de memoria, QoS garantizada, múltiples usuarios sin interferencia.
Limitaciones: Solo A100/H100, perfiles fijos (no arbitrarios).
Priority Classes: Prioridad de Workloads
Define prioridades para que training ceda GPUs a inferencia:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority-inference
value: 1000000
globalDefault: false
description: "Inferencia en producción - máxima prioridad"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority-training
value: 100
globalDefault: false
description: "Training batch - puede ser preempted"
Usa en Pods:
spec:
priorityClassName: high-priority-inference
containers:
- name: vllm
# ...
Si GPUs están saturadas, Kubernetes mata Pods de baja prioridad para hacer espacio a alta prioridad.
Costos por Namespace: FinOps para GPUs
Trackea costos con ResourceQuotas:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-data-science
namespace: data-science
spec:
hard:
requests.nvidia.com/gpu: "4" # Máximo 4 GPUs totales
limits.memory: "128Gi"
limits.cpu: "64"
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["low-priority-training"]
Combina con Kubecost para dashboards de costos por equipo/proyecto.
Pipelines ML Completos: De Training a Producción
Workflow ML End-to-End en Kubernetes
Un pipeline completo incluye:
- Data Ingestion: Descargar/procesar datos
- Training: Fine-tuning o training desde cero
- Validation: Evaluar métricas
- Model Registry: Versionar y almacenar modelo
- Deployment: Desplegar a producción
- A/B Testing: Comparar versiones
- Monitoring: Detectar drift
Ejemplo: Pipeline de Fine-Tuning con KubeFlow Pipelines
from kfp import dsl, compiler
@dsl.component
def download_data(dataset_name: str, output_path: dsl.OutputPath(str)):
from datasets import load_dataset
dataset = load_dataset(dataset_name)
dataset.save_to_disk(output_path)
@dsl.component(base_image='pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime')
def finetune_model(
data_path: dsl.InputPath(str),
model_name: str,
output_model: dsl.OutputPath(str)
):
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments
import torch
model = AutoModelForCausalLM.from_pretrained(model_name)
# ... código de fine-tuning
model.save_pretrained(output_model)
@dsl.component
def evaluate_model(model_path: dsl.InputPath(str)) -> float:
# Evaluar y retornar accuracy
return 0.95
@dsl.component
def deploy_model(model_path: dsl.InputPath(str), min_accuracy: float, actual_accuracy: float):
if actual_accuracy >= min_accuracy:
# Desplegar a KServe/Seldon
pass
else:
raise ValueError(f"Accuracy {actual_accuracy} menor que mínimo {min_accuracy}")
@dsl.pipeline(
name='LLM Fine-Tuning Pipeline',
description='Pipeline completo de fine-tuning'
)
def llm_finetuning_pipeline(
dataset: str = 'tatsu-lab/alpaca',
model: str = 'meta-llama/Llama-3.2-3B',
min_accuracy: float = 0.90
):
data_op = download_data(dataset_name=dataset)
train_op = finetune_model(
data_path=data_op.outputs['output_path'],
model_name=model
).set_gpu_limit(1).set_memory_limit('32Gi')
eval_op = evaluate_model(model_path=train_op.outputs['output_model'])
deploy_op = deploy_model(
model_path=train_op.outputs['output_model'],
min_accuracy=min_accuracy,
actual_accuracy=eval_op.output
)
# Compilar y ejecutar
compiler.Compiler().compile(llm_finetuning_pipeline, 'pipeline.yaml')
A/B Testing de Modelos
Con Seldon Core:
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
name: ab-test-llm
spec:
predictors:
- name: model-v1
replicas: 2
traffic: 80 # 80% del tráfico
graph:
name: classifier
implementation: SKLEARN_SERVER
modelUri: s3://models/llm-v1
- name: model-v2
replicas: 1
traffic: 20 # 20% del tráfico (canary)
graph:
name: classifier
implementation: SKLEARN_SERVER
modelUri: s3://models/llm-v2
Monitorea métricas y gradualmente incrementa tráfico a v2.
CI/CD para Modelos ML
Ejemplo con GitHub Actions + ArgoCD:
# .github/workflows/train-and-deploy.yml
name: Train and Deploy Model
on:
push:
paths:
- 'models/**'
- 'training/**'
jobs:
train:
runs-on: self-hosted # Runner con GPU
steps:
- uses: actions/checkout@v4
- name: Train Model
run: |
python train.py --epochs 10 --output model.pth
- name: Upload to Model Registry
run: |
mlflow models log-model model.pth -n llm-v2
- name: Update K8s Manifest
run: |
yq e '.spec.predictors[0].graph.modelUri = "mlflow://llm-v2"' -i k8s/deployment.yaml
git add k8s/deployment.yaml
git commit -m "Update model to v2"
git push
ArgoCD detecta cambios en Git y despliega automáticamente:
kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ml-models
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/tu-org/ml-models
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: ai
syncPolicy:
automated:
prune: true
selfHeal: true
EOF
Storage y Gestión de Datos
MinIO: Object Storage S3-Compatible
MinIO es ideal para almacenar modelos, datasets y artifacts:
helm repo add minio https://charts.min.io/
helm install minio minio/minio \
--namespace storage --create-namespace \
--set mode=standalone \
--set persistence.size=1Ti \
--set resources.requests.memory=4Gi \
--set rootUser=admin \
--set rootPassword=tu-password-segura
Configurar en MLFlow:
export MLFLOW_S3_ENDPOINT_URL=http://minio.storage.svc.cluster.local:9000
export AWS_ACCESS_KEY_ID=admin
export AWS_SECRET_ACCESS_KEY=tu-password-segura
mlflow server \
--backend-store-uri postgresql://... \
--default-artifact-root s3://mlflow-artifacts/
NFS para Datasets Compartidos
Para datasets grandes leídos por múltiples Pods:
apiVersion: v1
kind: PersistentVolume
metadata:
name: datasets-nfs
spec:
capacity:
storage: 5Ti
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.100 # Tu servidor NFS
path: /export/datasets
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: datasets
namespace: ai
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Ti
Cache de Embeddings con Redis
Para evitar regenerar embeddings:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-embeddings
namespace: ai
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
spec:
containers:
- name: redis
image: redis:7-alpine
args:
- --maxmemory 16gb
- --maxmemory-policy allkeys-lru
resources:
limits:
memory: "16Gi"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: redis-data
Uso desde Python:
import redis
import hashlib
r = redis.Redis(host='redis-embeddings.ai.svc.cluster.local')
def get_embedding(text: str, model):
cache_key = hashlib.sha256(text.encode()).hexdigest()
cached = r.get(cache_key)
if cached:
return eval(cached) # Deserializar
embedding = model.encode(text)
r.setex(cache_key, 3600*24*7, str(embedding.tolist())) # Cache 7 días
return embedding
Backup y Disaster Recovery
Usa Velero para backups automáticos:
velero install \
--provider aws \
--bucket velero-backups \
--backup-location-config region=us-west-2,s3Url=http://minio.storage.svc.cluster.local:9000 \
--snapshot-location-config region=us-west-2
# Backup automático diario
velero schedule create daily-backup --schedule="0 2 * * *"
# Backup específico de namespace AI
velero backup create ai-backup --include-namespaces ai,vllm,ollama
Monitorización y Observabilidad
Prometheus + Grafana: Métricas Básicas
Instalar Stack de Monitorización
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \
--set grafana.adminPassword=tu-password
Acceder a Grafana:
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
# Abrir http://localhost:3000 (admin / tu-password)
DCGM Exporter: Métricas de GPU
El GPU Operator instala DCGM Exporter automáticamente. Métricas expuestas:
DCGM_FI_DEV_GPU_UTIL: Utilización GPU (%)DCGM_FI_DEV_MEM_COPY_UTIL: Utilización memoriaDCGM_FI_DEV_FB_USED: Memoria GPU usada (MB)DCGM_FI_DEV_POWER_USAGE: Consumo eléctrico (W)DCGM_FI_DEV_GPU_TEMP: Temperatura (°C)
Dashboard de Grafana para GPUs
Importa dashboard ID 12239 (NVIDIA DCGM Exporter Dashboard) desde Grafana Labs.
Query Prometheus Personalizada
# GPUs con más de 90% utilización
DCGM_FI_DEV_GPU_UTIL > 90
# Temperatura promedio por nodo
avg by (instance) (DCGM_FI_DEV_GPU_TEMP)
# Memoria GPU usada vs total
DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE * 100
Loki: Logs Agregados
Loki es Prometheus para logs:
helm install loki grafana/loki-stack \
--namespace monitoring \
--set grafana.enabled=false \
--set prometheus.enabled=false
Consultar logs en Grafana:
{namespace="ai", app="vllm"} |= "error"
{namespace="ai"} | json | line_format "{{.level}}: {{.message}}"
Jaeger: Distributed Tracing
Para debugging de latencia en pipelines complejos:
kubectl create namespace observability
kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.61.0/jaeger-operator.yaml -n observability
kubectl apply -f - <<EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger-all-in-one
namespace: observability
spec:
strategy: allInOne
ingress:
enabled: true
EOF
AlertManager: Alertas Inteligentes
Configura alertas críticas:
apiVersion: v1
kind: ConfigMap
metadata:
name: alertmanager-config
namespace: monitoring
data:
alertmanager.yml: |
route:
receiver: 'slack'
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
receivers:
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/TU_WEBHOOK'
channel: '#ml-ops-alerts'
title: 'Alerta K8s ML'
text: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ml-alerts
namespace: monitoring
spec:
groups:
- name: ml-workloads
interval: 30s
rules:
- alert: GPUHighUtilization
expr: DCGM_FI_DEV_GPU_UTIL > 95
for: 10m
annotations:
summary: "GPU {{ $labels.gpu }} en nodo {{ $labels.instance }} >95% por 10min"
- alert: ModelInferenceLatencyHigh
expr: histogram_quantile(0.95, rate(request_duration_seconds_bucket[5m])) > 2
for: 5m
annotations:
summary: "P95 latency de inferencia >2s"
- alert: OOMKilledPod
expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
annotations:
summary: "Pod {{ $labels.pod }} en {{ $labels.namespace }} fue OOMKilled"
Troubleshooting: 15 Problemas Comunes y Soluciones
1. Pod Pending: No GPU Disponible
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# ollama-5d7f8b9c-xxxxx 0/1 Pending 0 5m
Diagnóstico:
kubectl describe pod ollama-5d7f8b9c-xxxxx
# Events:
# Warning FailedScheduling 5m default-scheduler 0/3 nodes are available: 3 Insufficient nvidia.com/gpu.
Causas:
- No hay nodos con GPU
- Todas las GPUs están asignadas
- Device plugin no está instalado
Solución:
# Verificar GPUs disponibles
kubectl describe nodes | grep -A 10 "Capacity:"
# Instalar device plugin si falta
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.16.2/deployments/static/nvidia-device-plugin.yml
# Configurar time-slicing si necesitas compartir GPUs
2. OOMKilled: Out of Memory
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# vllm-0 0/1 OOMKilled 3 10m
Diagnóstico:
kubectl describe pod vllm-0 | grep -A 5 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
Causas:
- Memory limit muy bajo para el modelo
- Modelo carga más datos de los esperados
- Batch size muy grande
Solución:
resources:
limits:
memory: "32Gi" # Aumentar según modelo (7B ~16GB, 13B ~32GB, 70B ~140GB)
nvidia.com/gpu: 1
requests:
memory: "24Gi"
Ajusta también parámetros del modelo:
# Para vLLM
--max-model-len=2048 # Reducir context window
--gpu-memory-utilization=0.90 # Usar solo 90% VRAM
3. ImagePullBackOff: No Puede Descargar Imagen
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# model-xxx 0/1 ImagePullBackOff 0 2m
Diagnóstico:
kubectl describe pod model-xxx
# Events:
# Warning Failed 2m kubelet Failed to pull image "registry.privado.com/model:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied
Solución:
# Crear secret de Docker registry
kubectl create secret docker-registry regcred \
--docker-server=registry.privado.com \
--docker-username=tu-usuario \
--docker-password=tu-password \
--docker-email=tu-email@ejemplo.com \
-n ai
# Usar en Pod
spec:
imagePullSecrets:
- name: regcred
containers:
- name: model
image: registry.privado.com/model:latest
4. Modelo No Carga: Permisos de PersistentVolume
Síntoma: Pod inicia pero logs muestran error al leer modelo.
Diagnóstico:
kubectl logs ollama-xxx
# Error: open /root/.ollama/models/llama3: permission denied
Causas: Usuario del contenedor no tiene permisos en el volumen.
Solución:
spec:
securityContext:
fsGroup: 1000 # Grupo del usuario del contenedor
runAsUser: 1000
containers:
- name: ollama
# ...
O arregla permisos en el volumen:
kubectl exec -it ollama-xxx -- chown -R 1000:1000 /root/.ollama
5. Inferencia Lenta: GPU Sharing Mal Configurado
Síntoma: Latencia 10x más lenta de lo esperado.
Diagnóstico:
# Verificar cuántos Pods comparten GPU
kubectl get pods -o json | jq '.items[] | select(.spec.containers[].resources.limits."nvidia.com/gpu") | .metadata.name'
# Ver utilización real
kubectl exec -it nvidia-device-plugin-xxx -n gpu-operator -- nvidia-smi
Causas: Time-slicing con demasiados replicas o modelos grandes compartiendo GPU.
Solución:
- Reducir replicas de time-slicing (de 8 a 4)
- Usar GPUs dedicadas para modelos grandes (70B+)
- Configurar MIG si tienes A100/H100
6. CrashLoopBackOff: Contenedor Reinicia Continuamente
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# model-xxx 0/1 CrashLoopBackOff 5 3m
Diagnóstico:
kubectl logs model-xxx --previous # Ver logs del contenedor anterior
kubectl describe pod model-xxx
Causas comunes:
- Comando mal configurado
- Dependencias faltantes
- Healthcheck demasiado agresivo
Solución: Ajusta probes:
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120 # Dar tiempo al modelo a cargar
periodSeconds: 30
failureThreshold: 5
7. DNS Resolution Failed: Servicio No Encontrado
Síntoma: Aplicación no puede conectar a otro servicio.
Diagnóstico:
kubectl exec -it app-pod -- nslookup ollama.ollama.svc.cluster.local
# Server: 10.96.0.10
# Address: 10.96.0.10:53
# ** server can't find ollama.ollama.svc.cluster.local: NXDOMAIN
Solución:
- Verificar nombre del servicio:
kubectl get svc -n ollama - Usar FQDN completo:
servicio.namespace.svc.cluster.local - Verificar CoreDNS:
kubectl get pods -n kube-system | grep coredns
8. PVC Pending: No PersistentVolume Disponible
Síntoma:
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# model-storage Pending local-path 5m
Diagnóstico:
kubectl describe pvc model-storage
# Events:
# Warning ProvisioningFailed 5m persistentvolume-controller storageclass.storage.k8s.io "local-path" not found
Solución:
# Instalar Rancher Local Path Provisioner
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
# O usar NFS provisioner si tienes NFS server
9. Timeout en Ingress: Requests Grandes Fallan
Síntoma: Requests HTTP a modelos timeout después de 60s.
Solución:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
10. Node NotReady: GPU Driver Issue
Síntoma:
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# gpu-node-1 NotReady worker 10m v1.31.0
Diagnóstico:
kubectl describe node gpu-node-1
# Conditions:
# Ready False KubeletNotReady container runtime not ready
Solución:
# SSH al nodo y verificar kubelet
sudo systemctl status kubelet
# Verificar runtime (containerd/docker)
sudo systemctl status containerd
# Verificar drivers NVIDIA
nvidia-smi
# Reiniciar servicios si es necesario
sudo systemctl restart containerd kubelet
11. ResourceQuota Exceeded: No Puede Crear Más Pods
Síntoma: Deployment no puede escalar.
Diagnóstico:
kubectl describe resourcequota -n ai
# Name: team-quota
# Resource Used Hard
# -------- ---- ----
# nvidia.com/gpu 4 4
Solución: Aumentar quota o liberar recursos.
12. Network Policy Bloquea Tráfico
Síntoma: Pods no pueden comunicarse.
Diagnóstico:
kubectl exec -it app-pod -- curl ollama.ollama.svc.cluster.local
# curl: (7) Failed to connect to ollama.ollama.svc.cluster.local port 80: Connection timed out
Solución: Revisar NetworkPolicies:
kubectl get networkpolicies -n ollama
kubectl describe networkpolicy -n ollama
13. Model Drift: Accuracy Degradada
Detección: Usa Evidently AI o Alibi Detect.
Solución: Pipeline automático de reentrenamiento cuando drift detectado.
14. GPU Throttling: Temperatura Alta
Diagnóstico:
kubectl exec -it nvidia-device-plugin-xxx -n gpu-operator -- nvidia-smi
# GPU Name Temp Perf Pwr:Usage/Cap
# 0 RTX 4090 89C P2 420W / 450W # ALTA
Solución:
- Mejorar cooling del servidor
- Reducir carga (menos replicas)
- Limitar power:
sudo nvidia-smi -pl 350(350W limit)
15. Etcd Database Full: Cluster Degradado
Síntoma: Operaciones lentas, errores «etcdserver: mvcc: database space exceeded».
Solución:
# Compactar etcd
kubectl exec -n kube-system etcd-master -- etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
compact $(kubectl exec -n kube-system etcd-master -- etcdctl endpoint status --write-out="json" | jq -r '.[0].Status.header.revision')
# Defragmentar
kubectl exec -n kube-system etcd-master -- etcdctl defrag
Costos y Optimización: FinOps para GPUs
Spot Instances para Training
En cloud, usa instancias spot (hasta 90% descuento) para training:
apiVersion: batch/v1
kind: Job
metadata:
name: finetune-spot
spec:
template:
spec:
nodeSelector:
karpenter.sh/capacity-type: spot # AWS Karpenter
# O cloud.google.com/gke-preemptible: "true" (GKE)
# O kubernetes.azure.com/scalesetpriority: spot (AKS)
tolerations:
- key: karpenter.sh/capacity-type
operator: Equal
value: spot
effect: NoSchedule
containers:
- name: trainer
# ...
Cluster Autoscaler: Escala Nodos Según Demanda
helm repo add autoscaler https://kubernetes.github.io/autoscaler
helm install cluster-autoscaler autoscaler/cluster-autoscaler \
--set autoDiscovery.clusterName=mi-cluster \
--set awsRegion=us-west-2
Escala nodos GPU de 0 a N según carga, ahorrando costos en horas valle.
Karpenter: Auto-scaling Inteligente
Karpenter (AWS) es más eficiente que Cluster Autoscaler:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu-pool
spec:
template:
spec:
requirements:
- key: karpenter.k8s.aws/instance-gpu-count
operator: Gt
values: ["0"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
nodeClassRef:
name: default
limits:
cpu: "1000"
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 300s # Termina nodos vacíos después de 5min
Kubecost: Visibility de Costos
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost --create-namespace \
--set prometheus.server.global.external_labels.cluster_id=prod-cluster
Dashboard en http://localhost:9090 muestra:
- Costo por namespace, deployment, pod
- GPU utilization vs cost
- Recomendaciones de right-sizing
Best Practices de FinOps
- Tag todo: Labels de equipo, proyecto, entorno
- ResourceQuotas: Limita recursos por equipo
- PriorityClasses: Training en baja prioridad
- Scale-to-zero: Inferencia serverless con KServe
- Rightsizing: Ajusta requests/limits según métricas reales
- Spot para training: Checkpoints frecuentes
- GPU sharing: Time-slicing para modelos pequeños
- Cluster autoscaler: Escala nodos según demanda
Alternativas a Kubernetes: Cuándo Usar Qué
Docker Compose: <3 Servicios, Homelab Simple
Usa cuando:
- 1-3 servicios de IA (Ollama + Open WebUI)
- Un solo servidor
- No necesitas auto-scaling
- Simplicidad > Features
Ver: Guía Docker Compose para Homelab IA
Docker Swarm: Middle Ground
Ventajas: Más simple que K8s, multi-nodo, scaling básico.
Desventajas: Ecosistema pobre, GPU support limitado, menor momentum.
Usa cuando: Ya usas Docker y necesitas 2-3 nodos sin complejidad de K8s.
HashiCorp Nomad: Si Odias YAML
Nomad es alternativa a K8s más simple:
job "ollama" {
datacenters = ["dc1"]
group "llm" {
task "ollama" {
driver = "docker"
config {
image = "ollama/ollama:latest"
}
resources {
device "nvidia/gpu" {
count = 1
}
}
}
}
}
Ventajas: Sintaxis HCL, simple, soporta Docker/QEMU/Java.
Desventajas: Ecosistema MLOps limitado vs K8s.
Ray Cluster Standalone: Solo Distributed Training
Si solo necesitas distributed training/inference, Ray standalone (sin K8s) es más simple.
Migración desde Docker Compose a Kubernetes
Kompose: Converter Automático
Kompose convierte docker-compose.yml a manifiestos K8s:
# Instalar
curl -L https://github.com/kubernetes/kompose/releases/download/v1.34.0/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv kompose /usr/local/bin/
# Convertir
kompose convert -f docker-compose.yml
# Genera: deployment.yaml, service.yaml, persistentvolumeclaim.yaml
Migración Manual: Paso a Paso
Docker Compose Original
services:
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama-data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
volumes:
ollama-data:
Equivalente Kubernetes
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ollama-data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 100Gi
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama
spec:
replicas: 1
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
containers:
- name: ollama
image: ollama/ollama:latest
ports:
- containerPort: 11434
volumeMounts:
- name: data
mountPath: /root/.ollama
resources:
limits:
nvidia.com/gpu: 1
volumes:
- name: data
persistentVolumeClaim:
claimName: ollama-data
---
# Service
apiVersion: v1
kind: Service
metadata:
name: ollama
spec:
selector:
app: ollama
ports:
- port: 11434
targetPort: 11434
Diferencias Clave en Migración
| Docker Compose | Kubernetes | Notas |
|---|---|---|
ports: "11434:11434" |
Service + Ingress | K8s separa networking en objetos |
volumes: nombre |
PVC + PV | K8s requiere claims explícitos |
depends_on |
Init containers | K8s no garantiza orden de inicio |
environment |
ConfigMap/Secret | K8s separa config de manifiestos |
deploy.replicas |
spec.replicas |
Similar pero K8s más robusto |
restart: unless-stopped |
restartPolicy: Always |
K8s auto-restart por defecto |
Compatibilidad con n8n, SwarmUI, etc.
Servicios populares de homelab funcionan perfectamente en K8s:
- n8n: Ver integración n8n con Kubernetes
- SwarmUI: Similar a ejemplo Stable Diffusion arriba
- ComfyUI: StatefulSet con PVC para workflows
- Open WebUI: Deployment + Service apuntando a Ollama
Preguntas Frecuentes (FAQs)
¿Necesito Kubernetes para IA Casera?
No necesariamente. Si tienes 1-3 servicios en un servidor, Docker Compose es suficiente. Kubernetes vale la pena si:
- Tienes 2+ servidores con GPUs
- Necesitas alta disponibilidad
- Múltiples equipos/proyectos comparten recursos
- Quieres aprender K8s para carrera profesional
¿Kubernetes Funciona con Una Sola GPU?
Sí, perfectamente. Usa time-slicing para compartir la GPU entre múltiples modelos pequeños (7B-13B). Es ideal para homelabbers con una RTX 4090 o similar.
¿Puedo Correr Ollama en Kubernetes?
Absolutamente. Ver Caso 1: Ollama en Kubernetes arriba con deployment completo, auto-scaling e ingress.
¿Kubernetes vs Docker Compose para ML?
| Criterio | Docker Compose | Kubernetes |
|---|---|---|
| Setup inicial | 5 minutos | 1-2 horas |
| Curva aprendizaje | Suave | Empinada |
| Multi-nodo | No (solo con Swarm) | Sí, nativo |
| Auto-scaling | No | Sí (HPA, VPA, KEDA) |
| GPU sharing | No | Sí (time-slicing, MIG) |
| Producción enterprise | No recomendado | Estándar |
¿Cómo Escalar Modelos en Kubernetes?
Tres formas:
- Manual:
kubectl scale deployment ollama --replicas=5 - HPA (Horizontal Pod Autoscaler): Escala automáticamente según CPU/memoria/métricas custom
- KEDA: Escala basado en eventos (queue depth, HTTP requests, cron)
¿Qué Es KubeFlow y Lo Necesito?
KubeFlow es plataforma end-to-end para ML en K8s. Lo necesitas si:
- Equipo grande (5+ data scientists)
- Pipelines complejos con múltiples etapas
- Necesitas notebooks managed
- Hyperparameter tuning automático
Si solo necesitas inferencia, KServe o Seldon son más livianos.
¿Kubernetes Consume Muchos Recursos?
Depende de la distribución:
- K3s: ~512MB RAM, perfecto para homelab
- Minikube: ~1GB RAM
- K8s vanilla: ~2GB RAM para control plane
- KubeFlow: +8GB RAM adicionales
Para un homelab con GPU, el overhead de K3s es insignificante vs beneficios.
¿Puedo Usar K3s para Producción?
Sí. K3s es Kubernetes certificado CNCF. Se usa en producción en edge computing, IoT y startups. No recomendado para clusters masivos (100+ nodos) donde distribuciones enterprise (RKE2, OpenShift) son mejores.
¿Cómo Monitorear GPUs en Kubernetes?
- Instalar NVIDIA GPU Operator (incluye DCGM Exporter)
- Instalar Prometheus + Grafana
- Importar dashboard 12239 en Grafana
- Configurar alertas para >90% utilización, >85°C temperatura
¿Kubernetes para Fine-Tuning o Solo Inferencia?
Ambos. Kubernetes es excelente para:
- Inferencia: Deployments con auto-scaling, alta disponibilidad
- Training/Fine-tuning: Jobs para entrenamientos batch, CronJobs para reentrenamientos periódicos
- Distributed training: PyTorchJob, TFJob, Ray on K8s
¿Cómo Gestionar Múltiples Modelos?
Estrategias:
- Namespaces: Un namespace por proyecto/equipo
- Multi-tenancy: KubeFlow con perfiles de usuario
- Model router: Seldon Inference Graph enruta requests al modelo correcto
- KServe: Múltiples InferenceServices con routing por path
¿Kubernetes en Raspberry Pi Cluster?
Sí pero limitado. K3s funciona excelente en RPi para aprender, pero:
- No soporta GPUs (RPi no tiene CUDA)
- Solo para modelos pequeños cuantizados (CPU inference)
- Mejor para aprender conceptos que para workloads reales de IA
¿Cuánto Cuesta Kubernetes para IA?
On-premise (homelab): Solo hardware ($0 cloud cost)
Cloud:
- AWS EKS: $0.10/hora cluster + instancias (g5.xlarge ~$1.00/hora GPU)
- GKE: Similar, con buenas opciones de preemptible GPUs
- AKS: Comparable a AWS
Optimización: Usa spot instances para training (hasta 90% descuento) y cluster autoscaler.
¿Kubernetes en Homelab Vale la Pena?
Vale la pena si:
- Tienes 2+ servidores o planeas escalar
- Quieres aprender K8s (muy valioso profesionalmente)
- Disfrutas la infraestructura tanto como la IA
- Necesitas features avanzadas (GPU sharing, auto-scaling)
No vale la pena si:
- Solo quieres «usar» IA, no gestionarla
- Un servidor con Docker Compose te satisface
- Tiempo de setup es crítico
¿Cómo Hacer Backup de Modelos en Kubernetes?
- Velero: Backups automáticos de PVs y manifiestos
- Exportar a S3/MinIO: Modelos en object storage con versionado
- Git para manifiestos: GitOps con ArgoCD
- Model registry: MLFlow/DVC trackea versiones
Conclusión: Tu Roadmap para Kubernetes + IA
Nivel 1: Principiante (1-2 semanas)
- Instala K3s en un servidor
- Habilita GPU support con NVIDIA Device Plugin
- Despliega Ollama con el YAML de este tutorial
- Expone con Ingress y accede desde tu red local
- Instala Prometheus + Grafana para métricas básicas
Nivel 2: Intermedio (1 mes)
- Configura time-slicing para compartir GPU
- Despliega vLLM o TensorFlow Serving
- Implementa HorizontalPodAutoscaler
- Configura PersistentVolumes con NFS o MinIO
- Añade NetworkPolicies para seguridad
- Implementa MLFlow para experiment tracking
Nivel 3: Avanzado (2-3 meses)
- Despliega KubeFlow o Seldon Core
- Implementa pipeline de CI/CD con GitHub Actions + ArgoCD
- Configura A/B testing de modelos
- Implementa distributed training con Ray o PyTorchJob
- Configura MIG si tienes A100/H100
- Implementa FinOps con Kubecost y optimización de costos
- Añade Istio para mTLS y observabilidad avanzada
Nivel 4: Experto (6+ meses)
- Multi-cluster federation con KubeFed
- Implementa GitOps completo con Flux/ArgoCD
- Disaster recovery con Velero y backups automáticos
- Custom operators para tus workloads específicos
- Contribuye a proyectos open source (KubeFlow, KServe, GPU Operator)
Recursos Adicionales
- Documentación oficial: kubernetes.io/docs
- KubeFlow: kubeflow.org
- NVIDIA GPU Operator: docs.nvidia.com
- KServe: kserve.github.io
- Seldon Core: docs.seldon.io
Únete a la Comunidad
- CNCF Slack: Canales #kubeflow, #kserve, #sig-scheduling
- Reddit: r/kubernetes, r/LocalLLaMA, r/selfhosted
- Discord: Kubernetes Community, Ollama, LocalAI
Kubernetes para IA en 2025 es la combinación perfecta de escalabilidad, flexibilidad y ecosystem maduro. Esta guía te da todas las herramientas para pasar de Docker Compose a una infraestructura de IA de nivel empresarial.
¿Listo para empezar? Copia los YAMLs de este tutorial, adapta a tu hardware y despliega tu primer modelo en Kubernetes hoy mismo.
¿Tienes preguntas o problemas? Déjalas en los comentarios. Si esta guía te ayudó a desplegar Kubernetes para IA, compártela con tu equipo.
