Configuring distributed tracing in Kubernetes with OpenTracing, Jaeger and Ingress-NGINX

In an era when large companies are using more than 500 microservices at the same time, it is important to be able to quickly figure out what caused the failure or decreased performance. Without specialized tools, it can be like looking for a needle in a haystack.

Distributed Tracing is the method used to monitor applications. For microservices, it is simply irreplaceable.

As an example, we will use the application Meow-Microcreated specially for this article. But you can deploy your own applications if you like.

What do we need?

This tutorial assumes that you understand Go code, know how to use Ingress-nginx, and know how core Kubernetes objects such as Service and Deployment work.

If you want to brush up on your knowledge, use these resources:

Running Kubernetes in Docker Desktop

Let’s start with the installation Docker Desktop, which allows not only to run containers, but also to create a local Kubernetes cluster.

After installing Docker Desktop, follow these steps to create your Kubernetes cluster:

  1. Click on the icon Preferences

2. Select the tab Kubernetes, check the box Enable Kubernetes and press the button Apply & Restart

3. In the window that appears, click the button Install and wait for the installation to complete

4. Select an item Kubernetes in the taskbar

5. In the context menu, select docker-desktop

6. Check that you are connected to the correct cluster

$ kubectl cluster-info

Kubernetes master is running at 
https://kubernetes.docker.internal:6443
KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/
namespaces/kube-system/services/kube-dns:dns/proxy

Installing the Ingress-NGINX Controller

1. Use the command from official leadership to install Ingress-NGINX Controller.

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/cloud/deploy.yaml

namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created]
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

2. Verify that the Ingress Controller installation was successful. The pods must be running and in the Ready state.

$ kubectl get pods -n ingress-nginx

NAME                                     READY   STATUS    RESTARTS 
ingress-nginx-admission-create-52jsl        0/1     Completed   0          
ingress-nginx-admission-patch-78fkc         0/1     Completed   0          
ingress-nginx-controller-6f5454cbfb-qsfnn   1/1     Running     0 

Important: If the Pod is in Pending status due to lack of CPU / Memory resources, you can add resources to your cluster in the Docker Desktop settings.

Installing Jaeger and configuring the Ingress Controller

Jaeger is a distributed tracing platform that we will use to monitor our microservices. Install Jaeger and enable tracing at the Ingress Controller level.

1. Clone the repository first meow-micro, we will use this project in all examples.

$ git clone https://github.com/diazjf/meow-micro.git

Cloning into 'meow-micro'...
remote: Enumerating objects: 105, done.
...
$ cd meow-micro

2. In the repository you will find manifests for jaeger-all-in-one… Let’s install Jaeger from these manifests.

$ kubectl apply -f jaeger/jaeger-all-in-one.yaml

deployment.apps/jaeger created
service/jaeger-query created
service/jaeger-collector created
service/jaeger-agent created
service/zipkin created

3. Make sure Jaeger is up and running.

$ kubectl get pods

NAME                      READY   STATUS    RESTARTS   AGE
jaeger-6f6b5d8689-8gccp   1/1     Running   0          17s

4. It’s time to configure the joint work of Ingress-NGINX and Jaeger, for this you need to add parameters enable-opentracing and jaeger-collector-host in ingress-nginx-controller ConfigMap. In the parameter jaeger-collector-host specify the name of the Jaeger service.

$ echo '
  apiVersion: v1
  kind: ConfigMap
  data:
    enable-opentracing: "true"
    jaeger-collector-host: jaeger-agent.default.svc.cluster.local              
  metadata:
    name: ingress-nginx-controller
    namespace: ingress-nginx
  ' | kubectl replace -f -
configmap/ingress-nginx-controller replaced

5. Make sure that Ingress Controller is enabled and configured in the settings. opentracing

$ kubectl get pods -n ingress-nginx | grep controller

ingress-nginx-controller-6f5454cbfb-qptxt   1/1     Running     0          8m56s

$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep ngx_http_opentracing_module.so"

load_module /etc/nginx/modules/ngx_http_opentracing_module.so;

$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat nginx.conf | grep jaeger"

opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so /etc/nginx/opentracing.json;

$ kubectl exec -it ingress-nginx-controller-6f5454cbfb-qptxt -n ingress-nginx -- bash -c "cat /etc/nginx/opentracing.json"

{
  "service_name": "nginx",
  "propagation_format": "jaeger",
  "sampler": {
    "type": "const",
    "param": 1,
    "samplingServerURL": "http://127.0.0.1:5778/sampling"
  },
  "reporter": {
    "endpoint": "",
    "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"
  },
  "headers": {
    "TraceContextHeaderName": "",
    "jaegerDebugHeader": "",
    "jaegerBaggageHeader": "",
    "traceBaggageHeaderPrefix": ""
  }
}

Jaeger and Ingress Controller have been successfully installed and configured. It’s time to deploy our microservices!

Deploying a test application

Test application meow-micro consists of two microservices. Client meow-client – accepts a REST request and sends information to the service meow-server via GRPC.

Details on using REST and GRPC with GoLang:

The test application also contains several additional tools:

tracing.go

Gets parameters for configuring Jaeger from the environment where Helm runs for installation meow-client and meow-server.

cfg, err := config.FromEnv()
if err != nil {
panic(fmt.Sprintf("Could not parse Jaeger env vars: %s", err.Error())) 
}  
tracer, closer, err := cfg.NewTracer()
if err != nil {  
 panic(fmt.Sprintf("Could not initialize jaeger tracer: %s", err.Error())) 
}

client.go

Performs client configuration – sets the service name to trace and defines the span.

  • Span Is the basic element of distributed tracing. It is a description of a certain workflow (for example, a query to a database). Spans usually contain links to other spans, which allows multiple spans to be combined in a Trace.

  • Trace – visualization of the life of a request in the process of its movement in a distributed system.

Detailed information on concepts trace and span can be found in the official documentation.

// main function span
os.Setenv("JAEGER_SERVICE_NAME", "meow-client")
tracer, closer := tracing.Init()
defer closer.Close()
http.HandleFunc("https://habr.com/", func(w http.ResponseWriter, r *http.Request) {
spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := tracer.StartSpan("send-meow-communication", ext.RPCServerOption(spanCtx))
defer span.Finish()
...
// sleep function span
os.Setenv("JAEGER_SERVICE_NAME", "meow-client")
tracer, closer := tracing.Init()
defer closer.Close()
span := tracer.StartSpan("sleep")
defer span.Finish()

Installing the test application

Install Helm v3 can be used on any operating system, for example macOS using brew. We are now ready to deploy microservices on our cluster.

$ brew install helm
...
==> Downloading https://ghcr.io/v2/homebrew/core/helm/manifests/3.5.4
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/helm/blobs/sha256:5dac5803c1ad2db3a91b0928fc472aaf80a4
==> Downloading from https://pkg-containers-az.githubusercontent.com/ghcr1/blobs/sha256:5dac5803c1ad2db
######################################################################## 100.0%
==> Pouring helm--3.5.4.big_sur.bottle.tar.gz
...
$ helm version
version.BuildInfo{Version:"v3.3.4", GitCommit:"a61ce5633af99708171414353ed49547cf05013d", GitTreeState:"clean", GoVersion:"go1.14.9"}

Let’s use the Makefile from the repository to deploy the test application to the Kubernetes cluster.

# Build client and server from Dockerfile
$ make build
docker build -t meow-client:1.0 -f client/Dockerfile .
[+] Building 17.1s (10/10) FINISHED
...
docker build -t meow-server:1.0 -f server/Dockerfile .
[+] Building 0.5s (10/10) FINISHED
...
# Install Microservices into Kubernetes via Helm

$ make install
helm install -f helm/Values.yaml meow-micro ./helm
NAME: meow-micro
LAST DEPLOYED: Mon Apr 26 13:42:38 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

Let’s check that the Pods of both services are up and running.

$ kubectl get pods

NAME                           READY   STATUS    RESTARTS   AGE
jaeger-6f6b5d8689-s7cln        1/1     Running   0          26m
meow-client-8b974778c-85896    1/1     Running   0          15m
meow-server-56f559db44-5mvgp   1/1     Running   0          15m

Viewing trace data

Now comes the fun part! Let’s take a look at the tracing.

Open the Jaeger console by pointing your browser to the address http: // localhost: 8081

2. Let’s send a request to our application.

$ curl http://localhost/meow -X POST -d '{"name": "Meow-Mixer"}'
200 - Meow sent: Meow-Mixer

3. Refresh the browser page. From the Service menu choose – nginx

4. Press the button Find traces

5. All traces for nginx will be displayed. Each of them can be expanded to get more information.

6. Now you can track how long each request took.

And display additional information for each request.

Despite the fact that this small example gives an idea of ​​what distributed tracing is, you can see its full power only in real applications under load.

Other tracing systems

We’ve covered how to use Jaeger to trace requests. Ingress-Nginx… You can also use Zipkin or DataDog for these purposes.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *