Práctica: Despliega un servicio de predicción con múltiples réplicas

Lectura
30 min~9 min lectura

Práctica: Despliega un servicio de predicción con múltiples réplicas

En esta lección práctica, pasarás de los conceptos teóricos a la implementación concreta. Aprenderás a definir, configurar y desplegar un servicio de inferencia de Machine Learning en Kubernetes, escalándolo mediante múltiples réplicas para garantizar alta disponibilidad y capacidad de respuesta ante cargas variables. Esta es la piedra angular del despliegue de modelos en producción, donde la resiliencia y la escalabilidad son no solo deseables, sino obligatorias.

Construiremos sobre la base de tener un modelo de Machine Learning ya contenerizado en una imagen Docker. Nuestro objetivo será crear los manifiestos de Kubernetes necesarios para orquestar este contenedor, exponerlo como un servicio estable a la red y asegurarnos de que múltiples instancias (pods) estén ejecutándose simultáneamente, gestionadas automáticamente por el controlador de Kubernetes. Este ejercicio simula un escenario real donde tu API de predicción debe soportar picos de tráfico sin caerse.

Concepto Clave: Réplicas, Deployments y Servicios

Para entender cómo Kubernetes maneja la escalabilidad, debemos dominar tres recursos fundamentales que trabajan en conjunto. Primero, un Pod es la unidad más pequeña y desplegable en Kubernetes, que encapsula uno o más contenedores. Sin embargo, gestionar Pods individuales es inviable en producción. Aquí es donde entra el Deployment. Un Deployment es un objeto de alto nivel que declara el estado deseado de tu aplicación: qué imagen usar, cuántas réplicas (copias idénticas) de los Pods deben ejecutarse, y cómo actualizarlas. El controlador del Deployment se encarga de que la realidad (Pods ejecutándose) coincida con tu declaración, creando o eliminando Pods según sea necesario.

El tercer pilar es el Service. Cuando tienes múltiples réplicas de un Pod, necesitas una forma estable de acceder a ellas, independientemente de qué Pod específico atienda la solicitud. Un Service actúa como un balanceador de carga interno (y a veces externo) y un punto de acceso de red permanente. Asigna un nombre de DNS estable (por ejemplo, mi-servicio-de-prediccion.namespace.svc.cluster.local) y un IP virtual, y distribuye el tráfico entrante entre todos los Pods sanos que coincidan con su selector de etiquetas. En esencia, el Deployment gestiona el "ejército" de réplicas, y el Service es el "mostrador de atención" único al que llegan todas las peticiones.

Tip: Piensa en un Deployment como el plano del arquitecto y el capataz de obra para tu aplicación. El plano (el manifiesto YAML) dice "quiero tres casas idénticas". El capataz (el controlador) se asegura de que siempre haya tres casas en pie. Si una se cae, él construye otra inmediatamente. El Service es el número de teléfono de emergencia único que la gente marca; la centralita (Service) decide a qué casa conectar la llamada, distribuyendo el trabajo equitativamente.

Cómo funciona en la práctica: El ciclo de una petición

Imaginemos que has desplegado un modelo de clasificación de imágenes con tres réplicas. El proceso comienza cuando defines tu estado deseado en un archivo YAML, aplicándolo con kubectl apply -f deployment.yaml. El controlador de la API de Kubernetes recibe esta definición y el controlador del Deployment se activa. Su misión es crear tres Pods (Réplicas). Para ello, crea un objeto secundario llamado ReplicaSet, cuyo único trabajo es contar Pods. El ReplicaSet crea los tres Pods programándolos en los nodos disponibles del clúster. Cada Pod descarga la imagen Docker especificada y ejecuta el servidor de predicción (por ejemplo, una app Flask que carga el modelo).

