= Ejemplo de integración de Consul y Spring Boot
image:https://travis-ci.org/labcabrera/sample-spring-consul.svg?branch=master["Build Status", link="https://travis-ci.org/labcabrera/sample-spring-consul"]
== Introducción
En este ejemplo se muestran las capacidades de https://cloud.spring.io/spring-cloud-consul/[Spring Cloud Consul]
tanto para el descubrimiento de servicios como para el uso de una
https://cloud.spring.io/spring-cloud-config/[configuración distribuída].
En este ejemplo utilizaremos una instancia local de https://www.consul.io/[Consul] a través de la
https://hub.docker.com/_/consul/[imagen de DockerHub].
Nuestra aplicación será muy sencilla, simplemente utilizaremos las anotaciones
[source,java]
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
public class ConsulSampleApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulSampleApplication.class, args);
}
}
== Docker
En primer lugar nos descargaremos la última versión de consul y levantaremos el contenedor con el
siguiente comando:
docker run -p 8500:8500 -p 8600:8600/udp --name=consul consul:latest agent -server -bootstrap -ui -client=0.0.0.0
Una vez arrancado podremos entrar en la consola web: http://localhost:8500/ui
También podremos comprobar el estado haciendo:
En este caso necesitaremos conocer la IP con la que el contenedor resolverá el servicio que
queremos descubrir. Para ello simplemente ejecutaremos el comando
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "31de39115c56a7129ad297ff038fbd08ff9bf731f035f53c24f29c9578f89c04",
"Created": "2018-05-17T08:38:32.147242687+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"18478727e1166e5ead3e5d3f14a3dec50f22fe64a59be683184f7b89d862dd5b": {
"Name": "consul",
"EndpointID": "732cd731fb163c1f86e1457e5524ef1f7ce52eab96a79d0b458975dc97128c27",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
Y vemos que resuelve nuestra IP como 172.17.0.1.
Este será el valor en nuestro ejemplo que tendremos que indicar en la configuración de service-discovery:
[source,yml]
spring:
cloud:
consul:
discovery:
enabled: true
register: true
prefer-ip-address: true
ip-address: 172.17.0.1
instanceId: ${spring.application.name}:${random.value}
Podemos comprobar que resuelve correctamente nuestra ip ejecutando:
docker exec -it ${containerId} ping 172.17.0.1 -c 1
== Probando la configuración
Para consultar la configuración vamos a incluir un servicio REST sencillo que simplemente devolverá
los valores recuperados:
[source,java]
@EnableAutoConfiguration
@EnableDiscoveryClient
@SpringBootApplication
public class ConsulSampleApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulSampleApplication.class, args);
}
}
Después desde la consola podremos de forma sencilla crear las siguientes claves de configuración:
image::https://raw.githubusercontent.com/labcabrera/sample-spring-consul/master/docs/images/consul-console-add-key-value.png[Adding custom configuration property]
Donde el formato de la clave será /config/${appName}/keyValue
.
Para comprobar que nuestra aplicación resuelve correctamente ese valor podemos hacer la siguiente
petición:
Al tener las dependencias de Spring Actuator también podremos inspeccionar nuestra configuración
accediendo a http://localhost:8080/env, donde veremos la sección de configuración
consul:config/consul-sample/.
Que devolverá el valor que hemos introducido anteriormente.
Vemos que si desde la consola de administración cambiamos el valor nuestra aplicación
automáticamente actualiza dicho valor:
2018-05-17 11:20:23.252 INFO 27664 --- [ask-scheduler-3] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1176c553: startup date [Thu May 17 11:20:23 CEST 2018]; root of context hierarchy
2018-05-17 11:20:23.283 INFO 27664 --- [ask-scheduler-3] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-05-17 11:20:23.295 INFO 27664 --- [ask-scheduler-3] trationDelegate$BeanPostProcessorChecker : Bean 'configurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$e02d6bc] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-05-17 11:20:23.507 INFO 27664 --- [ask-scheduler-3] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='consul', propertySources=[ConsulPropertySource {name='config/consul-sample/'}, ConsulPropertySource {name='config/application/'}]]
2018-05-17 11:20:23.509 INFO 27664 --- [ask-scheduler-3] o.s.boot.SpringApplication : No active profile set, falling back to default profiles: default
2018-05-17 11:20:23.511 INFO 27664 --- [ask-scheduler-3] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@720b5947: startup date [Thu May 17 11:20:23 CEST 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@1176c553
2018-05-17 11:20:23.517 INFO 27664 --- [ask-scheduler-3] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-05-17 11:20:23.538 INFO 27664 --- [ask-scheduler-3] o.s.boot.SpringApplication : Started application in 0.5 seconds (JVM running for 131.782)
2018-05-17 11:20:23.539 INFO 27664 --- [ask-scheduler-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@720b5947: startup date [Thu May 17 11:20:23 CEST 2018]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext@1176c553
2018-05-17 11:20:23.540 INFO 27664 --- [ask-scheduler-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1176c553: startup date [Thu May 17 11:20:23 CEST 2018]; root of context hierarchy
2018-05-17 11:20:23.734 INFO 27664 --- [ask-scheduler-3] o.s.c.e.event.RefreshEventListener : Refresh keys changed: [foo]
También podremos consultar el valor directamente desde la API REST de Consul a través de la
siguiente petición:
$ curl http://localhost:8500/v1/kv/config/consul-sample/foo
[{"LockIndex":0,"Key":"config/consul-sample/foo","Flags":0,"Value":"Zm9vVmFsdWVVcGRhdGVk","CreateIndex":120,"ModifyIndex":142}]l
Donde obtendremos el valor codificado en Base64.
== Service discovery
Para utilizar el sistema de registro de servicios primero declararemos el siguiente bean:
[source,java]
@LoadBalanced
@Bean
public RestTemplate loadbalancedRestTemplate() {
return new RestTemplate();
}
Después simplemente crearemos un cliente que obtenga el endpoint desde Consul:
[source,java]
@RestController
public class ApiConsumerController {
@Value("http://${spring.application.name}/api/v1/dummy")
private String endpoint;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/api/v1/consumer")
public DummyMessage consume() {
return restTemplate.getForObject(endpoint, DummyMessage.class);
}
}
Siendo consul-sample nuestro identificador de servicio con el que nos hemos registrado en Consul
(este valor será el spring.application.name).
Para probar que funciona simplemente haremos la siguiente petición
$ curl http://localhost:8080/api/v1/consumer
{"id":"a66a3e5d-4679-4c6d-8d5b-1d284807a81f","subject":"Hello","body":"Generated at 2018-05-17T12:18:47.566"}
Inspeccionando el log veremos:
2018-05-17 12:18:46.647 INFO 3849 --- [nio-8080-exec-7] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5c666bb7: startup date [Thu May 17 12:18:46 CEST 2018]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@859ea42
2018-05-17 12:18:46.724 INFO 3849 --- [nio-8080-exec-7] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-05-17 12:18:47.110 INFO 3849 --- [nio-8080-exec-7] c.netflix.config.ChainedDynamicProperty : Flipping property: consul-sample.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-05-17 12:18:47.191 INFO 3849 --- [nio-8080-exec-7] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-consul-sample
2018-05-17 12:18:47.280 INFO 3849 --- [nio-8080-exec-7] c.netflix.loadbalancer.BaseLoadBalancer : Client: consul-sample instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=consul-sample,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-05-17 12:18:47.292 INFO 3849 --- [nio-8080-exec-7] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2018-05-17 12:18:47.354 INFO 3849 --- [nio-8080-exec-7] c.netflix.config.ChainedDynamicProperty : Flipping property: consul-sample.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-05-17 12:18:47.363 INFO 3849 --- [nio-8080-exec-7] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client consul-sample initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=consul-sample,current list of Servers=[172.17.0.1:8080, 172.17.0.1:8080, 172.17.0.1:8080],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:3; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:172.17.0.1:8080; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 01:00:00 CET 1970; First connection made: Thu Jan 01 01:00:00 CET 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:ConsulServerList{serviceId='consul-sample', tag=null}
2018-05-17 12:18:48.315 INFO 3849 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty : Flipping property: consul-sample.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
Y eso es todo por el momento.