Monday, November 26, 2018

How to configure distribute tracing for spring-boot apps on Java 11


One of the big challenges in micro-services world is ability to understand what the system really does and being able to spot the potentials issues before the get propagated into the production.  The performance of JVM is most probably not the case why the system does not respond properly or provides a scary results. Aside of it a micro-services can be a really messy to debug especially when certain number of them has been reached.

In following post you will get an idea how the tracing can implement for spring-boot based applications. The blog post uses two libraries Jaeger and Zipkin. Both libraries are according to OpenTracing standard. The OpenTracing standard provides vendor-neutral APISs and instrumentation. 

Example source code is available on my GitHub account, project: spring-profile-logging

Motivation
The example project consists from two application Consumer and Producer, micro-services. Those two micro-services are communicating between each other over the HTTP protocol. (see Img.1.)
Img.1.: Architecture overview 

The basic idea behind the demo is the specific vehicle type construction and the process can be traced. For the demo simplification has been used two micro-services interacting between each other. The Producer contains the set of the Schedulers that are producing different types of HTTP requests (GET, PUT, POST ...). The Consumer is processing those requests.  It's possible to participate on the running process by using the consumer API: 

GET: http://<tracing-consumer>:<port>/shop/models
GET: http://<tracing-consumer>:<port>/shop/vehicle?id=<vehicle_id>
POST: http://<tracing-consumer>:<port>/shop/models/vehicle
PUT: http://<tracing-consumer>:<port>/shop/models/vehicle

The JSON vehicle model is very simple: {"id": 33, "name" : "some care2"}, but you can find the the model definition in VehicleModel class. 

The example can be used for low-level profiling using Java Mission Control / Flight Recorder to deep dive into the caused latencies.  How to run locally the last version of JMC see my previous blog post.

Technologies
As was mentioned before the project is the collection of two different spring-applications running upon the Java 11 and above (you can use previous versions ;)

The gradle.libraries files contains used versions:

projectJavaVersion = "11"
springBootStarterVersion = "2.1.0.RELEASE"
openTracingSpringVersion = "0.2.1"
openTracingJaegerSpringVersion = "0.2.2"
openTracingZipkinSpringVersion = "0.2.0"

Understanding consumer and producer applications 
The relation between producer and consumer apps is obvious from  the schema (see: Img.1.). Both of them use @EnableAutoConfiguration feature provided by spring to configure the Tracers.  It means spring starter of the specific tracer configures it based on variables application provides. The applications are configured by using YAML file application.yml with properties, example. 

spring:
  application:
    name: ${APPLICATION_NAME}
server:
  port: ${DEMO_PORT}

tracing:
  consumer:
    url: ${DEMO_CONSUMER_URL}
opentracing:
  jaeger:
    udp-sender:
      host: ${OPENTRACING_HOST}
      port: ${OPENTRACING_PORT}

The variables can be configured on demand (local environment , docker).  The consumer application may look in following way: 

@SpringBootApplication
public class ConsumerApp {

    public static void main(String[] args) {
        new SpringApplicationBuilder(ConsumerApp.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

Aside of it Producer application may use lightly different approach of configuring spring-boot framework. 

@EnableAutoConfiguration
@EnableScheduling
@RestController
public class ProducerApp {
...
    @Scheduled(fixedRate = 10000)
    public void postNewVehicle() {
        VehicleModel vehicle = new VehicleModel();
        vehicle.setName("vehicle" + vehicleNumber.getAndIncrement());
        ResponseEntity<VehicleModel> response = restTemplate.postForEntity(consumerServiceUrl + "/shop/models/vehicle",    vehicle, VehicleModel.class);
    }
...
    public static void main(String[] args) {
        SpringApplication.run(ProducerApp.class,args);
    }
}

Running producer consumer demo
Now we have configured both applications. The project offers couple of option how it can be run. You can use your favorite IDE to run it locally, as separate docker images or use docker-compose. We decided to use the docker compose file which provide us better comfort and all three part run isolated.  The  docker-compose.yml may look in following way: 

version: "3.7"

services:

  jaeger:

    container_name: jaeger

    image: jaegertracing/all-in-one:latest

    ports:
     ...

    networks:

      - simulation

  tracing-consumer:
    container_name: tracing-consumer
    depends_on:
      - jaeger
    environment:
      APPLICATION_NAME: "docker_tracing_consumer"
      DEMO_PORT: 8091
      OPENTRACING_HOST: jaeger
      OPENTRACING_PORT: 6831
    ports:
      - "8091:8091"
      - "9999:9999"
    networks:
    - simulation
  tracing-producer:
    container_name: tracing-producer
    depends_on:
    - jaeger
    - tracing-consumer
    environment:
     ...
    ports:
      - "8092:8092"
    networks:
      - simulation

Getting some result from Jaeger Tracer
As you may have noticed the example code uses only Jaeger tracer. The Jaeger project has been open-source by Uber technologies. In order you want to use Zipkin inside the docker-compose file there are necessary small modifications, but Zipkin tracer can be run fully locally. 
Let's run the project: 
$docker-compose -f ./docker/docker-compose.yml up
by executing $docker ps command we can see 3 container are running.  
The producer schedulers already start sending requests to the consumer.  Inside the web browser we can access the Jaeger UI on configured port (see Img.2):
http://localhost:16686

Img.2.: Jaeger UI
On the left conner is possible to select  configured micro-service. As tracing is basically span analysis we can track down each span that is included inside the trace:
Img.3.: Jaeger Tracing spans
Final Thoughts
Following simple example has shown the power of instrumentation using spring-boot together with OpenTracing standard. The tracing technology opens the door to see your real micro-services architecture, not just guess. It allows you to spot some initial problems during the development. Those problems can be latter more investigated by using different technologies like Java Mission Control / Java Flight Recorder. 
The example also shows the importance of tracing in order to clearly understand the system behaviour. 

Enjoy The Demo (here)


No comments: