Kubernetes para IA: Cómo Desplegar Modelos ML en Producción (Guía 2025)

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?

  1. Fundamentos de Kubernetes aplicados a workloads de IA
  2. Instalación paso a paso (K3s, Minikube, cluster completo)
  3. Desplegar Ollama, VLLM, Stable Diffusion en Kubernetes
  4. GPU scheduling, time-slicing y optimización de recursos
  5. Herramientas del ecosistema: KubeFlow vs Seldon vs KServe
  6. Networking, Ingress, mTLS y seguridad
  7. Pipelines ML completos desde training hasta A/B testing
  8. Monitorización con Prometheus, Grafana y métricas GPU
  9. Troubleshooting: 15 problemas comunes y soluciones
  10. 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:

  1. Detecta GPUs disponibles en cada nodo
  2. Expone recursos como nvidia.com/gpu
  3. 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 Empinada Equipos grandes, workflow complejo
Seldon Core Model serving avanzado Media-Alta Moderada Producción, A/B testing, explainability
KServe Serverless ML inference Media Moderada Auto-scaling, multi-framework
Ray on K8s Distributed training/inference Alta 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 Suave Solo TensorFlow/Keras
Triton Inference Server Multi-framework serving Media 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:

  1. Data Ingestion: Descargar/procesar datos
  2. Training: Fine-tuning o training desde cero
  3. Validation: Evaluar métricas
  4. Model Registry: Versionar y almacenar modelo
  5. Deployment: Desplegar a producción
  6. A/B Testing: Comparar versiones
  7. 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 memoria
  • DCGM_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

  1. Tag todo: Labels de equipo, proyecto, entorno
  2. ResourceQuotas: Limita recursos por equipo
  3. PriorityClasses: Training en baja prioridad
  4. Scale-to-zero: Inferencia serverless con KServe
  5. Rightsizing: Ajusta requests/limits según métricas reales
  6. Spot para training: Checkpoints frecuentes
  7. GPU sharing: Time-slicing para modelos pequeños
  8. 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?

, 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:

  1. Manual: kubectl scale deployment ollama --replicas=5
  2. HPA (Horizontal Pod Autoscaler): Escala automáticamente según CPU/memoria/métricas custom
  3. 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?

. 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?

  1. Instalar NVIDIA GPU Operator (incluye DCGM Exporter)
  2. Instalar Prometheus + Grafana
  3. Importar dashboard 12239 en Grafana
  4. 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:

  1. Namespaces: Un namespace por proyecto/equipo
  2. Multi-tenancy: KubeFlow con perfiles de usuario
  3. Model router: Seldon Inference Graph enruta requests al modelo correcto
  4. 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?

  1. Velero: Backups automáticos de PVs y manifiestos
  2. Exportar a S3/MinIO: Modelos en object storage con versionado
  3. Git para manifiestos: GitOps con ArgoCD
  4. Model registry: MLFlow/DVC trackea versiones

Conclusión: Tu Roadmap para Kubernetes + IA

Nivel 1: Principiante (1-2 semanas)

  1. Instala K3s en un servidor
  2. Habilita GPU support con NVIDIA Device Plugin
  3. Despliega Ollama con el YAML de este tutorial
  4. Expone con Ingress y accede desde tu red local
  5. Instala Prometheus + Grafana para métricas básicas

Nivel 2: Intermedio (1 mes)

  1. Configura time-slicing para compartir GPU
  2. Despliega vLLM o TensorFlow Serving
  3. Implementa HorizontalPodAutoscaler
  4. Configura PersistentVolumes con NFS o MinIO
  5. Añade NetworkPolicies para seguridad
  6. Implementa MLFlow para experiment tracking

Nivel 3: Avanzado (2-3 meses)

  1. Despliega KubeFlow o Seldon Core
  2. Implementa pipeline de CI/CD con GitHub Actions + ArgoCD
  3. Configura A/B testing de modelos
  4. Implementa distributed training con Ray o PyTorchJob
  5. Configura MIG si tienes A100/H100
  6. Implementa FinOps con Kubecost y optimización de costos
  7. Añade Istio para mTLS y observabilidad avanzada

Nivel 4: Experto (6+ meses)

  1. Multi-cluster federation con KubeFed
  2. Implementa GitOps completo con Flux/ArgoCD
  3. Disaster recovery con Velero y backups automáticos
  4. Custom operators para tus workloads específicos
  5. Contribuye a proyectos open source (KubeFlow, KServe, GPU Operator)

Recursos Adicionales

Ú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.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
El Diario IA
Resumen de privacidad

Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.