Paralelamente, defines un Service en otro archivo YAML y lo aplicas. El Service consulta la API para encontrar todos los Pods que tengan las etiquetas que él está configurado para seleccionar (por ejemplo, app: modelo-clasificador). A medida que los Pods se crean, el Service los detecta y los añade a su pool de endpoints. Cuando una aplicación cliente, ya sea dentro o fuera del clúster, necesita hacer una predicción, envía una petición HTTP al nombre DNS o IP del Service. El proxy de red de Kubernetes (kube-proxy) intercepta esta petición y, utilizando reglas de iptables o IPVS, la redirige de manera aleatoria o round-robin a la IP de uno de los tres Pods. Si un Pod falla, el ReplicaSet lo detecta y crea uno nuevo, y el Service automáticamente deja de enviarle tráfico, manteniendo la disponibilidad del sistema.

Código en acción: Manifiestos completos para un modelo de predicción

A continuación, presentamos un ejemplo completo y funcional para desplegar un servicio de predicción simple. Asumimos que tienes una imagen Docker llamada mi-usuario/modelo-clasificador:v1.0 que expone un endpoint /predict en el puerto 5000. El primer archivo define el Deployment con tres réplicas.

# deployment-modelo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-modelo-clasificador
  labels:
    app: modelo-clasificador
    tier: backend
spec:
  replicas: 3  # Número clave de réplicas deseadas
  selector:
    matchLabels:
      app: modelo-clasificador  # Debe coincidir con las labels de los Pods
  template:
    metadata:
      labels:
        app: modelo-clasificador  # Etiqueta crucial para que el Service encuentre estos Pods
        version: "v1.0"
    spec:
      containers:
      - name: contenedor-modelo
        image: mi-usuario/modelo-clasificador:v1.0
        ports:
        - containerPort: 5000  # Puerto que el contenedor escucha
        env:
        - name: MODELO_UMBRAL
          value: "0.75"
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 5000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 5000
          initialDelaySeconds: 5
          periodSeconds: 5

El segundo archivo define el Service que expone nuestro Deployment. Usaremos un Service de tipo ClusterIP para hacerlo accesible internamente dentro del clúster. Para pruebas locales con Minikube o si necesitas acceso externo, podrías usar NodePort o LoadBalancer.

# service-modelo.yaml
apiVersion: v1
kind: Service
metadata:
  name: servicio-modelo-clasificador
spec:
  type: ClusterIP  # Tipo de servicio. ClusterIP crea una IP interna.
  selector:
    app: modelo-clasificador  # ¡DEBE COINCIDIR con las labels del Pod en el Deployment!
  ports:
  - protocol: TCP
    port: 80       # Puerto en el que el Service escuchará INTERNAMENTE
    targetPort: 5000  # Puerto del contenedor al que redirige el tráfico

Para desplegar estos recursos, ejecuta los siguientes comandos en tu terminal, asumiendo que tienes configurado un contexto de Kubernetes (como Minikube o un clúster en la nube).

# Aplicar el Deployment
kubectl apply -f deployment-modelo.yaml
# Aplicar el Service
kubectl apply -f service-modelo.yaml
# Verificar el estado del Deployment
kubectl get deployments
# Verificar los Pods creados (deberías ver 3)
kubectl get pods -l app=modelo-clasificador
# Verificar el Service y su IP interna (CLUSTER-IP)
kubectl get service servicio-modelo-clasificador

Una vez desplegado, puedes probar el servicio desde dentro del clúster. Una forma rápida es crear un Pod temporal con curl y hacer una petición al nombre del servicio.

kubectl run curl-test --image=radial/busyboxplus:curl -i --tty --rm
# Una vez dentro del shell del Pod temporal:
curl http://servicio-modelo-clasificador/predict -X POST -H "Content-Type: application/json" -d '{"data": "tus_datos_aqui"}'

Errores comunes y cómo evitarlos

Al comenzar con Kubernetes, es fácil tropezar con ciertos errores que pueden detener tu despliegue. Aquí te presentamos los más frecuentes y cómo solucionarlos.

