Despliegue de microservicios Spring Boot y ejecución en contenedores

Capítulo 10

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Empaquetado para ejecución consistente

En despliegues modernos, el mismo artefacto debe ejecutarse igual en local, CI y producción. Para microservicios Spring Boot esto se logra combinando: (1) un empaquetado reproducible (JAR), (2) configuración de runtime por variables de entorno, y (3) logging pensado para contenedores (salida estándar). El objetivo es que el contenedor sea “inmutable”: no se reconfigura editando archivos dentro, sino inyectando configuración desde fuera.

1) Construir un JAR reproducible

Usa el empaquetado estándar de Spring Boot (fat jar). Asegúrate de fijar versión de Java y de producir un artefacto estable en CI.

# Maven (ejemplo) mvn -DskipTests package # Gradle (ejemplo) ./gradlew bootJar

Verifica que el JAR arranca con un perfil por defecto y que puede recibir configuración por variables de entorno.

java -jar target/app.jar --server.port=8080

2) Runtime: variables de entorno y parámetros

En contenedores, lo más práctico es mapear variables de entorno a propiedades de Spring. Spring Boot soporta “relaxed binding”, por lo que SERVER_PORT se mapea a server.port, y SPRING_PROFILES_ACTIVE a spring.profiles.active.

Variable de entornoPropiedad SpringUso típico
SPRING_PROFILES_ACTIVEspring.profiles.activeSeleccionar entorno (dev/stage/prod)
SERVER_PORTserver.portPuerto interno del contenedor
SPRING_APPLICATION_NAMEspring.application.nameIdentidad del servicio
MANAGEMENT_SERVER_PORTmanagement.server.portSeparar puerto de Actuator
JAVA_TOOL_OPTIONS(JVM)Opciones JVM sin tocar el CMD

Ejemplo de ejecución local simulando contenedor:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

export SPRING_PROFILES_ACTIVE=prod export SERVER_PORT=8080 export JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" java -jar target/app.jar

3) Logging para contenedores

En contenedores, el patrón recomendado es escribir logs a stdout/stderr y dejar que la plataforma los recolecte. Evita rotación de archivos dentro del contenedor. Configura un formato consistente (idealmente JSON) y niveles por entorno.

Ejemplo de configuración (propiedades) orientada a consola:

logging.pattern.console=%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5level [%thread] %logger{36} - %msg%n logging.level.root=INFO

Si tu stack de observabilidad consume JSON, usa un encoder JSON (por ejemplo con Logback) y emite a consola. Mantén el contenedor sin dependencias de agentes externos salvo que tu plataforma lo exija.

Receta de contenedor (Docker) para Spring Boot

Una “receta de contenedor” define cómo construir una imagen segura, ligera y predecible. Para Java, el enfoque más común es un build multi-stage: compilas en una imagen con herramientas y ejecutas en una imagen runtime mínima.

Objetivos de la receta

  • Imagen ligera (runtime JRE, sin toolchain).
  • Usuario no root.
  • Configuración de memoria acorde a cgroups.
  • Puertos explícitos y coherentes.
  • Arranque rápido y señales correctas (PID 1).

Dockerfile multi-stage (ejemplo práctico)

# syntax=docker/dockerfile:1 ############# Stage 1: build ############# FROM maven:3.9-eclipse-temurin-21 AS build WORKDIR /workspace COPY pom.xml . COPY src ./src RUN mvn -q -DskipTests package ############# Stage 2: runtime ############# FROM eclipse-temurin:21-jre-alpine AS runtime # Crear usuario/grupo no root RUN addgroup -S app && adduser -S app -G app WORKDIR /app COPY --from=build /workspace/target/*.jar /app/app.jar # Variables por defecto (se pueden sobrescribir en despliegue) ENV SERVER_PORT=8080 ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -Djava.security.egd=file:/dev/./urandom" # Exponer puerto (documentación; el runtime real lo decide la plataforma) EXPOSE 8080 # Cambiar a usuario no root USER app # Usar exec form para señales correctas (SIGTERM) ENTRYPOINT ["java","-jar","/app/app.jar"]

Notas importantes:

  • Imagen base: elige una JRE mínima (por ejemplo Temurin JRE). Alpine reduce tamaño, pero valida compatibilidad de librerías nativas si tu app las usa.
  • Usuario no root: reduce impacto ante una vulnerabilidad.
  • JAVA_TOOL_OPTIONS: permite ajustar memoria sin modificar el comando.
  • ENTRYPOINT en formato exec: el proceso Java recibe señales y se apaga correctamente.

Configuración de memoria en contenedores

En Kubernetes/Docker, la JVM detecta límites de cgroups en versiones modernas, pero conviene fijar porcentajes para evitar OOM o infrautilización. Una configuración típica:

  • -XX:MaxRAMPercentage=70..80 para dejar margen a metaspace, threads y buffers.
  • -XX:InitialRAMPercentage para reducir latencia de warm-up si aplica.
  • Evita fijar -Xmx rígido si tu plataforma cambia límites por entorno; usa porcentajes.

Ejemplo para un contenedor con límite de 512Mi:

JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" # ~384Mi para heap (aprox.)

Puertos y separación de tráfico

Una práctica útil es separar el puerto de negocio del puerto de gestión (Actuator) para aplicar políticas distintas (por ejemplo, solo accesible internamente).

# Variables de entorno típicas SERVER_PORT=8080 MANAGEMENT_SERVER_PORT=9090

Si tu plataforma no permite dos puertos fácilmente, mantén Actuator en el mismo puerto pero restringe rutas a nivel de red/ingress.

Guía paso a paso: construir y ejecutar el contenedor

Paso 1: construir la imagen

docker build -t mi-servicio:1.0.0 .

Paso 2: ejecutar con variables de entorno

docker run --rm -p 8080:8080 -e SPRING_PROFILES_ACTIVE=prod -e SERVER_PORT=8080 -e JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" mi-servicio:1.0.0

Paso 3: validar salud (si expones Actuator)

curl -f http://localhost:8080/actuator/health

Paso 4: revisar logs

Comprueba que los logs salen por consola (sin archivos internos):

docker logs -f <container_id>

Estrategias de despliegue y actualización (conceptual)

Actualizar microservicios sin downtime requiere controlar cómo se reemplazan instancias. Las estrategias más comunes se aplican en orquestadores (Kubernetes, ECS, Nomad) o plataformas PaaS.

Rolling update

Reemplaza instancias gradualmente: se levanta una nueva versión mientras se retira la anterior. Ventajas: simple, eficiente en recursos. Riesgos: si hay cambios incompatibles (schema, contratos), puedes tener versiones mezcladas sirviendo tráfico.

  • Recomendado cuando mantienes compatibilidad hacia atrás (API y datos) durante la transición.
  • Configura readiness para que una instancia no reciba tráfico hasta estar lista.
  • Controla el ritmo: máximo de instancias no disponibles y máximo de instancias extra.

Blue/Green

Mantienes dos entornos: “blue” (actual) y “green” (nuevo). Despliegas en green, validas y luego cambias el enrutamiento (switch). Ventajas: rollback rápido (volver a blue). Coste: duplica recursos durante el cambio.

  • Útil cuando necesitas una ventana de validación realista antes de exponer al 100%.
  • Requiere estrategia de datos compatible (migraciones y backfill planificados).

Consideraciones de compatibilidad durante actualizaciones

  • Contratos: evita cambios breaking mientras conviven versiones (versionado, tolerancia a campos extra).
  • Base de datos: aplica migraciones compatibles (expand/contract) para convivir con ambas versiones.
  • Sesiones/estado: diseña servicios stateless; si hay estado, externalízalo (cache/DB) o usa sticky sessions con cuidado.

Escalado horizontal: qué cambia en tu servicio

Escalar horizontalmente significa ejecutar múltiples réplicas del mismo microservicio. Para que funcione bien:

  • Stateless: no dependas de disco local ni memoria local para estado de negocio.
  • Idempotencia: endpoints y consumidores de eventos deben tolerar reintentos.
  • Concurrencia: revisa pools de threads, conexiones y límites para evitar saturación al aumentar réplicas.
  • Arranque y warm-up: reduce tiempos de arranque; una instancia lenta afecta rolling updates y autoescalado.

En plataformas con autoescalado, define señales: CPU/memoria, latencia, cola de mensajes, RPS. Asegúrate de que el servicio se degrada de forma controlada cuando se acerca a límites (timeouts, backpressure, circuit breakers ya configurados en capítulos previos).

Checklist de preparación para producción

Límites y recursos

  • Definir requests/limits (CPU y memoria) en el orquestador.
  • Configurar JVM con MaxRAMPercentage acorde al límite de memoria.
  • Revisar tamaño de pools (HTTP client, DB pool) para no exceder recursos por réplica.
  • Evitar escribir en filesystem del contenedor; si es imprescindible, usar volúmenes y permisos mínimos.

Timeouts y comportamiento ante fallos

  • Timeouts de servidor (por ejemplo, de Tomcat/Netty) coherentes con el gateway/ingress.
  • Timeouts de clientes HTTP y de base de datos configurados explícitamente.
  • Política de reintentos controlada (sin tormentas de reintentos).
  • Graceful shutdown: permitir terminar requests en curso antes de matar el contenedor.

Health endpoints (liveness/readiness)

  • Exponer endpoints de salud para distinguir: vivo (proceso sano) vs listo (puede recibir tráfico).
  • Readiness debe fallar si dependencias críticas no están disponibles (según tu política).
  • Evitar que liveness sea demasiado estricto (podría causar reinicios en bucle).

Ejemplo de rutas típicas (Actuator):

/actuator/health /actuator/health/liveness /actuator/health/readiness

Configuración de puertos y red

  • Puerto de aplicación definido por SERVER_PORT y documentado en el contenedor (EXPOSE).
  • Si separas gestión, definir MANAGEMENT_SERVER_PORT y restringir acceso por red.
  • Configurar cabeceras/proxy (si hay ingress) para URLs correctas y logs consistentes.

Logging

  • Logs a consola (stdout/stderr), sin rotación interna.
  • Nivel de log por entorno (INFO en prod, DEBUG solo puntual).
  • Correlación (trace/span/request id) si tu plataforma lo inyecta.

Despliegue y actualización

  • Rolling: definir máximo de indisponibilidad y máximo de sobreaprovisionamiento.
  • Blue/Green: plan de switch y rollback; validaciones automatizadas post-deploy.
  • Compatibilidad de datos y contratos durante coexistencia de versiones.

Operación

  • Definir probes y tiempos: initial delay, period, timeout, failure threshold.
  • Definir política de reinicio y ventanas de mantenimiento.
  • Verificar que el servicio arranca con configuración mínima y falla rápido si falta una variable crítica.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la práctica recomendada para configurar un microservicio Spring Boot dentro de un contenedor sin modificar archivos internos del mismo?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

En contenedores se busca un artefacto inmutable: el mismo paquete se despliega igual y la configuración se proporciona desde fuera en runtime (por ejemplo, con variables de entorno que se mapean a propiedades de Spring).

Siguiente capítulo

Patrones de consistencia de datos y sagas en microservicios con Spring Boot

Arrow Right Icon
Portada de libro electrónico gratuitaMicroservicios con Spring Boot desde Cero
83%

Microservicios con Spring Boot desde Cero

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.