1. Introducción
Hola a todos y bienvenidos a un nuevo post de nuestro querido blog!. En esta ocasión vamos a hablar de trazabilidad distribuida en un entorno cloud. Hemos visto en numerosas ocasiones que las peticiones que se hacen sobre nuestro entorno viajan a través de los microservicios de nuestro sistema. Por tanto parece razonable que podamos llevar a cabo la trazabilidad de dichas peticiones de forma unívoca con el fin de poder realizar diagnósticos sobre nuestro sistema. Es aquí cuando se hace necesaria la aparición de Spring Cloud Sleuth y Spring Cloud Zipkin.
Spring Cloud Sleuth no es más que una librería que permite identificar las peticiones de forma unívoca sobre nuestra arquitectua. Para ello sobre cada petición mantiene, entre otros, los siguientes atributos:
- Trace. Identificador asociado a la petición que viaja entre los microservicios
- Span. Identificador de la petición REST actual que se encuentra en un determinado microservicio
De esta forma podemos deducir que un trace contiene un conjunto de span
Spring Cloud Zipkin es un servidor que permite registrar los pares trace-span que van generando los microservicios con la librería Spring Cloud Sleuth. Además nos provee de una interfaz para realizar búsquedas de las peticiones de nuestro sistema.
Os podéis descargar el código de ejemplo de mi GitHub aquí.
Tecnologías empleadas:
- Java 8
- Gradle 3.1
- SpringBoot 1.5.2.RELEASE
- Spring 4.3.7.RELEASE
- SpringCloudSleuth 1.1.3
- SpringCloudZipkin 1.1.3
2. Spring Cloud Zipkin
Añadimos las dependencias org.springframework.cloud:spring-cloud-starter-zipkin
, io.zipkin.java:zipkin-server
y io.zipkin.java:zipkin-autoconfigure-ui
al build.gradle
group 'com.jorgehernandezramirez.spring.springcloud' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'org.springframework.boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.zipkin.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-zipkin' compile 'io.zipkin.java:zipkin-server' compile 'io.zipkin.java:zipkin-autoconfigure-ui' }
Utilizamos la anotación @EnableZipkinServer
para arrancar el servidor Zipkin
package com.jorgehernandezramirez.spring.springcloud.zipkin; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import zipkin.server.EnableZipkinServer; @EnableZipkinServer @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Arrancamos en el puerto 9411
Compilamos, arrancamos y vamos a http://localhost:9411
3. Spring Cloud Sleuth
Añadimos las dependencias org.springframework.cloud:spring-cloud-starter-sleuth
y org.springframework.cloud:spring-cloud-sleuth-zipkin
a nuestro build.gradle
group 'com.jorgehernandezramirez.spring.springcloud' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'org.springframework.boot' dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6' } } sourceCompatibility = 1.8 springBoot { mainClass = "com.jorgehernandezramirez.spring.springcloud.sleuth.Application" } repositories { mavenCentral() } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-sleuth' compile 'org.springframework.cloud:spring-cloud-sleuth-zipkin' }
Indicamos el nombre de la aplicación que se va a registrar en el servidor Zipkin
Indicamos en qué url se encuentra Zipkin
spring: zipkin: #No hace falta, por defecto se va a buscar el servidor zipkin a localhost:9411 baseUrl: http://localhost:9411
Además de enviar los atributos trace y span al servidor Zipkin, permite imprimir dichos valores en nuestros logs. Para ello es necesario indicar en nuestro patrón de log los siguientes atributos
- %X{X-B3-TraceId:-} Para el atributo trace
- %X{X-B3-SpanId:-} Para el atributo span
<?xml version="1.0" encoding="UTF-8"?> <configuration> <springProperty scope="context" name="springAppName" source="spring.application.name"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern> %d{dd-MM-yyyy HH:mm:ss} [%thread] %-5level [${springAppName:-}, trace=%X{X-B3-TraceId:-},span=%X{X-B3-SpanId:-}] %logger{40} - %msg%n </Pattern> </layout> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>
Main que arranca el microservicio
package com.jorgehernandezramirez.spring.springcloud.sleuth; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public Application(){ //For Spring } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Para Sleuth es necesario indicar una implementación de la clase Sampler
. Ésta le indicará cuando deberá enviar la información al servidor Zipkin. Para realizar diagnósticos o detectar posibles comportamientos puede no ser necesario enviar toda la información generada siendo suficiente una muestra reducida. En nuestro caso con la implementación AlwaysSampler
indicamos que considere siempre la información.
package com.jorgehernandezramirez.spring.springcloud.sleuth.configuration; import org.springframework.cloud.sleuth.Sampler; import org.springframework.cloud.sleuth.sampler.AlwaysSampler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class SleuthConfiguration { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } @Bean public Sampler buildAlwaysSampler() { return new AlwaysSampler(); } }
Mostramos el controlador de ejemplo que nos servirá para realizar nuestras pruebas
package com.jorgehernandezramirez.spring.springcloud.sleuth.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class PingController { private static Logger LOGGER = LoggerFactory.getLogger(PingController.class); @Autowired private RestTemplate restTemplate; @RequestMapping("/ping") public String doPing(){ LOGGER.info("Realizando ping..."); return "Alive!"; } @RequestMapping("/rest/ping") public String doRestPing(){ LOGGER.info("Realizando rest ping..."); return restTemplate.getForObject("http://localhost:8080/ping", String.class); } }
4. Probando la aplicación
A continuación compilamos nuestros fuentes y arrancamos Sleuth y Zipkin.
Compilamos
Arrancamos la aplicación
Atacamos a http://localhost:8080/rest/ping y obtenemos por consola
06-05-2017 11:56:03 [http-nio-8080-exec-4] INFO [backend, trace=2eee2af7a9cfbbcd,span=2eee2af7a9cfbbcd] c.j.s.s.sleuth.controller.PingController – Realizando rest ping…
06-05-2017 11:56:03 [http-nio-8080-exec-6] INFO [backend, trace=2eee2af7a9cfbbcd,span=01ffcce9c74eca0e] c.j.s.s.sleuth.controller.PingController – Realizando ping…
Si vamos a nuestro servidor Zipkin y buscamos por la traza 2eee2af7a9cfbbcd obtendremos