7 · Contenerizar la API
Ahora envuelve el servicio de FastAPI en una imagen de Docker: ejecútalo con uvicorn, expón el puerto, lee la ruta del modelo de una variable de entorno, y añade un endpoint `/healthz` para que la plataforma sepa cuándo el contenedor está realmente listo para servir.
Conteneriza el servicio de FastAPI ejecutándolo con `uvicorn` como el `CMD` del contenedor, haciendo `EXPOSE` de su puerto, y leyendo la ruta del modelo de una variable de entorno (ENV). Añade un endpoint `/healthz` para que el orquestador pueda distinguir LIVENESS (¿está el proceso vivo?) de READINESS (¿está el modelo cargado y capaz de servir?) — y enrutar tráfico solo cuando esté listo.
Sin esto:
Sin un endpoint de health/readiness, la plataforma envía tráfico real de usuarios a un contenedor en el instante en que el proceso arranca — antes de que el modelo termine de cargar — así que las primeras peticiones fallan; y sin configuración por variables de entorno la misma imagen no puede reutilizarse entre entornos.
Tienes una app de FastAPI (lección 6) y sabes escribir un Dockerfile (lecciones 3–5). Ahora combínalos: empaqueta la API como una imagen de contenedor para que pueda desplegarse en cualquier lugar.
Ejecútala con uvicorn. Una app de FastAPI se sirve mediante un servidor ASGI — uvicorn. Así que el CMD del contenedor lanza uvicorn apuntando a tu objeto de app: uvicorn app:app --host 0.0.0.0 --port 80. El --host 0.0.0.0 es esencial — le dice a uvicorn que escuche en todas las interfaces, no solo en localhost; sin él, las peticiones de fuera del contenedor no pueden alcanzarlo. EXPOSE 80 documenta en qué puerto sirve el contenedor.
Configuración por variables de entorno. Recuerda que la app de FastAPI leía MODEL_PATH de una variable de entorno. Eso rinde frutos aquí: la misma imagen puede apuntar a archivos de modelo distintos en dev, staging y prod con solo fijar MODEL_PATH de forma distinta en tiempo de ejecución. La configuración vive fuera de la imagen; solo el código + las dependencias quedan horneados.
Health checks — liveness vs readiness. Esta es la idea nueva clave. Cuando un orquestador (Kubernetes, un balanceador de carga, una plataforma en la nube) corre tu contenedor, necesita responder dos preguntas distintas:
- Liveness (vida) — ¿está vivo el proceso? Si el proceso se colgó o se cayó, reinicia el contenedor. Esto solo verifica que el servidor responde en absoluto.
- Readiness (listo) — ¿está este contenedor listo para recibir tráfico real? Una API de modelo tiene un arranque lento: el proceso está vivo en milisegundos, pero cargar el modelo
.joblibtarda más. Durante esa ventana el proceso está vivo pero no listo — si llega tráfico ahora, las peticiones fallan porque el modelo aún no está cargado. Readiness dice "no me enrutes peticiones hasta que yo señale que estoy listo".
Expones un endpoint ligero /healthz que la plataforma consulta. Un check de readiness robusto devuelve 200 OK solo una vez que el modelo está realmente cargado (p. ej. model is not None); antes de eso devuelve un estado de no-listo, y el orquestador retiene el tráfico. Por esto las primeras peticiones a un contenedor de modelo recién arrancado y sin instrumentar tan a menudo fallan — y por esto un endpoint de readiness lo arregla.
Un Dockerfile para el servicio de FastAPI: instalar dependencias, `MODEL_PATH` por variable de entorno, `EXPOSE 80`, y un `CMD` de uvicorn que enlaza a `0.0.0.0`.
Construye y corre el contenedor de la API, luego haz `curl` al endpoint de readiness `/healthz` y al endpoint `/predict` para verificar que sirve una vez que el modelo está cargado.
¿Cuál es la diferencia entre un check de liveness y un check de readiness para un contenedor de modelo?
- Conteneriza el servicio de FastAPI ejecutándolo con `uvicorn app:app --host 0.0.0.0 --port 80` como el `CMD`, con `EXPOSE 80` y un `MODEL_PATH` por variable de entorno.
- `--host 0.0.0.0` es requerido para que las peticiones de fuera del contenedor alcancen a uvicorn; enlazar a localhost solo aceptaría conexiones de dentro.
- Una sonda de liveness ('¿está arriba el proceso?' → reiniciar si no) es distinta de una sonda de readiness ('¿está cargado el modelo?' → retener tráfico hasta que sí); `/healthz` debe condicionar la readiness a `model is not None`.
Todo contenedor de modelo desplegado en la nube se envía con un entrypoint de uvicorn y una sonda de readiness `/healthz` — es como Kubernetes, los balanceadores de carga y las plataformas gestionadas saben cuándo empezar a enrutar tráfico en vivo a un modelo.
Si lo quitas: La plataforma enrutaría tráfico a un contenedor antes de que su modelo termine de cargar, así que las primeras peticiones fallan — y un modelo de carga lenta podría disparar un bucle de reinicio sin fin si liveness y readiness no se separan.