¿Cómo actualizar un servicio sin despertar a los usuarios?
Imagine: viernes por la noche, decide subir rápidamente un bugfix a producción. Presiona deploy — y su mensajero, cine en línea o CRM se cae durante 30 segundos. Miles de usuarios en ese momento ven un 502 Bad Gateway. ¿Y si es una pasarela de pago? Calcule las pérdidas.
El despliegue Zero-downtime (implementación con tiempo de inactividad cero) no es un lujo, sino un estándar para los servicios TypeScript modernos. En este artículo, analizaremos técnicas reales utilizadas en producción: desde graceful shutdown hasta despliegue azul-verde y rolling update. Obtendrá patrones listos para usar en TypeScript.
¿Por qué es peligroso el despliegue normal de una aplicación TypeScript?
Cuando ejecuta npm run build && node dist/index.js, ocurren varios problemas:
- Rotura de conexiones — el proceso antiguo se elimina mientras aún llegan nuevas solicitudes.
- Pérdida de datos en memoria — sesiones no guardadas, caché, colas.
- Inicio sucio — el nuevo proceso no ha tenido tiempo de calentarse y ya recibe tráfico.
En TypeScript (Node.js), todo se agrava por la asincronía: no sabe qué promesas se están ejecutando aún. Pero hay solución.
Técnica #1: Graceful Shutdown (finalización elegante)
La base del zero-downtime es el manejo correcto de las señales del SO. Cuando el orquestador (Kubernetes, PM2) decide detener su servicio, envía SIGTERM. Su código debe dejar de aceptar nuevas solicitudes, esperar a que finalicen las actuales y solo entonces terminar.
Implementación en TypeScript con Express
import express, { Request, Response } from 'express';import http from 'http';
const app = express();let server: http.Server;
app.get('/health', (req: Request, res: Response) => { res.status(200).json({ status: 'ok' });});
// Señal de Docker/K8sprocess.on('SIGTERM', async () => { console.log('SIGTERM recibido, iniciando graceful shutdown...'); // 1. Dejar de escuchar nuevas conexiones server.close(() => { console.log('Servidor HTTP cerrado'); // 2. Cerrar conexiones a BD, Redis, etc. // await db.disconnect(); process.exit(0); });
// 3. Dar tiempo a las solicitudes actuales (timeout de 30 seg) setTimeout(() => { console.error('Finalización forzada por timeout'); process.exit(1); }, 30000);});
const PORT = process.env.PORT || 3000;server = app.listen(PORT, () => { console.log(`Servicio iniciado en el puerto ${PORT}`);});Importante: en TypeScript use http.Server del módulo http, no el tipo Express. El método server.close() maneja correctamente las conexiones keep-alive.
Técnica #2: Rolling Update (actualización gradual)
Si tiene varias réplicas del servicio (3-5 instancias ejecutándose), el rolling update las actualiza una por una. Mientras una se reinicia, las otras reciben el tráfico.
Ejemplo para Kubernetes Deployment
# deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: my-typescript-servicespec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 # cuántos pods pueden no estar disponibles maxSurge: 1 # cuántos pods nuevos se pueden crear además del plan template: spec: containers: - name: app image: myregistry/service:v2 ports: - containerPort: 3000 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 3000 initialDelaySeconds: 3 periodSeconds: 5Agregue a su aplicación TypeScript un endpoint /ready que devuelva 200 solo cuando el servicio esté listo para recibir tráfico.