Zero downtime deployment: estrategias para no parar producción
El coste de “ahora no podemos desplegar”
Hay una frase que mata la velocidad de entrega de cualquier equipo de ingeniería: “esperemos al fin de semana para desplegar.” Lo que realmente dice esa frase es que desplegar es arriesgado, que el equipo no confía en el proceso, y que la ventana de mantenimiento se ha convertido en una muleta.
El coste de no poder desplegar cuando quieras es invisible pero acumulativo. Features que esperan días. Fixes urgentes que requieren ceremonias de aprobación. Equipos que agrupan cambios en releases gigantes porque desplegar es caro, lo cual hace cada release más peligrosa, lo cual refuerza la idea de que desplegar es arriesgado. Un círculo vicioso.
El despliegue sin downtime rompe ese círculo. Si desplegar es seguro, puedes desplegar con frecuencia. Si despliegas con frecuencia, cada despliegue es pequeño. Si cada despliegue es pequeño, el riesgo es bajo. Y si el riesgo es bajo, no necesitas la ventana de mantenimiento.
Las tres estrategias fundamentales
Blue-green
Blue-green es la estrategia más simple de entender y la más costosa en infraestructura. Mantienes dos entornos idénticos: blue (producción actual) y green (la nueva versión). Despliegas la nueva versión en green, la verificas, y cuando estás listo, redireccionas el tráfico de blue a green. Si algo falla, rediriges de vuelta a blue. El rollback es instantáneo.
La ventaja es la simplicidad del modelo mental. Siempre hay una versión limpia a la que volver. No hay estados intermedios ni mezcla de versiones.
El coste es que necesitas el doble de infraestructura. En cloud, esto es manejable: levantas el entorno green, despliegas, verificas, rediriges, y destruyes el entorno antiguo. Con Kubernetes, un blue-green se implementa con dos Deployments y un Service que apunta a uno u otro. Con Railway o servicios PaaS similares, es aún más simple: desplegar a un service slot alternativo y hacer el switch a nivel de DNS o load balancer.
El problema real del blue-green no es la infraestructura. Es la base de datos. Si la nueva versión requiere cambios de esquema, ambas versiones tienen que funcionar con el mismo esquema. Esto impone una disciplina de migraciones que tratamos más adelante.
Rolling
Rolling deployment actualiza las instancias de forma incremental. Si tienes 10 pods, actualiza 2, verifica, actualiza 2 más, y así sucesivamente. En todo momento hay instancias de la versión antigua y la nueva sirviendo tráfico simultáneamente.
En Kubernetes, el rolling update es el comportamiento por defecto de un Deployment. Los parámetros maxSurge y maxUnavailable controlan cuántas instancias se actualizan simultáneamente. Un maxSurge: 1 y maxUnavailable: 0 garantiza que siempre tienes al menos la capacidad total: se levanta una instancia nueva antes de matar una antigua.
La ventaja es la eficiencia de recursos. No necesitas el doble de infraestructura. El despliegue es gradual y si hay un problema, afecta a una fracción del tráfico.
La complejidad es que durante el despliegue conviven dos versiones. Si la nueva versión cambia el formato de un mensaje en una cola, la versión antigua no sabrá interpretarlo. Si la nueva versión cambia una API interna, la versión antigua seguirá llamando a la anterior. La compatibilidad hacia atrás entre versiones consecutivas es obligatoria.
El rollback es más lento que en blue-green. Hay que volver a desplegar la versión anterior de forma incremental. En Kubernetes, kubectl rollout undo automatiza esto, pero tarda lo que tarde un rolling update completo.
Canary
Canary es la estrategia más sofisticada y la que mejor equilibrio ofrece entre seguridad y velocidad. Despliegas la nueva versión a un subconjunto pequeño del tráfico (el canary: típicamente un 5-10%), monitorizas las métricas, y si todo va bien, incrementas gradualmente hasta el 100%.
La diferencia clave con rolling es que en canary tú controlas qué porcentaje de tráfico recibe la nueva versión y puedes mantener esa proporción el tiempo que necesites. En rolling, el porcentaje avanza automáticamente.
La implementación requiere un componente de routing inteligente. En Kubernetes, Istio o Linkerd proporcionan traffic splitting basado en pesos. En AWS, ALB con weighted target groups. En Cloudflare, Workers con lógica de routing. La herramienta varía, pero el concepto es el mismo: un porcentaje configurable del tráfico va al canary.
La monitorización durante el canary es crítica. Necesitas métricas que permitan comparar el canary contra la baseline en tiempo real: error rates, latencia, métricas de negocio. Si el canary muestra un error rate un 0.5% mayor que la baseline, es una señal de alarma. Herramientas como Prometheus con queries de comparación o Datadog con dashboards de canary facilitan esta comparación.
El punto más importante: automatizar la decisión de promover o revertir el canary. Si las métricas están dentro de los umbrales durante un periodo definido, el canary se promueve automáticamente. Si alguna métrica cruza un umbral, se revierte automáticamente. Argo Rollouts en Kubernetes implementa exactamente este patrón con analysis templates.
Migraciones de base de datos sin downtime
Esta es la parte donde la mayoría de equipos tropiezan. Puedes tener el mejor pipeline de despliegue del mundo, pero si tu migración de base de datos requiere un ALTER TABLE que bloquea la tabla durante 45 segundos, tienes 45 segundos de degradación de servicio.
La regla fundamental es: nunca hagas un cambio de esquema que sea incompatible con la versión actual del código. Esto se consigue con el patrón expand-contract:
Fase 1: Expand. Añade la nueva columna, tabla, o índice sin eliminar nada. El código actual sigue funcionando porque nada de lo que usa ha cambiado. La nueva columna puede tener un valor por defecto o ser nullable.
Fase 2: Migrate. Despliega la nueva versión del código que escribe en la nueva estructura además de la antigua. Si es una columna nueva, el código escribe en ambas. Si es un rename, escribe en la columna antigua y en la nueva. Los datos existentes se migran en background (backfill).
Fase 3: Contract. Una vez que la nueva versión está desplegada al 100% y los datos históricos se han migrado, elimina la estructura antigua. Esta fase ocurre en un despliegue separado, posterior.
Tres fases. Tres despliegues. Cada uno compatible hacia atrás. Ningún bloqueo.
Para ALTER TABLE en PostgreSQL, la situación ha mejorado con las versiones recientes. Añadir una columna con un valor por defecto ya no requiere un lock exclusivo desde PostgreSQL 11. Pero crear un índice en una tabla de millones de filas sí bloquea lecturas y escrituras a menos que uses CREATE INDEX CONCURRENTLY. MySQL tiene pt-online-schema-change de Percona para migraciones online que crean una tabla shadow, replican cambios, y hacen el swap atómicamente.
Herramientas como Flyway o Liquibase gestionan el versionado de migraciones. Lo que no gestionan es la lógica de expand-contract. Eso requiere disciplina del equipo.
Gestión de sesiones
Si tu aplicación almacena sesiones en memoria del servidor, un rolling deployment mata las sesiones de los usuarios cuyo servidor se recicla. Esto es inaceptable para la mayoría de aplicaciones.
La solución es externalizar las sesiones. Redis es el estándar de facto. Las sesiones se almacenan en Redis con un TTL apropiado, y cualquier instancia de la aplicación puede leerlas. Cuando un pod se destruye durante un rolling update, el usuario es redirigido a otro pod que lee su sesión de Redis sin interrupción.
Para aplicaciones que usan JWT, el problema es menor. Los tokens son autocontenidos y no dependen del estado del servidor. Pero hay que gestionar la rotación de claves de firma. Si la nueva versión usa una clave de firma diferente, los tokens emitidos por la versión anterior serán inválidos. La solución es mantener ambas claves activas durante un periodo de transición y verificar contra ambas.
Sticky sessions (afinidad de servidor) son una solución tentadora pero frágil. Funcionan hasta que el servidor al que está “pegado” el usuario se recicla. En un despliegue sin downtime, eso ocurre por definición. Las sticky sessions y el zero downtime son fundamentalmente incompatibles.
Rollback: el plan que esperas no necesitar
Todo despliegue necesita un plan de rollback. El plan no es “revertir y rezar.” Es un procedimiento documentado que responde a: qué métricas disparan el rollback, quién lo autoriza, cómo se ejecuta, y cuánto tarda.
En blue-green, el rollback es redirigir tráfico al entorno anterior. Segundos.
En rolling, el rollback es un rolling update de vuelta a la versión anterior. Minutos.
En canary, el rollback es redirigir el 100% del tráfico a la versión estable. Segundos, si el routing es configurable en tiempo real.
El caso difícil es cuando el rollback implica revertir una migración de base de datos. Si seguiste el patrón expand-contract, esto no es un problema: la versión anterior funciona con el esquema expandido. Si no lo seguiste, estás en territorio peligroso. Revertir un ALTER TABLE que eliminó una columna con datos es, en el mejor caso, una restauración desde backup. En el peor, pérdida de datos.
La regla es simple: si la migración no es reversible, el despliegue no es reversible, y necesitas más cautela (canary extendido, verificación manual, feature flags).
Feature flags como multiplicador
Los feature flags desacoplan el despliegue del código del lanzamiento de funcionalidad. Puedes desplegar código que contiene una nueva feature desactivada por defecto, verificar que el despliegue es estable, y activar la feature gradualmente.
Esto permite un patrón poderoso: desplegar primero, activar después. El despliegue es mecánico y automatizable. La activación es una decisión de producto que puede hacerse sin tocar infraestructura.
Herramientas como LaunchDarkly, Unleash o incluso un simple JSON en Redis proporcionan la infraestructura de feature flags. Lo importante es la disciplina de usarlos para cualquier cambio de comportamiento visible para el usuario y limpiarlos después (los feature flags eternos son deuda técnica).
Health checks y readiness probes
Un detalle que parece menor pero tiene impacto directo en la calidad del despliegue: los health checks deben diferenciar entre liveness y readiness.
Liveness responde a “está vivo el proceso?” Si el liveness check falla, el orquestador mata el pod y crea uno nuevo. Este check debe ser ligero: un endpoint que devuelve 200 si el proceso responde. No debe depender de servicios externos (base de datos, cache, APIs) porque un problema en la base de datos no se resuelve matando pods.
Readiness responde a “puede recibir tráfico?” Un pod puede estar vivo pero no listo: está cargando cache, esperando conexiones a la base de datos, calentando modelos. Mientras el readiness check falle, el Service de Kubernetes no envía tráfico a ese pod. Esto es crítico durante un rolling deployment: los pods nuevos no reciben tráfico hasta que están genuinamente preparados.
El error clásico es usar el mismo endpoint para ambos checks, o peor, no configurar readiness probes. El resultado es que durante un rolling update, el tráfico llega a pods que aún no están listos, generando errores 503 para un porcentaje de usuarios. Técnicamente no es downtime. Para el usuario que ve un error, sí lo es.
En la práctica, configuramos el liveness probe con un delay inicial de 10-15 segundos y un periodo de 10 segundos. El readiness probe con un delay inicial de 5 segundos y un periodo de 5 segundos. Los valores exactos dependen de la aplicación, pero la clave es que el readiness sea más frecuente y más estricto.
Smoke tests post-despliegue
Después de cada despliegue, antes de declarar éxito, un conjunto de smoke tests verifica que las funcionalidades críticas están operativas. No son tests exhaustivos. Son los 5-10 tests que cubren los flujos de mayor valor: login, búsqueda de producto, checkout, procesamiento de pedido.
Los smoke tests deben ejecutarse automáticamente como parte del pipeline. Si fallan, el despliegue se revierte automáticamente. Si pasan, el despliegue se marca como exitoso y se notifica al equipo.
Un smoke test útil es rápido (menos de 60 segundos en total), fiable (no tiene flaky tests que generen falsos positivos), y significativo (testa funcionalidad real, no endpoints de health check). Cypress, Playwright o incluso un script de curl que verifica respuestas HTTP cumplen esta función.
Poniéndolo todo junto
Un pipeline de despliegue maduro combina estos elementos:
- Tests automatizados como gate de entrada. Si los tests fallan, el despliegue no procede.
- Despliegue canary al 5% del tráfico con análisis automatizado de métricas.
- Promoción gradual (25%, 50%, 100%) con gates automáticos basados en error rate y latencia.
- Rollback automático si cualquier métrica cruza los umbrales definidos.
- Feature flags para cambios de comportamiento que requieren activación controlada.
- Migraciones de BD siempre compatibles hacia atrás usando expand-contract.
No necesitas implementar todo desde el día uno. Empieza con rolling deployment y migraciones compatibles. Añade canary cuando el volumen de tráfico justifique la inversión en observabilidad. Añade feature flags cuando la cadencia de despliegue sea diaria o mayor.
El objetivo no es la perfección técnica. Es poder desplegar un martes a las 11 de la mañana sin que nadie pierda el sueño. Cuando llegas a ese punto, la velocidad de entrega se multiplica y la calidad mejora porque los cambios son pequeños, incrementales y reversibles. Para profundizar en canary deployments y feature flags, consulta nuestro artículo sobre testing en producción. Si aún no tienes un pipeline automatizado, empieza por nuestra guía de CI/CD para equipos sin DevOps.