labcabrera / sample-spring-consul

Spring Cloud Consul app sample
0 stars 1 forks source link
consul spring-cloud

= 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:


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:

[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:


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.

== 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.