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

server:
   port: 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

spring:
  application:
     name: backend

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

gradle clean build

Arrancamos la aplicación

java -jar [JAR_EN_BUILD_LIBS]

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

5. Referencias