1. Selector de etiquetas (Label Selector) no coincide: Este es el error más común. Las etiquetas definidas en spec.selector.matchLabels del Deployment y en spec.template.metadata.labels deben coincidir exactamente. Además, el selector del Service (spec.selector) debe coincidir con estas mismas etiquetas. Si no coinciden, el Service no encontrará ningún Pod y no se enrutará tráfico. Solución: Verifica meticulosamente la ortografía y los pares clave-valor en tus archivos YAML. Usa kubectl get pods --show-labels para ver las etiquetas reales de tus Pods.

2. Falta de recursos (CPU/Memoria) en los nodos: Especialmente en entornos locales como Minikube, puedes solicitar más recursos de los disponibles. Si los resources.requests de tu contenedor exceden la capacidad del nodo, el Pod quedará en estado Pending. Solución: Usa kubectl describe pod <nombre-pod> para ver eventos que indiquen "Insufficient cpu/memory". Ajusta tus requests y limits a valores realistas o añade recursos a tu clúster.

3. Puertos incorrectos o no expuestos: El containerPort en el Deployment es principalmente informativo. El error real viene cuando el targetPort en el Service no coincide con el puerto en el que tu aplicación escucha realmente dentro del contenedor. Solución: Asegúrate de que tu aplicación dentro del contenedor Docker escucha en el puerto que especificas (por ejemplo, 5000) y que el Service apunta a ese mismo puerto con targetPort.

4. Imagen de contenedor no encontrada o problemas de pull: Si usas una imagen privada sin configurar correctamente los imagePullSecrets, o si el nombre/tag de la imagen es incorrecto, el Pod fallará. Solución: Revisa los eventos del Pod con kubectl describe pod. Busca errores como "ErrImagePull" o "ImagePullBackOff". Verifica el nombre de la imagen y, si es privada, configura un secret para Docker Registry.

5. Sondas (Probes) mal configuradas: Un error sutil pero crítico. Si tu contenedor tarda más de lo esperado en iniciarse, pero la initialDelaySeconds de tu livenessProbe es muy corta, Kubernetes matará el contenedor pensando que está en un bucle de error, creando un ciclo de reinicios infinito. Solución: Ajusta los tiempos de las sondas según el tiempo real de arranque de tu aplicación. Usa kubectl logs <nombre-pod> para depurar el inicio de tu aplicación.

Checklist de dominio

Antes de considerar esta lección completamente dominada, asegúrate de poder verificar cada uno de los siguientes puntos de forma práctica.

  • Puedo escribir un manifiesto de Deployment YAML desde cero que especifique una imagen de contenedor, el número de réplicas, y límites de recursos.
  • Comprendo la diferencia entre port, targetPort, y nodePort en la definición de un Service y puedo configurarlos correctamente.
  • Sé cómo escalar manualmente un Deployment usando kubectl scale deployment/<nombre> --replicas=5 y entiendo el impacto inmediato en los Pods.
  • Puedo diagnosticar un problema donde un Service no está enrutando tráfico, verificando selectores de etiquetas, endpoints (kubectl get endpoints), y el estado de los Pods.
  • Sé cómo exponer mi servicio de predicción fuera del clúster, ya sea cambiando el tipo de Service a NodePort o LoadBalancer, o usando un Ingress Controller.
  • Puedo definir y configurar correctamente livenessProbe y readinessProbe para una aplicación web de ML, entendiendo su propósito distinto.
  • Entiendo cómo Kubernetes distribuye el tráfico entre las réplicas y por qué la aplicación debe ser stateless o manejar el estado externamente (por ejemplo, en una base de datos) para que este esquema funcione.
  • Puedo realizar una actualización rodante (rolling update) de mi modelo cambiando la versión de la imagen en el Deployment y observando cómo Kubernetes reemplaza gradualmente los Pods antiguos por los nuevos.
De lección a portfolio

Convertí esta lección en una habilidad visible para entrevistas.

Guardá el curso, completá los ejercicios y conectá esta habilidad con una ruta de empleo, data, IA, programación o marketing.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión