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 bootJarVerifica 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=80802) 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 entorno | Propiedad Spring | Uso típico |
|---|---|---|
SPRING_PROFILES_ACTIVE | spring.profiles.active | Seleccionar entorno (dev/stage/prod) |
SERVER_PORT | server.port | Puerto interno del contenedor |
SPRING_APPLICATION_NAME | spring.application.name | Identidad del servicio |
MANAGEMENT_SERVER_PORT | management.server.port | Separar puerto de Actuator |
JAVA_TOOL_OPTIONS | (JVM) | Opciones JVM sin tocar el CMD |
Ejemplo de ejecución local simulando contenedor:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
export SPRING_PROFILES_ACTIVE=prod export SERVER_PORT=8080 export JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75" java -jar target/app.jar3) 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=INFOSi 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..80para dejar margen a metaspace, threads y buffers.-XX:InitialRAMPercentagepara reducir latencia de warm-up si aplica.- Evita fijar
-Xmxrí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=9090Si 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.0Paso 3: validar salud (si expones Actuator)
curl -f http://localhost:8080/actuator/healthPaso 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
MaxRAMPercentageacorde 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/readinessConfiguración de puertos y red
- Puerto de aplicación definido por
SERVER_PORTy documentado en el contenedor (EXPOSE). - Si separas gestión, definir
MANAGEMENT_SERVER_PORTy 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.