Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config

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

Spring

Java

No es raro la necesidad de querer cambiar algunos valores de la configuración de una aplicación sin ningún cambio adicional en el código. Normalmente la configuración se externaliza en un archivo de texto en un determinado formato como properties o yaml que se lee al iniciarse la aplicación pero que al querer hacer cambios y hacerlos efectivos requiere reiniciar la aplicación.

Para evitar la caída de servicio en un reinicio de aplicación requiere tener varias instancias de la aplicación, ir sacando del balanceador las instancias para que no le soliciten nuevas peticiones, reiniciarlas y añadirlas de nuevo al balanceador si se hace balanceo de carga en el servidor o reiniciar las aplicaciones progresivamente si se hace en el cliente. Y esto con todas las instancias del servicio. Spring Cloud Config y Spring Boot entre sus funcionalidades de configuración posee una que consiste en recargar la configuración o ciertas partes de la misma. Para ello Spring Boot Actuator ofrece un endpoint con el que disparar la recarga.

En el siguiente ejemplo de microservicio que posee una clase de configuración con algunas propiedades. El valor de estas propiedades se utilizan para el resultado de una acción en un endpoint del servicio.

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

@Configuration
@RefreshScope
public class DefaultConfiguration {

    @Value("${config.key}")
    private String key;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}
DefaultConfiguration.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package io.github.picodotdev.blogbitix.springcloud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
public class DefaultController {

	@Autowired
	private DefaultConfiguration configuration;

	@RequestMapping("/")
	public String home(HttpServletRequest request) {
		return String.format("Hello world (%s, %s)", request.getRequestURL(), configuration.getKey());
	}
}
DefaultController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server:
  port: ${port:8080}

spring:
  profiles:
    active: cloud
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

management:
  endpoints:
    web:
      exposure:
        include: '*'

config:
  key: value
service.yml

Iniciada la aplicación que requiere iniciar previamente el servicio de registro y descubrimiento y el servidor de configuración, la aplicación al iniciarse obtiene su configuración del servidor de configuración. Si se cambia la configuración de la variable config.key la aplicación no obtendrá el valor actualizado hasta que se invoque el endpoint http://localhost:8080/actuator/refresh. Para que Spring Boot recargue la configuración es necesario anotar con @RefreshScope la clase de configuración. Invocado el endpoint de recarga de configuración la aplicación toma de nuevo los nuevos valores del servicio de configuración.

1
2
3
$ ./gradlew discoveryserver:run --args="--port=8761"
$ ./gradlew configserver:run --args="--port=8090"
$ ./gradlew service:run --args="--port=8080"
gradle-run-1.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ curl -i -X POST  http://localhost:8080/
Hello world (http://localhost:8080/, value)

$ vim configserver/misc/config/service.yml
# config.key: secret

$ curl -i -X POST  http://localhost:8080/actuator/refresh
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 22 Sep 2018 21:19:03 GMT

["config.key"]

$ curl http://localhost:8080/
Hello world (http://localhost:8080/, secret)
curl-1.sh

En una aplicación orientada microservicios es muy posible que haya múltiples instancias del mismo servicio y para recargar la configuración de cada uno de ellos hay que hacerlo de forma individual con su endpoint de recarga de configuración. Dado el número de microservicios y su ubicación distribuida hacerlo de forma individual es un inconveniente.

Para resolver este inconveniente integrando Spring Cloud Bus en las aplicaciones es posible recargar la configuración de todos los microservicios haciendo una única llamada al endpoint http://localhost:8090/monitor indicando el servicio a actualizar su configuración lo que es independiente del número de instancias y de su ubicación. Integrar Spring Clud Bus requiere disponer de una instancia de mensajes como RabbitMQ e incluir como dependencia tanto en el servidor de configuración como en el servicio la dependencia spring-cloud-starter-bus-amqp. Para esta comunicación de mensajes Spring Cloud Config crea en RabbitMQ una cola de mensajes que empieza por springCloudBus.

1
2
3
4
5
6
7
...

dependencies {
    ...
    compile('org.springframework.cloud:spring-cloud-starter-bus-amqp', excludeSpringBootStarterLogging)
    ...
}
configserver.gradle
1
2
3
4
5
6
...
dependencies {
    ...
    compile('org.springframework.cloud:spring-cloud-starter-bus-amqp:2.0.0.RELEASE') { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }    
    ...
}
service.gradle

Los pasos para probar estas funcionalidades con Spring Cloud Bus en una o varias varias instancias son iniciar una instancia o más del servidor de registro y descubrimiento, iniciar una o más instancias del servidor de configuración, iniciar una o varias instancias del servicio todas las instancias en un puerto y terminal diferente, invocar el servicio cuyo valor de respuesta depende de una propiedad de configuración, modificar el valor de la propiedad de configuración, recargar la configuración e invocar de nuevo el servicio para comprobar que el nuevo valor se ha hecho efectivo.

1
2
3
4
$ ./gradlew discoveryserver:run --args="--port=8761"
$ ./gradlew configserver:run --args="--port=8090"
$ ./gradlew service:run --args="--port=8080"
$ ./gradlew service:run --args="--port=8081"
gradle-run-2.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ curl -i -X POST  http://localhost:8080/
Hello world (http://localhost:8080/, value)
$ curl -i -X POST  http://localhost:8081/
Hello world (http://localhost:8081/, value)

$ vim configserver/misc/config/service.yml
# config.key: secret

$ curl -X POST -d 'path=service' http://localhost:8090/monitor
["service"]

$ curl http://localhost:8080/
Hello world (http://localhost:8080/, secret)

$ curl http://localhost:8081/
Hello world (http://localhost:8081/, secret)
curl-2.sh

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 el comando ./gradle-run-1.sh, ./curl-1.sh.

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. Servicio de configuración para microservicios con Spring Cloud Config
  9. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  10. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  11. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  12. Balanceo de carga y resilencia en un microservicio con Spring Cloud Netflix y Ribbon
  13. Proxy para microservicios con Spring Cloud Netflix y Zuul
  14. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  15. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  16. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  17. Trazabilidad en microservicios con Spring Cloud Sleuth
  18. Implementar tolerancia a fallos con Resilience4j
  19. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  20. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  21. Microservicios con Spring Cloud, Consul, Nomad y Traefik