Skip to main content

Testing Kafka in a Go API with OpenTelemetry and Tracetest

Tracetest is a testing tool based on OpenTelemetry that allows you to test your distributed application. It allows you to use data from distributed traces generated by OpenTelemetry to validate and assert if your application has the desired behavior defined by your test definitions.

Sample Go APIs with OpenTelemetry Collector, Jaeger and Tracetest

This is a simple quick start on how to configure two Go APIs to communicate via Kafka. They use OpenTelemetry instrumentation with traces, Jaeger as a trace data store, and Tracetest for enhancing your E2E and integration tests with trace-based testing.

Prerequisites

You will need Docker and Docker Compose installed on your machine to run this quick start app!

Project Structure

The project is built with Docker Compose. It contains one docker-compose.yaml file that configures Tracetest, Jaeger, OpenTelemetry Collector, and both Go apps.

1. Go APIs

The docker-compose.yaml file references two Go apps, and one Kafka stream.

  • Producer API: Builds the ./producer-api/Dockerfile
  • Consumer Worker: Builds the ./consumer-worker/Dockerfile

The producer publishes a message to Kafka when receiving a POST request to the /publish endpoint.

The consumer accepts a message through Kafka.

2. Tracetest

The collector.config.yaml, tracetest-provision.yaml, and tracetest-config.yaml in the tracetest directory are for the setting up Tracetest and the OpenTelemetry Collector.

Docker Compose Network

All services in the docker-compose.yaml are on the same network and will be reachable by hostname from within other services. E.g. jaeger:4317 in the collector.config.yaml will map to the jaeger service, where the port 4317 is the port where Jaeger accepts traces.

Go Producer API

The Go API is a simple HTTP server, contained in the main.go file.

The OpenTelemetry tracing is contained in the ./telemetry/telemetry.go file. Traces will be sent to the OpenTelemetry Collector, and forwarded to Jaeger.

Traces will be sent to either the grpc endpoint. The hostname and port as seen in the env section of the producer-api in the docker-compose.yaml is:

  • OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317

The server starts by running the main.go file.

As you can see in the Dockerfile.

FROM golang:alpine as builder
ENV GO111MODULE=on
RUN apk update && apk add --no-cache git

WORKDIR /app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/main .

FROM scratch
COPY --from=builder /app/bin/main .
CMD ["./main"]

And, the docker-compose.yaml contains one service for the Go Producer API.

  producer-api:
image: quick-start-producer-api
platform: linux/amd64
build: ./producer-api
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 8080:8080
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317
- OTEL_SERVICE_NAME=producer-api
- KAFKA_BROKER_URL=kafka:9092
- KAFKA_TOPIC=messaging
depends_on:
otel-collector:
condition: service_started
kafka:
condition: service_healthy

Go Consumer Worker

The Go API is a simple HTTP server, contained in the main.go file.

The OpenTelemetry tracing is contained in the ./telemetry/telemetry.go file. Traces will be sent to the OpenTelemetry Collector, and forwarded to Jaeger.

Traces will be sent to either the grpc endpoint. The hostname and port as seen in the env section of the producer-api in the docker-compose.yaml is:

  • OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317

The server starts by running the main.go file.

As you can see in the Dockerfile.

FROM golang:alpine as builder
ENV GO111MODULE=on
RUN apk update && apk add --no-cache git

WORKDIR /app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/main .

FROM scratch
COPY --from=builder /app/bin/main .
CMD ["./main"]

And, the docker-compose.yaml contains one service for the Go Producer API.

  producer-api:
image: quick-start-producer-api
platform: linux/amd64
build: ./producer-api
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 8080:8080
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317
- OTEL_SERVICE_NAME=producer-api
- KAFKA_BROKER_URL=kafka:9092
- KAFKA_TOPIC=messaging
depends_on:
otel-collector:
condition: service_started
kafka:
condition: service_healthy

Tracetest

The docker-compose.yaml configures four services more including Tracetest.

  • Postgres - Postgres is a prerequisite for Tracetest to work. It stores trace data when running the trace-based tests.
  • OpenTelemetry Collector - A vendor-agnostic implementation of how to receive, process and export telemetry data.
  • Jaeger - Open source, distributed tracing platform. Monitor and troubleshoot workflows in complex distributed systems.
  • Tracetest - Trace-based testing that generates end-to-end tests automatically from traces.
  tracetest:
image: kubeshop/tracetest:${TAG:-latest}
platform: linux/amd64
volumes:
- type: bind
source: ./tracetest/tracetest-config.yaml
target: /app/tracetest.yaml
- type: bind
source: ./tracetest/tracetest-provision.yaml
target: /app/provisioning.yaml
ports:
- 11633:11633
command: --provisioning-file /app/provisioning.yaml
depends_on:
postgres:
condition: service_healthy
otel-collector:
condition: service_started
jaeger:
condition: service_started
healthcheck:
test: ["CMD", "wget", "--spider", "localhost:11633"]
interval: 1s
timeout: 3s
retries: 60
environment:
TRACETEST_DEV: ${TRACETEST_DEV}

postgres:
image: postgres:14
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
healthcheck:
test: pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
interval: 1s
timeout: 5s
retries: 60
ports:
- 5432:5432

otel-collector:
image: otel/opentelemetry-collector-contrib:0.59.0
command:
- "--config"
- "/otel-local-config.yaml"
volumes:
- ./tracetest/collector.config.yaml:/otel-local-config.yaml
ports:
- 4317:4317
depends_on:
jaeger:
condition: service_started

jaeger:
image: jaegertracing/all-in-one:latest
restart: unless-stopped
ports:
- 16686:16686
- 16685:16685
environment:
- COLLECTOR_OTLP_ENABLED=true
healthcheck:
test: ["CMD", "wget", "--spider", "localhost:16686"]
interval: 1s
timeout: 3s
retries: 60

Tracetest depends on all three services, Postgres, Jaeger, and the OpenTelemetry Collector. Both Tracetest and the OpenTelemetry Collector require config files to be loaded via a volume. The volumes are mapped from the root directory into the tracetest directory and the respective config files.

The tracetest-config.yaml file contains the basic setup of connecting Tracetest to the Postgres instance.

postgres:
host: postgres
user: postgres
password: postgres
port: 5432
dbname: postgres
params: sslmode=disable

The tracetest-provision.yaml file provisions the trace data store and polling to store in the Postgres database. The data store is set to OTLP, meaning the traces will be stored in Tracetest itself.

---
type: PollingProfile
spec:
name: Default
strategy: periodic
default: true
periodic:
retryDelay: 5s
timeout: 10m

---
type: DataStore
spec:
name: Jaeger
type: jaeger
default: true
jaeger:
endpoint: jaeger:16685
tls:
insecure: true

But how are traces sent to Tracetest?

The collector.config.yaml explains that. It receives traces via the grpc or http. Then, exports them to Jaeger's OLTP endpoint jaeger:4317.

receivers:
otlp:
protocols:
grpc:
http:

processors:
batch:
timeout: 100ms

exporters:
logging:
loglevel: debug
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true

service:
pipelines:
traces/1:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]

Run Both the Go Apps and Tracetest

To start both Go apps and Tracetest we will run this command:

docker-compose up # add --build if the images are not built already

This will start your Tracetest instance on http://localhost:11633/.

Open the URL and start creating tests! Make sure to use the http://producer-api:8080/publish URL in your test creation, because your Go app and Tracetest are in the same network.

Here are two sample tests you can get started with quickly.

Learn More

Feel free to check out our examples in GitHub and join our Slack Community for more info!