En este ejemplo se muestran las capacidades de Spring Cloud Consul tanto para el descubrimiento de servicios como para el uso de una configuración distribuída. En este ejemplo utilizaremos una instancia local de Consul a través de la imagen de DockerHub.
Nuestra aplicación será muy sencilla, simplemente utilizaremos las anotaciones
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
public class ConsulSampleApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulSampleApplication.class, args);
}
}
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:
curl http://localhost:8500/v1/health/service/consul?pretty
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:
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
Para consultar la configuración vamos a incluir un servicio REST sencillo que simplemente devolverá los valores recuperados:
@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:
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:
curl http://localhost:8080/api/v1/config/foo
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.
Para utilizar el sistema de registro de servicios primero declararemos el siguiente bean:
@LoadBalanced
@Bean
public RestTemplate loadbalancedRestTemplate() {
return new RestTemplate();
}
Después simplemente crearemos un cliente que obtenga el endpoint desde Consul:
@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.