mp.guides.tracing.adoc Maven / Gradle / Ivy
The newest version!
///////////////////////////////////////////////////////////////////////////////
Copyright (c) 2019, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
///////////////////////////////////////////////////////////////////////////////
= Helidon MP Tracing Guide
:description: Helidon tracing
:keywords: helidon, tracing, microprofile, guide
:toc:
:rootdir: {docdir}/../..
:imagesdir: {rootdir}/images
include::{rootdir}/includes/mp.adoc[]
This guide describes how to create a sample MicroProfile (MP) project
that can be used to run some basic examples using tracing with Helidon MP.
== What You Need
For this 30 minute tutorial, you will need the following:
include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites]
== Introduction
Distributed tracing is a critical feature of microservice based applications, since it traces workflow both
within a service and across multiple services. This provides insight to sequence and timing data for specific blocks of work,
which helps you identify performance and operational issues. Helidon MP includes support for distributed tracing
through the https://opentracing.io[OpenTracing API]. Tracing is integrated with WebServer
and Security using either the https://zipkin.io[Zipkin] or https://www.jaegertracing.io[Jaeger] tracers.
=== Tracing Concepts
This section explains a few concepts that you need to understand before you get started with tracing.
* In the context of this document, a _service_ is synonymous with an application.
* A _span_ is the basic unit of work done within a single service, on a single host.
Every span has a name, starting timestamp, and duration.
For example, the work done by a REST endpoint is a span.
A span is associated to a single service, but its descendants can belong to different services and hosts.
* A _trace_ contains a collection of spans from one or more services, running on one or more hosts. For example,
if you trace a service endpoint that calls another service, then the trace would contain spans from both services.
Within a trace, spans are organized as a directed acyclic graph (DAG) and
can belong to multiple services, running on multiple hosts. The _OpenTracing Data Model_ describes the details
at https://opentracing.io/specification[The OpenTracing Semantic Specification].
Spans are automatically created by Helidon as needed during execution of the REST request.
== Getting Started with Tracing
The examples in this guide demonstrate how to integrate tracing with Helidon, how to view traces, how to trace
across multiple services, and how to integrate tracing with Kubernetes. All examples use Jaeger and traces
will be viewed using Jaeger UI.
=== Create a Sample Helidon MP project
Use the Helidon MP Maven archetype to create a simple project that can be used for the examples in this guide.
[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion={helidon-version} \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-mp \
-Dpackage=io.helidon.examples.quickstart.mp
----
[source,bash]
.The project will be built and run from the `helidon-quickstart-mp` directory:
----
cd helidon-quickstart-mp
----
=== Set up Jaeger
First, you need to run the Jaeger tracer. Helidon will communicate with this tracer at runtime.
[source,bash]
.Run Jaeger within a docker container, then check the Jaeger server working:
----
docker run -d --name jaeger \ <1>
-e COLLECTOR_OTLP_ENABLED=true \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-p 14269:14269 \
-p 9411:9411 \
jaegertracing/all-in-one:1.50
----
<1> Run the Jaeger docker image.
[source,bash]
.Check the Jaeger server by opening in browser:
----
http://localhost:16686/search
----
=== Enable Tracing in the Helidon Application
Update the pom.xml file and add the following Jaeger dependency to the ``
section (*not* ``). This will enable Helidon to use Jaeger at the
default host and port, `localhost:14250`.
[source,xml]
.Add the following dependency to `pom.xml`:
----
io.helidon.microprofile.tracing
helidon-microprofile-tracing
io.helidon.tracing.providers
helidon-tracing-providers-jaeger
----
All spans sent by Helidon to Jaeger need to be associated with a service. Specify the service name below.
[source,bash]
.Add the following line to `META-INF/microprofile-config.properties`:
----
tracing.service=helidon-mp-1
----
[source,bash]
.Build the application, skipping unit tests, then run it:
----
mvn package -DskipTests=true
java -jar target/helidon-quickstart-mp.jar
----
[source,bash]
.Run the curl command in a new terminal window and check the response:
----
curl http://localhost:8080/greet
----
[source, json]
.JSON response:
----
{
"message": "Hello World!"
}
----
=== View Tracing Using Jaeger UI
The tracing output data is verbose and can be difficult to interpret using the REST API, especially since it represents a structure of spans. Jaeger provides a web-based UI at http://localhost:16686/search, where you can see a visual representation of the same data and the relationship between spans within a trace. If you see a `Lens UI` button at the top center then click on it, and it will take you to the specific UI used by this guide.
Click on the UI Find traces button (the search icon) as shown in the image below.
.Jaeger UI
image::guides/12_tracing_refresh.png[Trace Refresh,role="fit"]
The image below shows the trace summary, including start time and duration of each trace. There are several traces, each one generated in response to a `curl http://localhost:8080/greet` invocation. The oldest trace will have a much longer duration since there is one-time initialization that occurs.
.Tracing list view
image::guides/12_tracing_top.png[Traces,role="fit"]
Click on a trace, and you will see the trace detail page where the spans are listed. You can clearly see the root span and the relationship among all the spans in the trace, along with timing information.
.Trace detail page
image::guides/12_tracing_detail.png[Trace Detail,role="fit"]
NOTE: A parent span might not depend on the result of the child. This is called a `FollowsFrom` reference, see https://github.com/opentracing/specification/blob/master/specification.md[Open Tracing Semantic Spec]. Note that the last span that writes the response after the root span ends falls into this category.
You can examine span details by clicking on the span row. Refer to the image below, which shows the `security` span details, including timing information. You can see times for each space relative to the root span. These rows are annotated with `Server Start` and `Server Finish`, as shown in the third column.
=== Enable Tracing on CDI Beans
So far in this tutorial, you have used tracing with JAX-RS without needing to annotate. You can enable tracing on other CDI beans, either at the class level or at the method level, as shown by the following examples.
==== Tracing at the Method Level
To trace at the method level, you just annotate a method with @Traced.
[source,java]
.Add the @Traced annotation to the `getMessage` method:
----
include::{sourcedir}/mp/guides/TracingSnippets.java[tag=snippet_1, indent=0]
----
<1> Enable tracing for getMessage.
[source,bash]
.Build and run the application, then invoke the endpoints and check the response:
----
curl http://localhost:8080/greet
----
Click the back button on your browser, then click on the UI refresh button to see the new trace. Select the newest trace
in the list to see the trace detail page like the one below.
Notice the new span named `io.helidon.examples.quickstart.mp.greetingprovider.getmessage`.
==== Tracing at the Class Level
To trace at the class level, annotate the class with @Traced. This will enable tracing for all class methods,
except for the constructor and private methods.
[source,java]
.Add @Traced to the `GreetingProvider` class and remove @Traced from the `getMessage` method:
----
include::{sourcedir}/mp/guides/TracingSnippets.java[tag=snippet_2, indent=0]
----
<1> This will enable tracing for all class methods, except for the constructor and methods that are private.
<2> Remove @Traced for the `getMessage` method.
[source,bash]
.Build and run the application, then invoke the endpoints and check the response:
----
curl http://localhost:8080/greet
----
You can refresh the UI view and drill down the trace to see the new spans.
NOTE: Methods invoked directly by your code are not enabled for tracing, even if you explicitly annotate them with @Traced. Tracing only works for methods invoked on CDI beans. See the example below.
[source,java]
.Update the `GreetingProvider` class with the following code:
----
include::{sourcedir}/mp/guides/TracingSnippets.java[tag=snippet_3, indent=0]
----
<1> The `getMessage` method will be traced since it is externally invoked by `GreetResource`.
<2> The `getMessage2` method will not be traced, even with the @Traced annotation, since it is called internally by `getMessage`.
[source,bash]
.Build and run the application, then invoke the endpoints:
----
curl http://localhost:8080/greet
----
Then check the response in the Jaeger UI in the browser.
=== Trace Across Services
Helidon automatically traces across services as long as the services use the same tracer, for example, the same instance of Jaeger.
This means a single trace can include spans from multiple services and hosts. OpenTracing uses a `SpanContext` to propagate tracing information across process boundaries. When you make client API calls, Helidon will internally call OpenTracing APIs to propagate the `SpanContext`. There is nothing you need to do in your application to make this work.
To demonstrate distributed tracing, you will need to create a second project, where the server listens on port 8081.
Create a new root directory to hold this new project, then do the following steps, similar to
what you did at the start of this guide:
==== Create a second service
[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn -U archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion={helidon-version} \
-DgroupId=io.helidon.examples \
-DartifactId=helidon-quickstart-mp-2 \
-Dpackage=io.helidon.examples.quickstart.mp
----
[source,bash]
.The project will be built and run from the `helidon-quickstart-mp` directory:
----
cd helidon-quickstart-mp-2
----
[source,xml]
.Add the following dependency to `pom.xml`:
----
io.helidon.tracing.providers
helidon-tracing-providers-jaeger
----
[source,bash]
.Replace `META-INF/microprofile-config.properties` with the following:
----
app.greeting=Hello From MP-2
tracing.service=helidon-mp-2
# Microprofile server properties
server.port=8081
server.host=0.0.0.0
----
[source,bash]
.Build the application, skipping unit tests, then run it:
----
mvn package -DskipTests=true
java -jar target/helidon-quickstart-mp-2.jar
----
[source,bash]
.Run the curl command in a new terminal window and check the response (*notice the port is 8081*) :
----
curl http://localhost:8081/greet
----
[source, json]
.JSON response:
----
{
"message": "Hello From MP-2 World!"
}
----
==== Modify the first service
Once you have validated that the second service is running correctly, you need to modify the original application to
call it.
[source,java]
.Replace the `GreetResource` class with the following code:
----
include::{sourcedir}/mp/guides/TracingSnippets.java[tag=snippet_4, indent=0]
----
<1> This is the `WebTarget` needed to send a request to the second service at port `8081`.
<2> This is the new endpoint that will call the second service.
[source,bash]
.Build and run the application, then invoke the endpoint and check the response:
----
curl -i http://localhost:8080/greet/outbound # <1>
----
<1> The request went to the service on `8080`, which then invoked the service at `8081` to get the greeting.
[source, hocon]
----
{
"message": "Hello From MP-2 World!" // <1>
}
----
<1> Notice the greeting came from the second service.
Refresh the Jaeger UI trace listing page and notice that there is a trace across two services.
.Tracing across multiple services detail view
image::guides/12_tracing_detail_2_services.png[Traces,role="fit"]
In the image above, you can see that the trace includes spans from two services. You will notice there is a gap before the sixth span, which is a `get` operation. This is a one-time client initialization delay. Run the `/outbound` curl command again and look at the new trace to
see that the delay no longer exists.
You can now stop your second service, it is no longer used in this guide.
== Integration with Kubernetes
The following example demonstrate how to use Jaeger from a Helidon application running in Kubernetes.
[source,bash]
.Add the following line to `META-INF/microprofile-config.properties`:
----
tracing.host=jaeger
----
[source,bash]
.Stop the application and build the docker image for your application:
----
docker build -t helidon-tracing-mp .
----
=== Deploy Jaeger into Kubernetes
[source,yaml]
.Create the Kubernetes YAML specification, named `jaeger.yaml`, with the following contents:
----
apiVersion: v1
kind: Service
metadata:
name: jaeger
spec:
ports:
- port: 16686
protocol: TCP
selector:
app: jaeger
---
kind: Pod
apiVersion: v1
metadata:
name: jaeger
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one
imagePullPolicy: IfNotPresent
ports:
- containerPort: 16686
----
[source,bash]
.Create the Jaeger pod and ClusterIP service:
----
kubectl apply -f ./jaeger.yaml
----
[source,bash]
.Create a Jaeger external server and expose it on port 9142:
----
kubectl expose pod jaeger --name=jaeger-external --port=16687 --target-port=16686 --type=LoadBalancer # <1>
----
<1> Create a service so that you can access the Jaeger UI.
Navigate to http://localhost:16687/search to validate that you can access Jaeger running in Kubernetes. It may
take a few seconds before it is ready.
=== Deploy Your Helidon Application into Kubernetes
[source,yaml]
.Create the Kubernetes YAML specification, named `tracing.yaml`, with the following contents:
----
kind: Service
apiVersion: v1
metadata:
name: helidon-tracing # <1>
labels:
app: helidon-tracing
spec:
type: NodePort
selector:
app: helidon-tracing
ports:
- port: 8080
targetPort: 8080
name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: helidon-tracing
spec:
replicas: 1 # <2>
selector:
matchLabels:
app: helidon-tracing
template:
metadata:
labels:
app: helidon-tracing
version: v1
spec:
containers:
- name: helidon-tracing
image: helidon-tracing-mp
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
----
<1> A service of type `NodePort` that serves the default routes on port `8080`.
<2> A deployment with one replica of a pod.
[source,bash]
.Create and deploy the application into Kubernetes:
----
kubectl apply -f ./tracing.yaml
----
=== Access Your Application and the Jaeger Trace
[source,bash]
.Get the application service information:
----
kubectl get service/helidon-tracing
----
[source,bash]
----
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helidon-tracing NodePort 10.99.159.2 8080:31143/TCP 8s # <1>
----
<1> A service of type `NodePort` that serves the default routes on port `31143`.
[source,bash]
.Verify the tracing endpoint using port `31143`, your port will likely be different:
----
curl http://localhost:31143/greet
----
[source, json]
.JSON response:
----
{
"message": "Hello World!"
}
----
Access the Jaeger UI at http://localhost:16687/search and click on the refresh icon to see the trace that was just created.
=== Cleanup
You can now delete the Kubernetes resources that were just created during this example.
[source,bash]
.Delete the Kubernetes resources:
----
kubectl delete -f ./jaeger.yaml
kubectl delete -f ./tracing.yaml
kubectl delete service jaeger-external
docker rm -f jaeger
----
include::{rootdir}/includes/tracing/common-callbacks.adoc[tags=defs;detailed]
== Summary
This guide has demonstrated how to use the Helidon MP tracing feature with Jaeger. You have learned to do the following:
* Enable tracing within a service
* Use tracing with JAX-RS and CDI beans
* Use the Jaeger UI
* Use tracing across multiple services
* Integrate tracing with Kubernetes
Refer to the following references for additional information:
* link:{microprofile-tracing-spec-url}[MicroProfile OpenTracing specification]
* link:{microprofile-tracing-javadoc-url}[MicroProfile OpenTracing Javadoc]
* link:{javadoc-base-url}/index.html?overview-summary.html[Helidon Javadoc]