Problema
Muchas plataformas internas empiezan con Docker Compose porque permite lanzar varios contenedores con una sola definición YAML y sin necesidad de un clúster completo. Cuando el número de servicios crece, la gestión de redes, puertos, salud de pods y recuperación ante fallos se vuelve cada vez más compleja. Un único nodo que ejecuta Docker Engine y un agente personalizado termina asumiendo responsabilidades propias de un orquestador: asignación de subredes, balanceo de tráfico, rollback de despliegues y persistencia de datos.
El patrón problemático surge cuando:
- Cada proyecto recibe su propia red y rango de puertos, lo que escala mal al añadir nodos.
- El agente de despliegue mantiene estado en una base de datos diseñada para Docker (IDs de contenedor, puertos asignados, etc.).
- Los despliegues fallidos dejan al equipo sin visibilidad de dónde está corriendo cada servicio.
- La introducción de aplicaciones con estado (bases de datos, caches) obliga a gestionar volúmenes y reemplazos de pods sin una capa de abstracción adecuada.
En estos escenarios, la falta de un scheduler y de un modelo declarativo provoca que la operatividad dependa de scripts ad‑hoc y de conocimiento implícito del equipo. Cuando la plataforma empieza a ser usada por equipos externos, el riesgo de interrupciones aumenta y el coste de mantenimiento del agente propio se vuelve insostenible.
Causa
-
Acoplamiento de lógica de orquestación al agente
El agente implementa funciones que Kubernetes o Docker Swarm ya ofrecen (health checks, reconciliación, asignación de IP). Al crecer, el código duplica funcionalidades y se vuelve difícil de testear. -
Persistencia de datos específicos de Docker
Guardar IDs de contenedor, puertos y redes en una base de datos crea una dependencia fuerte. Migrar a otro runtime implica transformar esos registros, lo que suele postergarse y genera deuda técnica. -
Escalado monolítico en un solo nodo
Un único VM con recursos sobrantes oculta problemas de scheduling y de alta disponibilidad. Cuando se añaden más nodos, el agente no sabe distribuir carga ni reenrutar tráfico. -
Falta de modelo declarativo
Los despliegues se disparan mediante llamadas a sockets del agente. No hay un manifiesto único que describa el estado deseado, por lo que la recuperación tras reinicios es manual y propensa a errores. -
Componentes auxiliares fuera del control del clúster
Servicios como Caddy, registry mirrors o load balancers se ejecutan como contenedores independientes y a menudo requieren permisos de nodo, rompiendo la premisa de “tenant del clúster”.
Solución
Una migración controlada a Kubernetes elimina la mayor parte de la complejidad anterior y delega la orquestación a un sistema probado. El proceso puede dividirse en fases reutilizables:
1. Definir la frontera de responsabilidad
- Base de datos → almacena intención del usuario (qué aplicación, versión, parámetros) y el historial de despliegues.
- Kubernetes → mantiene el estado de ejecución (Pods, Deployments, Services, PVCs).
- Los workers consultan el clúster para obtener la fase actual en lugar de replicar información en la BD.
2. Normalizar los manifiestos
Crear plantillas Helm o Kustomize que describan cada tipo de servicio:
Deploymentconstrategy: RollingUpdateyreadinessProbe.ServicetipoClusterIPoLoadBalancersegún necesidad.PersistentVolumeClaimconstorageClassNameadecuada.- ConfigMaps y Secrets para variables de entorno y credenciales.
Esto garantiza que cualquier cambio futuro sea versionado y auditado.
3. Migrar la lógica de despliegue
Reemplazar las llamadas al socket del agente por una librería cliente de Kubernetes (por ejemplo, client‑go o la CLI kubectl). El flujo típico pasa a:
- Generar el manifiesto a partir de la intención almacenada.
- Aplicar con
kubectl apply -f -o mediante la API. - Esperar a que el
Deploymentalcance la generación deseada (kubectl rollout status). - Actualizar la BD con el ID del recurso (
uid) y marcar el despliegue como exitoso.
4. Gestionar la red y los puertos
En lugar de crear redes Docker aisladas, usar NetworkPolicy para limitar el tráfico entre namespaces. Los puertos expuestos se manejan con Ingress o Service de tipo LoadBalancer, evitando colisiones manuales.
5. Reemplazar componentes auxiliares
- Caddy → despliegue como Ingress Controller interno o como Deployment detrás de un Ingress existente.
- Registry mirror → instalar como Deployment con
hostPortsolo si es estrictamente necesario, o usar una solución gestionada. - Eliminar cualquier script que modifique nodos; delegar esas tareas a políticas de
PodSecurityPolicyoOPA.
6. Pruebas y rollback
Kubernetes ya provee mecanismos de rollback (kubectl rollout undo). Sin embargo, para mantener la trazabilidad en la BD, registrar la versión de manifiesto aplicada y, en caso de error, volver a aplicar la versión anterior desde la base de datos.
7. Documentar y automatizar
- CI/CD que genere y aplique los charts automáticamente.
- Tests de integración que desplieguen en un clúster de prueba (k3s o kind) y verifiquen salud y rutas.
- Scripts de migración de datos que conviertan los registros Docker en referencias a recursos Kubernetes (
container_id → pod_uid).
Cuándo aplicar esta solución
Se recomienda cuando se observan al menos dos de los siguientes indicadores:
- Crecimiento de usuarios que depende de un único VM para despliegues.
- Fallos de despliegue que requieren intervención manual para reasignar puertos o redes.
- Necesidad de ejecutar aplicaciones con estado que requieren PVCs y políticas de reemplazo.
- El equipo ya gestiona más de una aplicación simultáneamente y la complejidad del agente supera la capacidad de mantenimiento.
No es necesario si la plataforma se mantiene en un entorno de desarrollo o pruebas con menos de cinco servicios y sin requisitos de alta disponibilidad. En esos casos, Docker Compose sigue siendo más rápido de configurar.
Código
# Generar manifiesto a partir de una plantilla Helm
helm template myapp ./charts/myapp \
--set image.tag=v1.2.3 \
--set replicaCount=3 > /tmp/myapp.yaml
# Aplicar al clúster
kubectl apply -f /tmp/myapp.yaml
# Esperar a que el rollout termine
kubectl rollout status deployment/myapp -n prod
# En caso de error, volver a la versión anterior
kubectl rollout undo deployment/myapp -n prod
Verificación
-
Estado del Deployment
kubectl get deployment myapp -n prod -o jsonpath='{.status.conditions[?(@.type=="Available")].status}'debe devolverTrue. -
Endpoint accesible
Ejecutarcurl -s http://<ingress-host>/myapp/healthy comprobar que responde200. -
Persistencia
Crear un archivo dentro del PVC, reiniciar el pod (kubectl delete pod <pod-name>) y validar que el archivo persiste. -
Rollback
Simular una versión fallida cambiando la etiqueta de la imagen a una que no exista y aplicar. Verificar quekubectl rollout statusmuestra error y quekubectl rollout undorestaura la versión anterior sin intervención manual.
Notas adicionales
- Gestión de recursos: habilitar
ResourceQuotayLimitRangeevita que un solo proyecto consuma todo el clúster. - Seguridad: usar ServiceAccounts con permisos mínimos y evitar que los charts soliciten privilegios de nodo.
- Monitoreo: integrar Prometheus y Grafana para observar latencias de readiness y número de rollouts fallidos.
- Migración de datos: si la BD contiene referencias a puertos, mapearlas a nombres de Service y actualizar los registros mediante un script de una sola pasada.
- Capacitación: aunque Kubernetes tiene curva de aprendizaje, comenzar con k3s o kind permite que el equipo practique sin sobrecargar la infraestructura de producción.