Esta es la novena entrega de nuestra serie sobre patrones de arquitectura de microservicios. En capítulos previos, hemos abordado aspectos como la comunicación sincrónica y asíncrona, la orquestación de procesos y la resiliencia ante fallos. Si quieres un panorama aún más amplio de cómo diseñar, desplegar y operar microservicios de manera profesional, te invitamos a revisar los posts anteriores:
- Patrones de arquitectura de microservicios, ¿qué son y qué ventajas nos ofrecen?
- Patrones de arquitectura: organización y estructura de microservicios
- Patrones de arquitectura: comunicación y coordinación de microservicios
- Patrones de arquitectura de microservicios: SAGA, API Gateway y Service Discovery
- Patrones de arquitectura de microservicios: Event Sourcing y arquitectura orientada a eventos (EDA)
- Patrones de arquitectura de microservicios: comunicación y coordinación con CQRS, BFF y Outbox
- Patrones de microservicios: escalabilidad y gestión de recursos con escalado automático
- Patrones de arquitectura: de monolitos a microservicios
En el ecosistema moderno de TI, los microservicios se han convertido en una de las estrategias predilectas para diseñar y escalar aplicaciones complejas.
En este artículo abordaremos en detalle un patrón arquitectónico clave, se trata del patrón de configuración externalizada (Externalized Configuration) y veremos:
- Cómo aislar y proteger la información sensible, eliminando la necesidad de incrustarla en el propio código.
- Métodos para externalizar y gestionar la configuración (variables de entorno, ficheros externos, servidores de configuración).
- Ejemplos detallados basados en un eCommerce con Spring Boot, Docker y Kubernetes.
El contexto de eCommerce resulta idóneo para ilustrar este patrón, por eso lo utilizaremos como en el resto de posts de la serie, pues suele implicar múltiples dominios y servicios especializados: un catálogo de productos (catalog), un módulo de pedidos (orders), un servicio de pagos (payment), un servicio de logística (shipping), entre otros. Cada uno con sus propias dependencias, configuraciones y diferentes APIs expuestas que consumen el resto de módulos.
Antes de entrar en materia, conviene reforzar por qué este patrón es tan necesario en una arquitectura de microservicios:
- Cuando se tienen multitud de microservicios, cada uno puede requerir endpoints de otros servicios, credenciales de bases de datos independientes, “features flags” específicos o parámetros de rendimiento.
- Incluir esta configuración “hardcodeada” en el repositorio de cada servicio conlleva riesgos de seguridad (exposición de claves) y dificultades de mantenimiento (cada cambio obliga a volver a desplegar).
- Además, la necesidad de trabajar con entornos distintos (dev, test, preproducción, producción) hace que la configuración se multiplique exponencialmente.
Con estos antecedentes en mente, pasemos directamente a desgranar el patrón y su implementación con especial foco en ejemplos técnicos.
*El almacenamiento no tiene por qué ser en el cloud.
La configuración externalizada se basa en la idea de que toda la información variable o sensible de un servicio debe residir fuera del propio binario. Esto implica que credenciales de bases de datos, tokens de pasarelas de pago, URLs de dependencias o parámetros de negocio no deben “incrustarse” en el código fuente o en los ficheros de configuración internos del proyecto, sino que deben ser proporcionados en tiempo de despliegue o, idealmente, gestionados por un servicio aparte.
- Seguridad y gobernanza
- Reducimos la probabilidad de que contraseñas o API keys terminen en repositorios públicos o logs de build. Por lo que claves secretas no se incluyen accidentalmente en commits.
- Es más sencillo aplicar permisos de acceso a la configuración (quién puede modificarla) y auditar los cambios.
- Flexibilidad de despliegue
- Una misma imagen Docker (o un mismo paquete JAR) puede promocionarse por el pipeline de dev → test → pre → pro, inyectando únicamente la configuración apropiada a cada entorno.
- Permite cambiar parámetros sin recompilar el servicio, ahorrando tiempo y riesgos de errores. (por ejemplo, la URL de un proveedor de pagos)
- Mantenibilidad y escalabilidad
- Cuando hay decenas o cientos de microservicios, un repositorio único de configuración (o un servicio especializado) ayuda a orquestar y actualizar valores comunes (por ejemplo, la URL de un servicio compartido).
A grandes rasgos, existen tres aproximaciones habituales: variables de entorno, ficheros externos y servicios de configuración centralizada. Veamos cada una con mayor profundidad.
- Variables de entorno
- En entornos de contenedores (Docker, Kubernetes) se ha convertido en la forma más básica y alineada con los 12-Factor App para externalizar configuración.
- Muy típico en entornos contenerizados (Docker, Kubernetes).
- Cada microservicio obtiene sus valores en tiempo de ejecución
- Fácil de manejar con orquestadores (Kubernetes ConfigMaps y Secrets), aunque puede no escalar bien si hay muchas propiedades.
Docker: al arrancar un contenedor con docker run, podemos definir:
docker run -d \
-e DB_USER=admin \
-e DB_PASS=secret123 \
-e SPRING_PROFILES_ACTIVE=prod \
--name orders-service \
myrepo/orders-service:latest
Aquí, DB_USER y DB_PASS se leen dentro del servicio con System.getenv(), o mediante las propiedades en Spring Boot (spring.datasource.username=${DB_USER}, etc.)
Kubernetes: se usa un ConfigMap para variables genéricas y un Secret para credenciales o valores sensibles.
Un ejemplo de Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-service
spec:
replicas: 2
selector:
matchLabels:
app: orders-service
template:
metadata:
labels:
app: orders-service
spec:
containers:
- name: orders-service
image: myrepo/orders-service:latest
env:
- name: DB_USER
valueFrom:
configMapKeyRef:
name: orders-config
key: db-user
- name: DB_PASS
valueFrom:
secretKeyRef:
name: orders-secrets
key: db-pass
De este modo, cada pod hereda la configuración adecuada.
Ventajas:
- Simples de usar y gestionar en contenedores.
- Cumplen muy bien con el principio 12-Factor.
Limitaciones:
- El manejo se vuelve engorroso cuando hay muchas propiedades.
- En contenedores, no hay un mecanismo de recarga dinámica nativo (habría que hacer “rolling updates” si cambiamos variables de entorno).
Ficheros externos versionados
Otra opción consiste en almacenar los parámetros en ficheros de configuración (YAML, JSON, Properties) ubicados fuera de la carpeta principal del servicio, pero accesibles en tiempo de ejecución.
Por ejemplo, en application-prod.yml podríamos tener:
payment:
provider: "Stripe"
api-key: "sk_live_XYZ123"
catalog:
url: "http://catalog-service:8080"
Estos ficheros se pueden montar como volumen en contenedores Docker, descargar en tiempo de despliegue desde un repositorio Git independiente (GitOps) e integrar con frameworks (Spring Boot permite importar ficheros adicionales con spring.config.additional-location).
Ventajas:
- Facilidad de uso y lectura, con soporte de “merge” y perfiles de Spring (por ejemplo: application-dev.yml, application-prod.yml).
- Versionado sencillo en Git, permitiendo revertir cambios si algo falla.
Inconvenientes:
- Se requiere un proceso manual o automatizado para distribuir estos ficheros a cada nodo.
- No siempre es trivial manejar secretos (hay que cifrarlos o restringir su acceso).
Servicios de configuración centralizada
Para grandes ecosistemas de microservicios, un servidor de configuración se convierte en una pieza fundamental. Algunas herramientas populares:
- Spring Cloud Config:
- Funciona como un puente entre los microservicios y un repositorio Git (u otro backend).
- El microservicio arranca y contacta al Config Server, que le entrega el conjunto de propiedades específico para su nombre y perfil (application-name, dev/prod, etc.).
- Permite recarga dinámica (si usas Actuator y el bus de Spring Cloud) y versionado completo en Git.
- HashiCorp Vault:
- Focalizado en la gestión de secretos (claves, contraseñas, tokens).
- Ofrece cifrado de datos, políticas de acceso (ACL), generación dinámica de credenciales en algunos casos (por ejemplo, credenciales temporales para BBDD).
- Se integra con apps Java, Kubernetes, etc.
- Consul:
- Además de la parte de key/value store para configuración, ofrece capacidades de service discovery.
- Se puede combinar con Vault para almacenar valores cifrados.
Ventajas:
- Centralización y escalabilidad: cada microservicio no necesita “saber” dónde está su config, solo el endpoint del servicio de configuración.
- Auditoría y trazabilidad: los cambios quedan registrados y se pueden gestionar con roles.
- Actualizaciones dinámicas (hot reload) sin redespliegue (especialmente en Spring Cloud Config).
Desventajas:
- Añade complejidad y requiere mantenimiento de ese servidor.
- Se debe tener un plan de alta disponibilidad para no generar un punto único de fallo.
Imagina que nuestra plataforma eCommerce se compone de cuatro microservicios:
- catalog-service: expone el catálogo de productos (nombre, stock, precio).
- orders-service: gestiona pedidos, calculando totales y coordinando.
- payment-service: integra pasarelas de pago (Stripe, PayPal).
- shipping-service: calcula costes de envío y gestiona la logística.
- Config Server:
- Es una aplicación Spring Boot que incluye la dependencia spring-cloud-config-server.
- En su application.yml, definimos dónde está el repo Git de configuraciones:
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/config-repo
default-label: main
- Activamos el servidor con la anotación @EnableConfigServer.
- Repositorio Git “config-repo”:
- Contiene ficheros como catalog-service.yml, orders-service.yml, etc.
- Para entornos, podríamos tener catalog-service-dev.yml y catalog-service-prod.yml.
- Supongamos catalog-service-prod.yml:
catalog:
db:
url: "jdbc:postgresql://prod-db-host/catalog"
user: "catalog_user"
pass: "SuperSecretPass"
cache-ttl: 300
- catalog-service (microservicio en Spring Boot)
- En su bootstrap.yml (se carga antes que application.yml), indicamos:
spring:
application:
name: catalog-service
cloud:
config:
uri: http://config-server:8888
profile: prod
label: main
- Al arrancar, catalog-service hará una petición GET /catalog-service-prod.yml al Config Server, obteniendo las propiedades definidas en Git.
- Despliegue en Kubernetes:
- Definimos un Deployment para catalog-service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-service
spec:
replicas: 2
selector:
matchLabels:
app: catalog-service
template:
metadata:
labels:
app: catalog-service
spec:
containers:
- name: catalog-service
image: myrepo/catalog-service:latest
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: SPRING_CLOUD_CONFIG_URI
value: "http://config-server:8888"
- Con esto, cada pod de catalog-service sabrá conectarse al Config Server, recibiendo la configuración actualizada de catalog-service-prod.yml.
- Cambio en la configuración:
- Si queremos subir el TTL de la caché a 600 en producción, editamos catalog-service-prod.yml en Git y hacemos commit.
- Podemos notificar al servicio para recargar la configuración con curl -X POST http://catalog-service:8080/actuator/refresh (suponiendo que usamos Spring Cloud Bus o un endpoint de Actuator).
- Sin recompilar ni redesplegar, catalog-service adopta el nuevo valor catalog.cache-ttl=600.
Este flujo se repite para orders-service, payment-service y así sucesivamente, manteniendo la configuración en un lugar central y versionado. A medida que la plataforma crece, esta práctica se vuelve fundamental para la gobernanza y la seguridad.
- Menor riesgo de filtraciones: al no almacenar claves en repositorios de código, reduces enormemente las posibilidades de exponer datos sensibles.
- Despliegues multi-entorno: un único binario de orders-service puede funcionar en dev, test o prod sin recompilaciones adicionales.
- Trazabilidad de cambios: con repositorios Git o servicios de configuración, siempre hay un historial de quién modificó qué propiedad y cuándo.
- Hot Reload y escalado: especialmente con Spring Cloud Config, es factible actualizar la configuración al vuelo y escalar los pods sin que cada uno tenga un set distinto de propiedades.
- Adoptar un enfoque “Secure by Design”. Para la configuración externalizada, protege especialmente los datos sensibles con Vault, Secrets de Kubernetes o cifrado robusto.
- Versionar las APIs. CDCT no elimina la necesidad de versionar endpoints si se introducen cambios radicales. Puedes, por ejemplo, mantener /v1/pay y /v2/pay en payment-service, permitiendo que los consumidores migren progresivamente.
- Mantener la comunicación entre equipos. El éxito de CDCT depende de la colaboración; no es un sustituto de la comunicación, sino un catalizador para discutir contratos de manera objetiva.
- Automatizar el pipeline. Tanto la recopilación de configuraciones como la verificación de contratos deben ejecutarse en cada proceso de compilación, no solo antes de una release.
Las arquitecturas de microservicios brindan escalabilidad y flexibilidad, pero exigen prácticas de gestión y verificación sólidas para mantener la coherencia en un entorno distribuido. Para elaborar una solución más efectiva a los problemas habituales sería:
- Separar parámetros y secretos del código, aportando seguridad, control y facilidad de actualización.
- Desde variables de entorno hasta servicios centralizados como Spring Cloud Config o HashiCorp Vault, la clave es elegir la solución que mejor encaje con el tamaño y las necesidades de la organización.
- Si manejas pocos microservicios y apenas necesitas una o dos credenciales, inicia con variables de entorno o ficheros YAML. A medida que escale la plataforma, contempla un Config Server o un Vault.
- Combinación con otras prácticas: un microservicio robusto también requiere un buen diseño de logs, trazas distribuidas (observabilidad) y mecanismos de resiliencia (circuit breakers, timeouts). Externalized Configuration no opera en aislamiento, sino que se suma a la estrategia global de DevOps/Cloud Native.
- Con la configuración externalizada, aseguramos que cada microservicio consuma los parámetros que necesita de forma segura y fácil de gestionar, independientemente del entorno.
En la siguiente entrega veremos Consumer-Driven Contract Testing (CDCT) y, si tienes algún comentario, te leemos 👇.
Cuéntanos qué te parece.