El auge de las arquitecturas de microservicios ha traído consigo algunos retos que debemos ser capaces de abordar para conseguir un sistema consistente, como son la monitorización, gestión de la configuración centralizada, centralización de logs...

En este tipo de arquitecturas una petición de un consumidor de nuestro sistema puede desencadenar varias llamadas internas entre microservicios, por lo que es importante poder asociar un identificador único de petición para que se pueda propagar entre estas llamadas y así poder posteriormente consultar las peticiones de forma centralizada. Spring pone a nuestra disposición herramientas que nos facilitan este trabajo: Spring Cloud Sleuth y Zipkin.

En este post explicaremos la forma de almacenar y explotar de forma centralizada la trazabilidad de peticiones a nuestros servicios utilizando estas herramientas.

trazabilidad-distribuida

Spring Cloud Sleuth

La primera pieza de la ecuación para conseguir identificar inequívocamente una petición es Spring Cloud Sleuth. Es una librería que implementa una solución de trazado distribuido para Spring Cloud. Con tan solo incluir la dependencia con Sleuth en los microservicios, dotamos al ecosistema de un mecanismo automático de identificación de peticiones, ya que añade varios campos útiles a las mismas para identificarlas.

De estos campos extra, los más importantes son traceId y spanId (son los que realmente aseguran una traza única), los cuales incluye en las salida de log del microservicio. El sistema de trazado utilizado por Sleuth se basa en terminología Dapper, cuyos principales conceptos son:

A continuación se muestra un diagrama de cómo se comportaría una petición en una llamada entre dos microservicios que hacen uso de Sleuth:

trazabilidad-distribuida

Además de generar los identificadores únicos y añadirlos a los logs de la aplicación, es necesario que se propaguen de forma correcta entre los microservicios que forman parte de la petición.

Para ello incluye una capa que gestiona automáticamente dicha propagación de los datos de trazado de Sleuth entre los sistemas más comunes de comunicación entre microservicios Spring Cloud, como son los RestTemplate, servlets, filtros Zuul, clientes feign, mensajería…

A continuación se presentan las posibilidades de implementación de la trazabilidad que ofrece Sleuth.

Implementaciones Spring Cloud Sleuth

Spring initializr es una herramienta que Spring ha puesto a nuestra disposición para agilizar la creación de aplicaciones Spring Boot. Según la misma, existen dos posibles dependencias Sleuth:

trazabilidad-distribuida

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

Una vez incluida se lanza una petición con un sistema de llamadas compatible con Sleuth, como puede ser RestTemplate:


ResponseEntity<String> response  =
restTemplate.getForEntity("http://localhost:8089/getName", String.class);

Por cada petición que trace contendrá identificadores para definirla unívocamente, como se ve en la imagen (5501a4bacd8faaa8 el traceId y 44d49402e8b56e98 el spanId del servicio llamante):

Como se ha comentado anteriormente, estos identificadores se envían de un servicio a otro por cabeceras HTTP:


<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-sleuth-stream</artifactId>
</dependency>

Además de la dependencia con el broker, en este caso RabbitMQ:


<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

A continuación se define el canal de comunicación tanto en el emisor como en el receptor:


spring:
  cloud:
    stream:
      bindings:
        sleuth-stream-save:
          destination: sleuth-stream-save
          group: sleuth-stream
          content-type: application/json


public interface SleuthStreamProcessor {
       String SLEUTH_SAVE = "sleuth-stream-save";
       @Output(SleuthStreamProcessor.SLEUTH_SAVE)
       MessageChannel sleuthSave();
}


public interface SleuthStreamProcessor {
       String SLEUTH_SAVE = "sleuth-stream-save";
       @Input(SleuthStreamProcessor.SLEUTH_SAVE)
       SubscribableChannel sleuthSave();
}

Y como se ve en las imágenes, el traceId y el spanId se propaga en la cabecera del mensaje enviado a RabbitMQ:

Zipkin

La segunda pieza de la ecuación es Zipkin. Si con Sleuth se consigue identificar los saltos entre microservicios de una petición en concreto, con Zipkin se consigue un sistema completo de trazabilidad distribuida para microservicios Spring Cloud: generación de trazas con ids únicos, almacenamiento de las mismas y posterior visualización en una interfaz gráfica para su estudio. Abraza la funcionalidad de Spring Cloud Sleuth y la amplía para formar un sistema completo de monitorización de peticiones entre microservicios.

Su objetivo es consultar la salud del ecosistema. Si se quiere tener un procesado a más alto nivel, se debe utilizar en conjunto con algún sistema de agregación de logs como Kibana o Splunk ya que estos sistemas permiten explotar las trazas de forma mucho más intensiva y personalizada.

Implementaciones Zipkin

Como Zipkin se apoya en Sleuth, tiene dos posibles sistemas de envío de trazas al servidor, mediante cabeceras HTTP y con brokers de mensajería. Según Spring Initializr estas son las dependencias publicadas para Zipkin:

trazabilidad-distribuida
trazabilidad-distribuida

En los microservicios proveedores de trazas, se debe incluir la dependencia con el Zipkin Client (spring-cloud-starter-zipkin) el cual tiene embebida la dependencia con spring-cloud-sleuth-zipkin que compatibiliza las trazas Sleuth con las que espera recibir Zipkin.


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

Una vez generadas las trazas, se envían en la cabecera HTTP al Zipkin Server (zipkin-server).


<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
</dependency>

Si no se le especifica ninguna forma de almacenarlas, las deja en memoria. Esta es una mala práctica si se quiere implantar este sistema de trazabilidad en producción, por lo que se debería especificar una base de datos, como puede ser Elasticsearch (zipkin-autoconfigure-storage-elasticsearch).


<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-storage-elasticsearch</artifactId>
    <version>1.2.1</version>
</dependency>

Se deben configurar las propiedades de conexión en el zipkin-server:


zipkin:
  storage:
    type: elasticsearch
    elasticsearch:
      cluster: elasticsearch
      hosts: localhost:9300
      index: zipkin
      index-shards: 5
      index-replicas: 1

Zipkin crea índices por fecha en Elasticsearch para mejorar su gestión:

Para gestionar el volumen de información que se almacena en elastic, el creador recomienda eliminar periódicamente estos índices con Elastic Curator.

Para que una aplicación Spring Boot se convierta en un Zipkin Server tan solo hace falta incluir la anotación @EnableZipkinServer.

trazabilidad-distribuida

Los microservicios de los que se desea obtener las trazas deben contener la dependencia con Sleuth Stream el cual enviará las mismas al broker configurado.

Por su parte, el Zipkin Stream (spring-cloud-sleuth-zipkin-stream) actúa de la misma forma que el Zipkin Server, pero recogiendo las trazas del broker (se debe incluir la dependencia con el mismo) y almacenándolas en memoria si no se especifica nada o en una base de datos si se especifica.


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

Para empezar a trabajar no es necesaria ninguna acción de creación de colas, ya que el propio Sleuth crea automáticamente todo lo necesario. Al ejecutar la aplicación se encarga de gestionarlo.

Con la anotación @EnableZipkinStreamServer dotas a una aplicación Spring Boot de ésta funcionalidad. Cabe destacar que existe un api sobre el que ejecutar consultas al Zipkin Server.

Las peticiones mas importantes son:


curl   http://localhost:9411/api/v1/services
curl   http://localhost:9411/api/v1/traces?serviceName=<service-name>
curl   http://localhost:9411/api/v1/trace/<id>
curl   http://localhost:9411/api/v1/dependencies?endTs=$(date +%s)

A continuación se muestra un ejemplo de traza en una llamada entre dos microservicios, donde zipkin-client y zipkin-client-response son los nombres de los microservicios, 994f6a06f0193d35 es el traceId y a079a9da1fcc2960 y 1629c34056ef5226 son los correspondientes spanId. El campo true indica si la traza se exporta o no a Zipkin, es decir, si se envía al Zipkin Server para ser tratada, o solo aparece en los logs.

trazabilidad-distribuida

De cara a mejorar el rendimiento y no sobrecargar el sistema de logs, el sistema por defecto envía un 10% de las trazas generadas al Zipkin Server. Si se desea cambiar este porcentaje, se debe modificar la siguiente propiedad: spring.sleuth.sampler.percentage = 0.2

Zipkin UI

Zipkin provee de una interfaz gráfica que agiliza las consultas a la BBDD del Zipkin Server y muestra la información recogida. Para tenerla disponible, simplemente hay que incluir la siguiente dependencia zipkin-autoconfigure-ui en la aplicación.

Zipkin UI permite filtrar las trazas para obtener un desgranado de las mismas. Una vez obtenido el rango de trazas que deseamos estudiar, se puede ver en detalle cada una de las trace y los spans que las componen.

Trace generados en el intervalo:

trazabilidad-distribuida

Spans de uno de los trace:

trazabilidad-distribuida

Annotation (cs, sr, ss, cr) y Tags de la petición:

trazabilidad-distribuida

Además, genera un grafo de la comunicación de los servicios en dicha traza. Haciendo clic en los elementos obtenemos información ampliada.

trazabilidad-distribuida

A la hora de implementar una solución basada en microservicios se deben abordar ciertas peculiaridades que con aplicaciones monolíticas no ocurren, ya que al ser sistemas descentralizados el control de la comunicación entre los diferentes componentes es muy importante.

Al utilizar las herramientas Spring Cloud mencionadas no solo nos aportará la necesaria correlación de logs de una petición entre distintos microservicios, sino que también proporciona una serie de herramientas para poder visualizar la salud de nuestro ecosistema.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.