Despliegue Zero-downtime de servicios Java: cómo no tumbar la producción
Imagina: viernes, 17:45, subes una nueva versión del servicio, vas por un café, vuelves — y el sitio está caído. Error 500, los clientes se ponen nerviosos, el líder técnico escribe en Slack "¿Qué pasó?". ¿Un dolor familiar? Si trabajas con Java y microservicios, sabes que detener un servicio para desplegar no es solo un contratiempo técnico, es pérdida de dinero y reputación.
La buena noticia: el despliegue zero-downtime (despliegue sin tiempo de inactividad) no es magia, sino un conjunto de prácticas probadas. En este artículo, analizaremos cómo organizar un despliegue sin interrupciones de aplicaciones Java: desde conceptos hasta código real en Spring Boot. Aprenderás a usar rolling update, blue-green y canary deployment para que tus usuarios ni siquiera noten las actualizaciones.
¿Por qué los servicios Java fallan durante el despliegue?
El problema clásico: la aplicación se inicia, pero el proceso antiguo aún procesa solicitudes. Matas el proceso antiguo — y las nuevas solicitudes caen en el vacío. O viceversa: la nueva instancia aún no está caliente, pero el tráfico ya está llegando. En Java, esto se agrava por el lento arranque de la JVM, la inicialización de Hibernate y la carga de cachés.
Principales causas de tiempo de inactividad:
- Finalización abrupta del proceso antiguo — las solicitudes se interrumpen a mitad de camino.
- Nueva instancia no caliente — la primera solicitud es lenta, la caché está vacía.
- Rotura de sesiones — el usuario pierde datos del carrito o la autorización.
- Incompatibilidad de bases de datos — las migraciones rompen la versión anterior.
El despliegue zero-downtime de Java resuelve todos estos problemas si se aborda de manera sistemática. Veamos tres patrones principales.
Patrón 1: Rolling Update (actualización continua)
Este es el enfoque más popular en el mundo de Kubernetes y las plataformas en la nube. La idea es simple: tienes varias instancias del servicio (réplicas). Las actualizas una por una: la instancia antigua se detiene, la nueva se inicia — y así sucesivamente hasta que todas las réplicas sean nuevas.
Cómo se ve en la práctica con Spring Boot y Kubernetes
Supongamos que tienes un microservicio en Spring Boot desplegado en Kubernetes. Para un rolling update, basta con una configuración correcta de Deployment y la configuración de graceful shutdown (apagado suave).
Paso 1: Configura Spring Boot para graceful shutdown:
// application.ymlserver: shutdown: graceful
spring: lifecycle: timeout-per-shutdown-phase: 30s¿Qué logra esto? Cuando Kubernetes envía la señal SIGTERM, Spring Boot deja de aceptar nuevas solicitudes, pero completa las que ya se están procesando en un plazo de 30 segundos. Sin esta configuración, la JVM simplemente mataría el proceso, interrumpiendo las solicitudes.
Paso 2: Configura readiness probe y liveness probe:
@RestControllerpublic class HealthController {
@GetMapping("/actuator/health/readiness") public ResponseEntity<String> readiness() { // Verifica que el servicio esté listo para recibir tráfico // Por ejemplo, la base de datos está conectada, la caché está caliente return ResponseEntity.ok("OK"); }
@GetMapping("/actuator/health/liveness") public ResponseEntity<String> liveness() { // Verifica que la JVM esté viva return ResponseEntity.ok("OK"); }}Paso 3: Configuración del Deployment en Kubernetes:
apiVersion: apps/v1kind: Deploymentmetadata: name: my-java-servicespec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # cuántos pods nuevos se pueden crear por encima de lo normal maxUnavailable: 0 # ningún pod debe estar no disponible template: spec: containers: - name: app image: myregistry/my-java-service:2.0 readinessProbe: