Configuración de una aplicación en diferentes entornos con Spring Cloud Config

Escrito por el , actualizado el .
java planeta-codigo programacion spring
Enlace permanente Comentarios

La configuración de una aplicación suele varíar según el entorno en el que se ejecuta, la opción recomendada es que este externalizada y que el artefacto que se despliega en cada entorno sea el mismo. Con Spring Cloud Config en vez de guardar la configuración en un archivo de la propia máquina donde se instala podemos guardar de forma centralizada en un repositorio y que la aplicación obtenga la versión más actualizada cuando se inicia. En este ejemplo explicaré como crear el servidor de configuraciones con Spring Cloud Config y un ejemplo de cliente con Spring Boot que le solicita su configuración según su entorno.

Spring

Java

Desarrollar una aplicación no consiste solo en programar el código que proporciona su funcionalidad, con igual de importancia está como poner en producción esa aplicación para que preste su servicio, algo de lo que el desarrollador no debería ser ajeno. Casi siempre hay algo de configuración que varia entre entornos siendo estos al menos el de desarrollo y producción. En el ciclo de vida de una aplicación esta pasa por varios entornos de ejecución hasta llegar a producción, desde desarrollo, pruebas, QA y finalmente en producción. Casi seguro que la aplicación en cada uno de estos entornos la configuración varía, por ejemplo, las direcciones ya sean IP o nombres de dominio de las bases de datos relacional u otros servicios externos. Para que en el entorno de pruebas y QA se use exactamente el mismo artefacto (en Java un archivo war o jar) que el que se enviaría al entorno de producción la configuración de la aplicación no debería ser incluida en el propio artefacto, si la configuración fuese incluida en el propio artefacto sería distinto que el que se enviaría a producción y las pruebas no válidas, podría haber alguna diferencia en la construcción del artefacto para cada entorno.

El proyecto Spring Cloud con Spring Cloud Config proporciona un mecanismo para externalizar y actualizar de forma sencilla las varias configuraciones de una aplicación en los diferentes entornos en los que se vaya a ejecutar. La opción recomendada es crear un repositorio de Git donde se almacenarán las diferentes configuraciones de la aplicación para cada entorno y bajo un sistema de control de versiones. El que las configuraciones se obtengan de un repositorio y con Git evita que el archivo de configuración esté como un fichero regular en cada máquina del entorno de ejecución, duplicados si hay varias máquinas o con algunas diferencias en cada una. En caso de tener solo una máquina si deja de funcionar o ser accesible perderíamos el archivo de configuración y los cambios que hubiésemos hecho en él directamente, al mismo tiempo estando en un sistema de control de versiones como Git tendremos un histórico de los cambios realizados.

En el caso de este ejemplo usaré la opción del sistema de archivos en vez de Git por ser más sencilla para el ejemplo. Spring Cloud Config usa una arquitectura cliente/servidor en la que cada aplicación al iniciarse solicitará su configuración en función del entorno para el que se desee ejecutar. Spring Cloud Config no solo es usable en aplicaciones Java sino que al proporcionar una API REST y devolver documentos JSON puede ser usado por cualquier lenguaje popular (C#, Python, Ruby, Groovy, …).

Para el ejemplo me basaré en varios artículos que he escrito anteriormente como la herramienta de construcción Gradle ya que con esta herramienta construiré el proyecto, multiproyectos con Gradle dado que el ejemplo se dividirá en dos, la parte cliente y la parte servidor y aplicación autocontenida con Spring Boot como forma de iniciar tanto la aplicación cliente como servidor, también como obtener información y métricas de la aplicación con Spring Boot Actuator. Recomiendo leer estos artículos si no las conoces aún.

Para la parte servidor deberemos incluir como dependencia en el archivo build.gradle la propia del servidor de Spring Cloud Config, org.springframework.cloud:spring-cloud-config-server, y dos archivos de configuración, application.yml y bootstrap.yml donde indicaremos el puerto donde escuchará la aplicación y la ruta del sistema de ficheros del repositorio de configuraciones. En la clase que inicia el servidor con Spring Boot usaremos la anotación @EnableConfigServer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.github.picodotdev.blogbitix.springcloud.server;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootApplication
@EnableConfigServer
public class Main implements CommandLineRunner {

	@Override
	public void run(String... args) {
	}

	public static void main(String[] args) throws Exception {
		SpringApplication application = new SpringApplication(Main.class);
		application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
		SpringApplication.run(Main.class, args);
	}
}
Main-server.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
apply plugin: 'application'
apply plugin: 'spring-boot'

mainClassName = 'io.github.picodotdev.blogbitix.springcloud.server.Main'

buildscript {
    repositories {
        mavenCentral()        
    }
   
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
    }
}

dependencies {
	// Spring
	compile 'org.springframework:spring-context:4.2.3.RELEASE'	
	compile('org.springframework.boot:spring-boot-starter:1.3.0.RELEASE') { exclude(group: 'ch.qos.logback') }
	compile('org.springframework.cloud:spring-cloud-starter-config:1.0.3.RELEASE') { exclude(group: 'ch.qos.logback') }
	compile('org.springframework.cloud:spring-cloud-config-server:1.0.2.RELEASE') { exclude(group: 'ch.qos.logback') }
}
build-server.gradle
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
server:
  port: 8888

spring:
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          searchLocations: ./misc/config
application.yml
1
2
3
4
5
spring:
  application:
    name: springcloudserver
  profiles:
    active: dev
bootstrap-server.yml

En el repositorio de configuraciones cada combinación de aplicación y entorno de ejecución tendrá su propio archivo de configuración. En el caso de una aplicación de nombre springcloudclient que se ejecuta en los entornos dev, test y prod los archivos serían los siguientes. Cada archivo tiene similares propiedades de configuración pero posiblemente variando los valores de cada una de ellas. Los archivos pueden definirse en varios formatos, en este caso usando YAML.

1
2
config:
  key: dev
springcloudclient-dev.yml
1
2
config:
  key: test
springcloudclient-test.yml
1
2
config:
  key: prod
springcloudclient-prod.yml

En la búsqueda de las ubicaciones de los archivos de configuración se siguen los siguientes patrones, en el caso del ejemplo he usado la segunda opción, el primero que se encuentre es el que se usa:

  • /{application}/{profile}[/{label}]
  • /{application}-{profile}.yml
  • /{label}/{application}-{profile}.yml
  • /{application}-{profile}.properties
  • /{label}/{application}-{profile}.properties

Este sería el inicio del servidor de configuración y el documento JSON que devuelve para la aplicación springcloudclient para el entorno prod en una petición HTTP.

Configuración de la aplicación para el entorno de producción

Configuración de la aplicación para el entorno de producción

La aplicación cliente cuando se inicie solicitará su configuración al servidor Spring Cloud Config mediante una petición HTTP en función del entorno para el que se inicie. Deberemos usar la dependencia org.springframework.cloud:spring-cloud-starter-config. Para obtener los valores de las propiedades de configuración podemos usar la anotación @Value. En los archivos application.yml y bootstrap.yml indicamos el perfil para el cual se activará la aplicación y podemos especificar la URL con la localización del servidor de configuración.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.github.picodotdev.blogbitix.springcloud.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@SpringBootApplication
public class Main implements CommandLineRunner {

    @Value("${config.key}")
    String key;
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
	@Override
	public void run(String... args) {
	    System.out.printf("Valor de propiedad de configuración (%s): %s%n", "config.key", key);
	}

	public static void main(String[] args) throws Exception {
		SpringApplication application = new SpringApplication(Main.class);
		application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
		SpringApplication.run(Main.class, args);
	}
}
Main-client.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apply plugin: 'application'
apply plugin: 'spring-boot'

mainClassName = 'io.github.picodotdev.blogbitix.springcloud.client.Main'
applicationDefaultJvmArgs = ['-Dspring.profiles.active=prod']

buildscript {
    repositories {
        mavenCentral()        
    }
   
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
    }
}

repositories {
    maven {
        url 'http://repo.spring.io/milestone'
    }
}

dependencies {
	// Spring
	compile 'org.springframework:spring-context:4.2.3.RELEASE'	
	compile('org.springframework.boot:spring-boot-starter:1.3.0.RELEASE') { exclude(group: 'ch.qos.logback') }
	compile('org.springframework.cloud:spring-cloud-starter-config:1.0.3.RELEASE') { exclude(group: 'ch.qos.logback') }
}
build-client.gradle
1
2
3
4
5
6
7
8
9
spring:
  application:
    name: springcloudclient
  profiles:
    active: dev
  cloud:
    config:
      uri: http://localhost:8888
      failFast: true
bootstrap-client.yml

Si no queremos obtener las propiedades con la anotación @Value podemos usar el bean Environment que define Spring y sus métodos getProperty con el que además podremos averiguar los perfiles activos de la aplicación. Por otra parte las mismas propiedades de configuración del cliente podemos especificarlos mediante parámetros, propiedades de sistema, propiedades de entorno y algunas formas más como se explica en como externalizar la configuración en las aplicaciones Spring. De cualquiera de estas formas podemos indicar o sobrescribir los valores como puede ser el perfil activo de la aplicación.

Este ejemplo solo muestra una pequeña parte de las posibilidades que ofrece Spring Cloud Config, otras son la posibilidad de servir archivos de configuración completos para por ejemplo Nginx, los valores de las propiedades de configuración en el repositorio de configuración pueden ser cifradas y al enviarlas al cliente ser descifradas. Spring Cloud Config solo es una pequeña parte del proyecto Spring Cloud que proporciona más funcionalidades útiles para sistemas distribuidos y microservicios y un complemento adicional interesante para Spring Boot.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew cloudconfig:run, ./gradlew clouddiscovery:run, ./gradlew service:run, ./gradlew client:run


Este artículo forma parte de la serie spring-cloud:

  1. Datos de sesión externalizados con Spring Session
  2. Aplicación Java autocontenida con Spring Boot
  3. Configuración de una aplicación en diferentes entornos con Spring Cloud Config
  4. Información y métricas de la aplicación con Spring Boot Actuator
  5. Registro y descubrimiento de servicios con Spring Cloud y Consul
  6. Aplicaciones basadas en microservicios
  7. Registro y descubrimiento de servicios con Spring Cloud Netflix
  8. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  9. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  10. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  11. Balanceo de carga y resiliencia en un microservicio con Spring Cloud Netflix y Ribbon
  12. Proxy para microservicios con Spring Cloud Netflix y Zuul
  13. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  14. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  15. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  16. Trazabilidad en microservicios con Spring Cloud Sleuth
  17. Implementar tolerancia a fallos con Resilience4j
  18. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  19. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  20. Microservicios con Spring Cloud, Consul, Nomad y Traefik
  21. Trazabilidad en servicios distribuidos con Sleuth y Zipkin
  22. Configuración de una aplicación con Spring Boot y configuración centralizada con Spring Cloud Config
Comparte el artículo: