Set up observability for Kong Event Gateway

TL;DR

Export metrics and traces from Kong Event Gateway into your own observability systems using OpenTelemetry (OTEL), which helps you understand how Event Gateway functions and how to troubleshoot it when something goes wrong.

In this tutorial, we’re using Jaeger and Prometheus, but you can substitute your own preferred tools as well.

Prerequisites

Install kafkactl. You’ll need it to interact with Kafka clusters.

Start a Docker Compose cluster with multiple Kafka services.

First, we need to create a docker-compose.yaml file. This file will define the services we want to run in our local environment:

cat <<EOF > docker-compose.yaml
name: kafka_cluster

networks:
  kafka:
    name: kafka_event_gateway

services:
  kafka1:
    image: apache/kafka:4.1.1
    networks:
      - kafka
    container_name: kafka1
    ports:
      - "9094:9094"
    environment:
      KAFKA_NODE_ID: 0
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENERS: INTERNAL://kafka1:9092,CONTROLLER://kafka1:9093,EXTERNAL://0.0.0.0:9094
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:9092,EXTERNAL://localhost:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_CONTROLLER_QUORUM_VOTERS: 0@kafka1:9093,1@kafka2:9093,2@kafka3:9093
      KAFKA_CLUSTER_ID: 'abcdefghijklmnopqrstuv'
      KAFKA_LOG_DIRS: /tmp/kraft-combined-logs

  kafka2:
    image: apache/kafka:4.1.1
    networks:
      - kafka
    container_name: kafka2
    ports:
      - "9095:9095"
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENERS: INTERNAL://kafka2:9092,CONTROLLER://kafka2:9093,EXTERNAL://0.0.0.0:9095
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka2:9092,EXTERNAL://localhost:9095
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_CONTROLLER_QUORUM_VOTERS: 0@kafka1:9093,1@kafka2:9093,2@kafka3:9093
      KAFKA_CLUSTER_ID: 'abcdefghijklmnopqrstuv'
      KAFKA_LOG_DIRS: /tmp/kraft-combined-logs

  kafka3:
    image: apache/kafka:4.1.1
    networks:
      - kafka
    container_name: kafka3
    ports:
      - "9096:9096"
    environment:
      KAFKA_NODE_ID: 2
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_LISTENERS: INTERNAL://kafka3:9092,CONTROLLER://kafka3:9093,EXTERNAL://0.0.0.0:9096
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka3:9092,EXTERNAL://localhost:9096
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_CONTROLLER_QUORUM_VOTERS: 0@kafka1:9093,1@kafka2:9093,2@kafka3:9093
      KAFKA_CLUSTER_ID: 'abcdefghijklmnopqrstuv'
      KAFKA_LOG_DIRS: /tmp/kraft-combined-logs
EOF

Now, let’s start the local setup:

docker compose up -d

If you don’t have a Konnect account, you can get started quickly with our onboarding wizard.

  1. The following Konnect items are required to complete this tutorial:
    • Personal access token (PAT): Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
  2. Set the personal access token as an environment variable:

    export KONNECT_TOKEN='YOUR KONNECT TOKEN'
    

In this guide, you’ll configure:

  • An OpenTelemetry (OTEL) collector to receive data from Event Gateway and send it to the observability systems.
  • Jaeger for visualizing traces from Event Gateway.
  • Prometheus for visualizing and querying metrics from Event Gateway.

Here’s how it works:

 
flowchart LR

    A[Traces]
    B[Metrics]
    C[OTLP
    receiver]
    D[Prometheus
    receiver]
    E[Tracing system]
    F[Metrics system]

    subgraph id1 [Event Gateway]
    A
    B
    end

    A --push traces --> C
    B --scrape metrics--> D

    subgraph id2 [OTEL Collector]
    C
    D
    end

    C -- Export traces --> E
    D -- Export metrics --> F
  

In this setup:

  1. Event Gateway generates traces and metrics.
  2. Using the OTEL collector:
    • The OTLP receiver gathers traces generated by Event Gateway.
    • The Prometheus receiver ingests the Prometheus-compatible metrics directly from Event Gateway.
  3. We export the data to our Prometheus instance using the OTLP/HTTP exporter, which sends metrics directly to Prometheus’ OTLP endpoint.

Create an Event Gateway control plane and data plane

Run the quickstart script to automatically provision a demo Event Gateway control plane and data plane, and configure your environment for sending metrics and traces:

curl -Ls https://get.konghq.com/event-gateway | bash -s -- \
  -k $KONNECT_TOKEN \
  -N kafka_event_gateway \
  -e "KEG__OBSERVABILITY__OTLP__TRACING__ENABLED=true" \
  -e "KEG__RUNTIME__HEALTH_LISTENER_ADDRESS_PORT=0.0.0.0:8080" \
  -e "OTEL_EXPORTER_OTLP_PROTOCOL=grpc" \
  -e "OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317" \
  -e "OTEL_EXPORTER_OTLP_TIMEOUT=10s" \
  -e "OTEL_SERVICE_NAME=eventgw"

Where you configure the following custom telemetry settings:

Parameter

Default

New value

Description

KEG__OBSERVABILITY__OTLP__TRACING__ENABLED false true Determines whether to turn on OTLP tracing.
KEG__RUNTIME__HEALTH_LISTENER_ADDRESS_PORT localhost:8080 0.0.0.0:8080 Determines the address and port for the health listener where metrics are exposed for scraping.
OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf grpc Protocol used to export OpenTelemetry data.
OTEL_EXPORTER_OTLP_ENDPOINT https://localhost:4317 http://otel-collector:4317 Endpoint to send OpenTelemetry data. In most cases, this will be an OTEL collector URL.
OTEL_EXPORTER_OTLP_TIMEOUT 10s 10s Max waiting time for the backend to process each metrics batch. We’re not adjusting this for the tutorial, but you can adjust as needed for troubleshooting.
OTEL_SERVICE_NAME none eventgw Name of the OTEL service identified in the observability tools. For example, in Jaeger, the service will appear as eventgw.

This sets up an Event Gateway control plane named event-gateway-quickstart, provisions a local data plane, and prints out the following environment variable export:

export EVENT_GATEWAY_ID=your-gateway-id

Copy and paste this into your terminal to configure your session.

This quickstart script is meant for demo purposes only, therefore it runs locally with most default parameters and a small number of exposed ports. If you want to run Kong Gateway as a part of a production-ready platform, set up your control plane and data planes through the Konnect UI, or using Terraform.

Configure the OTEL collector and Prometheus

Next, we need to create two configuration files: one for the OTEL collector, and one for Prometheus.

Open a new terminal window and create a directory for the configuration:

mkdir otel && cd otel

Create an OTEL collector configuration file:

cat <<EOF > otel-collector-config.yaml
receivers:
  otlp/keg:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
  prometheus/keg:
    config:
      scrape_configs:
        - job_name: kong
          scrape_interval: 5s
          static_configs:
            - targets: ['event-gateway-quickstart:8080']

processors:
  batch: {}

extensions:
  health_check: {}

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  otlphttp/prometheus:
    endpoint: http://prometheus:9090/api/v1/otlp
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp/keg]
      processors: [batch]
      exporters: [otlp/jaeger]
    metrics:
      receivers: [prometheus/keg]
      processors: [batch]
      exporters: [otlphttp/prometheus]
EOF

Create a Prometheus configuration file:

cat <<EOF > prometheus.yaml
global:
  scrape_interval: 5s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
EOF

Launch OTEL collector stack

Now, let’s run the OTEL collector stack with Jaeger and Prometheus.

Run the following command to save a Docker compose file, making sure that it communicates on the same network as your Kafka cluster and your Event Gateway data plane:

cat <<EOF > docker-compose.yaml

name: otel-stack
networks:
  kafka:
    name: kafka_event_gateway
    external: true

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    networks:
      - kafka
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"  # OTLP gRPC receiver
      - "18639:9090" # Use this for testing the scraping endpoint
  
  jaeger:
    image: jaegertracing/all-in-one:latest
    networks:
      - kafka
    ports:
      - "6831:6831/udp" # UDP port for Jaeger agent
      - "16686:16686"   # Web UI
      - "14268:14268"   # HTTP port for spans

  prometheus:
    image: prom/prometheus:latest
    networks:
      - kafka
    command:
      - '--config.file=/etc/prometheus/prometheus.yaml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.enable-otlp-receiver'   # Enables Prometheus to receive metrics using OTLP
    volumes:
      - ./prometheus.yaml:/etc/prometheus/prometheus.yaml
    ports:
      - "9090:9090"
EOF

Launch the OTEL stack:

docker compose up -d

Add Kafka configuration

Use the following Kafka configuration to access your Kafka resources from the virtual clusters:

cat <<EOF > kafkactl.yaml
contexts:
  direct:
    brokers:
      - localhost:9095
      - localhost:9096
      - localhost:9094
  vc:
    brokers:
      - localhost:19092
EOF

Create a backend cluster

Use the following command to create a backend cluster that connects to the Kafka servers you set up:

BACKEND_CLUSTER_ID=$(curl -X POST "https://us.api.konghq.com/v1/event-gateways/$EVENT_GATEWAY_ID/backend-clusters" \
     --no-progress-meter --fail-with-body  \
     -H "Authorization: Bearer $KONNECT_TOKEN" \
     --json '{
       "name": "backend_cluster",
       "bootstrap_servers": [
         "kafka1:9092",
         "kafka2:9092",
         "kafka3:9092"
       ],
       "authentication": {
         "type": "anonymous"
       },
       "tls": {
         "enabled": false
       },
       "insecure_allow_anonymous_virtual_cluster_auth": true
     }' | jq -r ".id"
)

In this example configuration:

  • bootstrap_servers: Points the backend cluster to the three bootstrap servers that we launched in the prerequisites.
  • authentication and insecure_allow_anonymous_virtual_cluster_auth: For demo purposes, we’re allowing insecure anonymous connections, which means no authentication required.
  • tls: TLS is disabled so that we can easily test the connection.

Add a virtual cluster

Run the following command to create a new virtual cluster associated with our backend cluster. This will let you route event traffic and apply policies:

VIRTUAL_CLUSTER_ID=$(curl -X POST "https://us.api.konghq.com/v1/event-gateways/$EVENT_GATEWAY_ID/virtual-clusters" \
     --no-progress-meter --fail-with-body  \
     -H "Authorization: Bearer $KONNECT_TOKEN" \
     --json '{
       "name": "example_virtual_cluster",
       "destination": {
         "id": "'$BACKEND_CLUSTER_ID'"
       },
       "dns_label": "vcluster-1",
       "authentication": [
         {
           "type": "anonymous"
         }
       ],
       "acl_mode": "passthrough"
     }' | jq -r ".id"
)

In this example:

  • authentication: Allows anonymous authentication.
  • acl_mode: The setting passthrough means that all clients are allowed and don’t have to match a defined ACL. In a production environment, you would set this to enforce_on_gateway and define an ACL policy.
  • name is an internal name for the configuration object, while the dns_label is necessary for SNI routing.

Add a listener and policy

For testing purposes, we’ll use port forwarding to route traffic to the virtual cluster.
In production environments, you should use SNI routing instead.

Run the following command to create a new listener:

LISTENER_ID=$(curl -X POST "https://us.api.konghq.com/v1/event-gateways/$EVENT_GATEWAY_ID/listeners" \
     --no-progress-meter --fail-with-body  \
     -H "Authorization: Bearer $KONNECT_TOKEN" \
     --json '{
       "name": "example_listener",
       "addresses": [
         "0.0.0.0"
       ],
       "ports": [
         "19092-19095"
       ]
     }' | jq -r ".id"
)

Create the port mapping policy:

curl -X POST "https://us.api.konghq.com/v1/event-gateways/$EVENT_GATEWAY_ID/listeners/$LISTENER_ID/policies" \
     --no-progress-meter --fail-with-body  \
     -H "Authorization: Bearer $KONNECT_TOKEN" \
     --json '{
       "type": "forward_to_virtual_cluster",
       "name": "forward",
       "config": {
         "type": "port_mapping",
         "advertised_host": "localhost",
         "destination": {
           "id": "'$VIRTUAL_CLUSTER_ID'"
         }
       }
     }'

Validate the cluster

Create a topic using the direct context, which is a direct connection to our Kafka cluster:

kafkactl -C kafkactl.yaml --context direct create topic my-test-topic

Then produce a message through the virtual cluster:

kafkactl -C kafkactl.yaml --context vc produce my-test-topic --value="test message"

You should see the following responses:

topic created: my-test-topic
message produced (partition=0	offset=0)

View metrics in Prometheus

Now that we’ve configured the OTEL collector to scrape the metrics endpoint and send them to Prometheus, let’s look at what data is available in Prometheus. If we go to the query tab, we will see the metrics populated:

  1. In your browser, open the Prometheus dashboard at http://localhost:9090/.
  2. Search for kong to see the list of available metrics.
  3. Let’s look at a sample metric: kong_keg_kafka_backend_roundtrip_duration_seconds_sum.

This tells you how long it took for the Event Gateway to send a request to the backend cluster and receive a response.

View traces in Jaeger

Let’s go to Jaeger to see we can see the full trace generated by Event Gateway.

  1. Send any command through the virtual cluster, such as list topics:

    kafkactl -C kafkactl.yaml --context vc list topics
    
  2. In your browser, open the Jaeger search dashboard at http://localhost:16686/search.
  3. Select the eventgw service.
  4. Click Find Traces.

Here you can see the full trace generated by Event Gateway for each command. For example, you can click an eventgw: request trace to see the details, including information about the virtual cluster and backend cluster.

FAQs

You can find the list of all available metrics in the metrics reference.

Something wrong?

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!