Alex
Alex Passionate about inclusivity, sustainability, cycling and health. Software problems solver, aspiring writer. My opinions are my own. He/him

Practical OpenTelemetry part 5: Test drive Python, Go, Java with a single command

Practical OpenTelemetry part 5: Test drive Python, Go, Java with a single command

In part 5 of my Practical OpenTelemetry series, I’ve decided to take a bit of time to make launching the applications from the previous parts easier. This article will go over:

  • containerizing each service written so far
  • creating a docker-compose file for all the previous services
  • launching the services and seeing the results

The nice thing about this article, is that if all you want is to use a single docker compose command to launch an application and see OpenTelemetry in Go, Python and Java in action, you now can.

Requirements

If you’re only interested in seeing the results, run the following to clone the repository and launch the example using compose.

1
2
git clone https://github.com/codeboten/practical-otel.git && cd practical-otel
docker compose up

If the previous docker compose command failed, you may be using an older version of compose, in which case, the command may be docker-compose. Open http://localhost:9411 or http://localhost:16686 and you should be able to see traces right away! Let’s figure out how all the in this example works. First, I dockerized the applications from previous examples.

Containerizing services

Containerizing in context of this article refers to taking the application code and packaging it as a Docker container. In order to containerize the applications, I created separate Dockerfile configuration for each part of this series.

Part 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM python:slim

RUN pip install \
  flask \
  requests

RUN pip install \
  opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-jaeger

RUN pip install \
  opentelemetry-instrumentation-flask \
  opentelemetry-instrumentation-requests

WORKDIR /app
ADD grocery_store.py ./
ADD shopper.py ./

CMD ["python", "grocery_store.py"]

Part 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#
# Build stage
#
FROM golang:1.16
WORKDIR /go/src/github.com/codeboten/practical-otel/part2
COPY go.* .
COPY inventory.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o inventory .

#
# Package stage
#
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/codeboten/practical-otel/part2/inventory .
CMD ["./inventory"]

Part 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
COPY Checkout/src /home/app/src
COPY Checkout/pom.xml /home/app
RUN curl -L https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.3.1/opentelemetry-javaagent-all.jar -o /home/app/opentelemetry-javaagent-all.jar
RUN mvn -f /home/app/pom.xml clean package

#
# Package stage
#
FROM openjdk:11-jre-slim
COPY --from=build /home/app/target/Checkout-0.0.1-SNAPSHOT.jar /usr/local/lib/Checkout.jar
COPY --from=build /home/app/opentelemetry-javaagent-all.jar /usr/local/lib/
EXPOSE 8080
ENTRYPOINT ["java", \
     "-javaagent:/usr/local/lib/opentelemetry-javaagent-all.jar", \
     "-Dserver.host=0.0.0.0", \
     "-Dserver.port=8083", \
     "-Dspring.redis.host=redis", \
     "-Dotel.resource.attributes=service.name=checkout", \
     "-Dotel.metrics.exporter=none", \
     "-jar", "/usr/local/lib/Checkout.jar" \
]

Once we have Dockerfiles, we can build each application individually to create a container. We won’t in this example, as we’ll use compose to build all the applications simultaneously shortly.

Part 4

The configuration for the collector from Part 4 needs to be updated slightly to ensure that the collector can send data to the backends. If you remember, in Part 4, we exported the traffic to localhost, but because we’re now running the network using a Docker environment, the destination for the backends will need updating. Below you’ll find the new collector configuration needed, it’s the same as it was in Part 4, with new hosts configured for jaeger and zipkin exporters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
receivers:
  jaeger:
    protocols:
      thrift_compact:
      grpc:
exporters:
  jaeger:
    endpoint: jaeger:14250
    insecure: true
  zipkin:
    endpoint: http://zipkin:9411/api/v2/spans
    format: proto
  logging:
    loglevel: debug

service:
  pipelines:
    traces:
      receivers: [jaeger]
      exporters: [zipkin, jaeger, logging]

Compose

To tie it all together, the following Docker compose configuration file configures the shopper, store, inventory and checkout applications. It uses OpenTelemetry environment variables defined in the specification to set the host and port to connect and send data to the collector. Additionally, this compose file also configures the collector, zipkin, jaeger and redis instances needed for all the examples to work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
version: "3.9"
services:
  shopper:
    build: part1/
    command: "/app/shopper.py"
    depends_on:
      - collector
      - store
    environment:
      OTEL_EXPORTER_JAEGER_AGENT_HOST: collector
      STORE_URL: http://store:5000
  store:
    build: part1/
    depends_on:
      - collector
      - inventory
    environment:
      OTEL_EXPORTER_JAEGER_AGENT_HOST: collector
      INVENTORY_SERVICE_URL: http://inventory:8080/inventory
      CHECKOUT_SERVICE_URL: http://checkout:8083/orders
  inventory:
    build: part2/
    depends_on:
      - collector
      - checkout
    environment:
      OTEL_EXPORTER_JAEGER_AGENT_HOST: collector
      OTEL_EXPORTER_JAEGER_AGENT_PORT: 6831
  checkout:
    build: part3/
    depends_on:
      - collector
      - redis
    environment:
      OTEL_EXPORTER_JAEGER_ENDPOINT: http://collector:14250
      OTEL_TRACES_EXPORTER: jaeger
  collector:
    image: otel/opentelemetry-collector:0.27.0
    volumes:
      - ${PWD}/part4/collector-compose-example.yaml:/etc/collector.yaml
    command:
      - "--config=/etc/collector.yaml"
    depends_on:
      - jaeger
      - zipkin
  # tracing backends
  jaeger:
    image: jaegertracing/all-in-one
    ports:
      - "0.0.0.0:16686:16686"
  zipkin:
    image: openzipkin/zipkin
    ports:
      - "0.0.0.0:9411:9411"
  # data store
  redis:
    image: redis

Gotchas

One thing that’s worth highlighting is that the jaeger port varies depending on which application is instrumented. This is because currently, different languages in OpenTelemetry have implemented different protocols for Jaeger, and depending on which protocol is supported, a different port is used.

Time to get the services running! Run docker compose from the root of the repository

1
docker-compose up

This command takes a few minutes the first time it is run as Docker is downloading images and building the application containers. Once all the services are up and running, visit http://localhost:9411 or http://localhost:16686 and search for traces! If you’re interested in the previous chapters of this series, check them out here:

As always, feel free to let me know if this has been helpful in your journey to using OpenTelemetry!


Photo by Antoine Petitteville on Unsplash