Llegamos al quinto capítulo de esta serie de arquitectura de microservicios, en la que vamos a ver cómo recargar en caliente la configuración de nuestros microservicios gracias a Spring Cloud Config, Spring Cloud Bus y RabbitMQ.
La idea de recarga en caliente nos permite no perder servicio al evitar tener que parar y arrancar de nuevo los servicios por un cambio de configuración.
Por este motivo se hace imprescindible seguir 2 buenas prácticas relativas a la gestión de la configuración:
- Externalización de la configuración (por ejemplo con un servidor de configuración en arquitecturas de microservicios).
- Gestión del versionado de la configuración (por ejemplo, manteniendo los archivos de configuración en un repositorio Git).
Para ilustrar esta característica nos basaremos en los componentes desarrollados en los anteriores posts de esta serie. Recordemos que todo el código puede descargarse del repositorio cygnus.
El funcionamiento básico es el siguiente:
Cuando se hace una modificación en algún fichero de configuración, el servicio config-server (Spring cloud config) lanza un evento de "refresh" a través de Spring Cloud Bus.
El/los servicios destinatarios al recibir este evento a través también de Spring Cloud Bus, pedirán su configuración al config-server y Spring recargará en su contexto aquellos beans que hayamos anotado con @RefreshScope.
Y eso es todo. Ahora vamos a ver cómo funciona:
Necesitaremos incluir cambios en los siguientes componentes de nuestra arquitectura:
Servidor de configuración
Incluiremos las siguientes dependencias en el pom de config-server:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
</dependency>
Si el config-server está configurado en modo nativo (spring.profiles.active=native
) o repositorio Git local, el evento RefreshRemoteApplicationEvent se lanzará automáticamente al modificar cualquier archivo de configuración.
En cambio, si tenemos nuestra configuración en un repositorio Git remoto será necesario invocar el endpoint /monitor del config-server para que este haga pull de la configuración y lance el evento RefreshRemoteApplicationEvent.
El modo de invocar este endpoint dependerá de nuestro repositorio Git.
Si por ejemplo mantenemos la configuración en un repositorio Git corporativo deberemos hacerlo de modo "custom" a través de un hook de Git.
Si nuestro repositorio está en Github, Gitlab o Bitbucket por ejemplo, podremos hacerlo configurando un webhook en el servidor.
En este enlace se explica de forma sencilla cómo configurar un webhook en GitHub para que haga la notificación push a un config-server local a través de ngrok.
Debemos incluir también en la configuración de nuestro config-server (fichero application.yml
) las propiedades de acceso a RabbitMQ:
spring:
application:
name: config-server
profiles:
active: ${SPRING_PROFILES_ACTIVE}
rabbitmq:
host: rabbitmq
port: 5672
username: guest
password: guest
Añadiremos a nuestro fichero hosts la entrada 127.0.0.1 rabbitmq
para resolver el host de RabbitMQ que deberá correr en nuestro host.
RabbitMQ
Spring Cloud Bus funciona a través de un message broker AMQP como sistema de comunicación broadcast entre nodos de un sistema distribuido. En nuestro ejemplo hemos elegido RabbitMQ como message broker desplegado con Docker por sencillez.
Para desplegar RabbitMQ en nuestro Docker engine, nada más sencillo: docker run --name rabbitmq -p 5672:5672 rabbitmq
Servicios
Sobre los microservicios que queramos que sean susceptibles de recargar su configuración en caliente incluiremos las siguientes modificaciones:
- Incluir la siguiente dependencia:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- Anotar con @RefreshScope los beans que queremos recargar. Vamos a hecerlo sobre nuestro servicio Deneb:
En la clase DenebConfig
hemos anotado con @RefreshScope
el bean que implementa el cliente al servicio Sadr:
@Configuration
class DenebConfig {
@Bean
@RefreshScope
SadrClient sadrClient(){
return new SadrClient();
}
}
Este bean recibe por configuración la property pingUrl
que es la url para invocar al servicio Sadr y se encuentra definida en su fichero de configuración (deneb.yml
):
cygnus:
sadr:
name: sadr-service
pingUrl: http://${cygnus.sadr.name}/sadr/ping/
De forma que podríamos cambiar en caliente el valor de esta property.
(En nuestra arquitectura, cambiar en caliente la url de un endpoint no tiene mucho sentido pues la resolución de ip y puerto se hace a través de eureka, y el resto de la url si cambia, en un servicio REST por lo general significa que el API ha cambiado y por tanto nuestro cliente fallará. Pero servirá de ejemplo para ilustrar el cambio de configuración que queremos).
Play time!
Para comprobar que todo funciona, primero arrancaremos rabbitMQ (docker run --name rabbitmq -p 5672:5672 rabbitmq
) y después el config-server (mvn clean spring-boot:run -DSPRING_PROFILES_ACTIVE=native
).
En mi caso utilizo el profile native en entorno de desarrollo por sencillez y he sobreecrito la versión de la dependencia spring-cloud-config-monitor
a 1.3.0.M1 para solventar una issue que había con este perfil.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
<version>1.3.0.M1</version>
</dependency>
A continuación arrancamos el servicio Deneb (mvn clean spring-boot:run
) y modificamos el valor de la propery cygnus.sadr.pingUrl
.
Veremos en los logs del config-server que se lanza el evento RefreshRemoteApplicationEvent:
Ahora podemos comprobar que el valor de la property ha cambiado en el contexto de Deneb, por ejemplo en el endpoint: http://localhost:2222/env
O en nuestro admin-server (recordemos el anterior post):
Y esto es todo por el momento. Si te gusta esta serie, ¡no te olvides de seguirnos en Twitter!