Table of Contents
Use OpenTelemetry with Zipkin and the Micronaut Framework for Microservice Distributed Tracing
Use Zipkin distributed tracing to investigate the behavior of your Micronaut applications.
Authors: Sergio del Amo
Micronaut Version: 3.9.2
1. Getting Started
In this guide, we will integrate Zipkin with a Micronaut application composed of three microservices.
Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in microservice architectures. It manages both the collection and lookup of this data.
You will discover how the Micronaut framework eases Zipkin integration.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the Application
To learn more about this sample application, read the Consul and the Micronaut Framework - Microservices Service Discovery guide. The application contains three microservices.
-
bookcatalogue
- This returns a list of books. It uses a domain consisting of a book name and an ISBN. -
bookinventory
- This exposes an endpoint to check whether a book has sufficient stock to fulfil an order. It uses a domain consisting of a stock level and an ISBN. -
bookrecommendation
- This consumes previous services and exposes an endpoint that recommends book names that are in stock.
The bookrecommendation
service consumes endpoints exposed by the other services. The following image illustrates the application flow:
A request to bookrecommendation
(http://localhost:8080/books) triggers several requests through our microservices mesh.
5. Zipkin and the Micronaut Framework
5.1. Install Zipkin via Docker
The quickest way to start Zipkin is via Docker:
docker run -d -p 9411:9411 openzipkin/zipkin
6. OpenTelemetry dependencies
6.1. OpenTelemetry annotations
To enable OpenTelemetry annotations, every service includes the following annotation processor:
kapt("io.micronaut.tracing:micronaut-tracing-opentelemetry-annotation")
6.2. Micronaut OpenTelemetry HTTP
To enable creation of span objects on every HTTP server request, client request, server response, and client response, each service includes the following dependency:
implementation("io.micronaut.tracing:micronaut-tracing-opentelemetry-http")
6.3. OpenTelemetry Protocol Exporter
An exporter is a component in the OpenTelemetry Collector configured to send data to different systems/back-ends.
Each service adds the OpenTelemetry Zipkin exporter dependency.
implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
Each service sets zipkin
as exporter in configuration:
otel:
traces:
exporter: zipkin
6.4. Disable OpenTelemetry in Tests
Disable Micronaut Open Telemetry integration in tests:
micronaut:
otel:
enabled: false
6.5. Changes to Book inventory
Annotate the BookController
method with @ContinueSpan
and the method parameter with @SpanTag
:
package example.micronaut
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import io.micronaut.tracing.annotation.ContinueSpan
import io.micronaut.tracing.annotation.SpanTag
import java.util.Optional
import javax.validation.constraints.NotBlank
@Controller("/books")
open class BooksController {
@Produces(MediaType.TEXT_PLAIN)
@Get("/stock/{isbn}")
@ContinueSpan (1)
open fun stock(@SpanTag("stock.isbn") @NotBlank isbn: String): Boolean? { (2)
return bookInventoryByIsbn(isbn).map { (_, stock) -> stock > 0 }.orElse(null)
}
private fun bookInventoryByIsbn(isbn: String): Optional<BookInventory> {
if (isbn == "1491950358") {
return Optional.of(BookInventory(isbn, 4))
} else if (isbn == "1680502395") {
return Optional.of(BookInventory(isbn, 0))
}
return Optional.empty()
}
}
1 | The @ContinueSpan annotation will continue an existing span, wrapping the method call or reactive type. |
2 | The @SpanTag annotation can be used on method arguments to include the value of each argument within a Span’s tags. When you use @SpanTag you need either to annotate the method with @NewSpan or @ContinueSpan . |
7. Running the Application
Run bookcatalogue
microservice:
To run the application, execute ./gradlew run
.
...
14:28:34.034 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081
Run bookinventory
microservice:
To run the application, execute ./gradlew run
.
...
14:31:13.104 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082
Run bookrecommendation
microservice:
To run the application, execute ./gradlew run
.
...
14:31:57.389 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080
You can run a cURL command to test the whole application:
curl http://localhost:8080/books
[{"name":"Building Microservices"}]
You can then navigate to http://localhost:9411 to access the Zipkin UI.
The previous request generates a trace composed of 5 spans.
In the previous image, you can see the requests to bookinventory
are done in parallel.
In the previous image, you can see that:
-
Whenever a Micronaut HTTP client executes a new network request, a span is involved.
-
Whenever a Micronaut server receives a request, a span is involved.
The stock.isbn
tags that we configured with @SpanTag
are present, as shown in the next image:
8. Generate a Micronaut Application Native Executable with GraalVM
We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Micronaut application.
Compiling native executables ahead of time with GraalVM improves startup time and reduces the memory footprint of JVM-based applications.
Only Java and Kotlin projects support using GraalVM’s native-image tool. Groovy relies heavily on reflection, which is only partially supported by GraalVM.
|
8.1. Native executable generation
The easiest way to install GraalVM on Linux or Mac is to use SDKMan.io.
sdk install java 22.3.r11-grl
If you still use Java 8, use the JDK11 version of GraalVM. |
sdk install java 22.3.r17-grl
For installation on Windows, or for manual installation on Linux or Mac, see the GraalVM Getting Started documentation.
After installing GraalVM, install the native-image
component, which is not installed by default:
gu install native-image
To generate a native executable using Gradle, run:
./gradlew nativeCompile
The native executable is created in build/native/nativeCompile
directory and can be run with build/native/nativeCompile/micronautguide
.
It is possible to customize the name of the native executable or pass additional parameters to GraalVM:
graalvmNative {
binaries {
main {
imageName.set('mn-graalvm-application') (1)
buildArgs.add('--verbose') (2)
}
}
}
1 | The native executable name will now be mn-graalvm-application |
2 | It is possible to pass extra arguments to build the native executable |
Start the native images for the three microservices and run the same curl
request as before to check that everything works with GraalVM.
9. Next Steps
As you have seen in this guide, without any annotations, you can get distributed tracing up and running quickly with the Micronaut framework.
The Micronaut framework includes several annotations to give you more flexibility. We introduced the @ContinueSpan
and @SpanTag
annotations.
Also, you have at your disposal the @NewSpan
annotation, which will create a new span, wrapping the method call or reactive type.
Make sure to read more about Tracing with Zipkin in the Micronaut framework.
10. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.