Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kong Event Gateway — Kubernetes Deployment

A complete Kubernetes deployment for Kong Event Gateway using Kong Operator 2.2+ and Strimzi Kafka. Demonstrates multi-tenant Kafka governance for the Northwind Financial use case via SNI-based virtual cluster routing.

Overview

Northwind Financial is a mid-size financial services firm with two business units — Retail Banking NY and Wealth Management LA — sharing a single Kafka cluster. This deployment shows how Kong Event Gateway provides isolated, governed access to that shared cluster without touching broker configuration.

All components — the Event Gateway data plane, virtual clusters, listeners, and policies — are managed declaratively through Kong Operator CRDs. No static deployment YAML is required.

Northwind Financial Virtual Clusters

Business Unit Virtual Cluster Kafka Prefix Topics (logical names)
Retail Banking NY retail-banking-ny RETAIL_NY. payments.card-dispatch.v1 · markets.exchange-ticker.v1 · branches.commuter-foot-traffic.v1 · core.accounts.status.v2
Wealth Management LA wealth-management-la WEALTH_LA. advisor.daily-client-activity.v1 · portfolios.esg-allocation-adjustments.v1 · clients.sentiment-signals.v1 · infosec.security.fraud.risk-scores.v3

Topic prefixes are transparent to clients — Retail Banking connects to payments.card-dispatch.v1; the gateway silently maps it to RETAIL_NY.payments.card-dispatch.v1 on the broker.

Bootstrap Endpoints

Business Unit Bootstrap Address
Retail Banking NY bootstrap.retail-banking-ny.127-0-0-1.sslip.io:9092
Wealth Management LA bootstrap.wealth-management-la.127-0-0-1.sslip.io:9092

For non-minikube clusters replace 127-0-0-1 with your LoadBalancer IP in dotted-dash notation (e.g. 192-168-49-2 for 192.168.49.2).

🏗️ Architecture

graph TB
    subgraph host["Host Machine (minikube tunnel → 127.0.0.1)"]
        clientA["Kafka Client\nRetail Banking NY\nbootstrap.retail-banking-ny\n.127-0-0-1.sslip.io:9092"]
        clientB["Kafka Client\nWealth Management LA\nbootstrap.wealth-management-la\n.127-0-0-1.sslip.io:9092"]
    end

    subgraph konnect["Konnect (Control Plane)"]
        cp["KonnectEventGateway\ncp-event-gateway"]
    end

    subgraph k8s["Kubernetes Cluster"]
        subgraph certmgr["cert-manager"]
            cert["Certificate\nkeg-certificate\n→ keg-tls-secret"]
        end

        subgraph kongns["Namespace: kong"]
            gw["Kong Gateway\nGateway API · port 9092\nTLS Passthrough\n*.127-0-0-1.sslip.io"]

            tlsA["TLSRoute\nkeg-tls-retail-banking-ny\n*.retail-banking-ny.*"]
            tlsB["TLSRoute\nkeg-tls-wealth-management-la\n*.wealth-management-la.*"]

            keg["KegDataPlane\nkeg-dp / keg-dp-kafka:9092\nTLS termination · SNI routing"]

            subgraph kegconfig["KEG Configuration (Konnect-synced)"]
                bc["EventGatewayBackendCluster\nnorthwind-kafka-bootstrap:9092"]
                vcA["VirtualCluster\nretail-banking-ny\nprefix: RETAIL_NY."]
                vcB["VirtualCluster\nwealth-management-la\nprefix: WEALTH_LA."]
                listener["EventGatewayListener\nport 9092"]
                polTLS["ListenerPolicy\nsni-tls-server\nTLS termination"]
                polSNI["ListenerPolicy\nsni-forward\nSNI suffix routing"]
            end
        end

        subgraph kafkans["Namespace: kafka"]
            strimzi["Strimzi Operator\nv1.0.1+"]
            kafka["Kafka Cluster\nnorthwind · 3 brokers · KRaft\nKafka 4.2.0"]
            topics["KafkaTopics\nRETAIL_NY.* · WEALTH_LA.*"]
        end
    end

    clientA -->|"TLS :9092\nSNI: *.retail-banking-ny.*"| gw
    clientB -->|"TLS :9092\nSNI: *.wealth-management-la.*"| gw

    gw --> tlsA & tlsB
    tlsA & tlsB -->|"TLS passthrough"| keg

    keg -->|"SNI → retail-banking-ny"| vcA
    keg -->|"SNI → wealth-management-la"| vcB

    vcA & vcB --> bc
    bc -->|"PLAINTEXT\ninternal"| kafka

    cert -.->|"keg-tls-secret"| keg
    cp -.->|"operator sync"| keg
    cp -.->|"operator sync"| kegconfig
    strimzi -.->|"manages"| kafka
    strimzi -.->|"manages"| topics
Loading

Traffic Flow

  1. Client connects to bootstrap.retail-banking-ny.127-0-0-1.sslip.io:9092 with TLS — DNS resolves to 127.0.0.1 via sslip.io, minikube tunnel routes to the Kong Gateway LoadBalancer
  2. Kong Gateway matches the *.retail-banking-ny.127-0-0-1.sslip.io TLSRoute and forwards (TLS passthrough) to keg-dp-kafka:9092
  3. KEG terminates TLS, reads the SNI suffix (.127-0-0-1.sslip.io), and routes to the retail-banking-ny virtual cluster
  4. Client produces to payments.card-dispatch.v1 → KEG transparently writes to RETAIL_NY.payments.card-dispatch.v1 on the broker; the prefix is never visible to the client

📋 Prerequisites

  • Kubernetes cluster (1.24+) — tested with minikube
  • kubectl configured
  • helm 3.x
  • A Konnect account and Personal Access Token (PAT)
  • Gateway API standard CRDs v1.5.1 (installed in step 2)

⚠️ Minikube clock drift: If you leave minikube running while your computer sleeps, the system clock may drift and cause certificate validation failures. Run minikube stop && minikube start to reset.

🖥️ Minikube-Specific Setup

If you are using minikube for local development, configure it before starting.

Resource Allocation

This deployment requires approximately 3.5 CPU cores and 6 GB of memory. Start minikube with adequate resources:

minikube start --cpus=6 --memory=8g

To adjust an existing cluster:

minikube stop
minikube start --cpus=6 --memory=8g

Enable Metrics Server (optional)

minikube addons enable metrics-server

LoadBalancer Access

Run minikube tunnel in a separate terminal (keep it running for the entire session). This exposes LoadBalancer services on 127.0.0.1, making the sslip.io DNS names resolve correctly:

# Run in a separate terminal — requires sudo on some systems
sudo minikube tunnel -p <your-profile-name>

💻 Resource Requirements

Component CPU Request CPU Limit Memory Request Memory Limit Replicas
Kafka Brokers (Strimzi) 750m 1.2 cores 2.3 GB 3 GB 3
Kong Event Gateway (KEG) 500m 750m 512 MB 1 GB 1
Kong Gateway (Operator) 500m 1 core 512 MB 1.5 GB 1
Kong Operator 100m 400m 256 MB 512 MB 1
Strimzi Operator 100m 400m 256 MB 512 MB 1
cert-manager 100m 250m 128 MB 256 MB 1
Total ~3.3 cores ~5.7 cores ~6 GB ~9 GB

🚀 Deployment Steps

1. Create Namespaces

kubectl create namespace kong
kubectl create namespace kafka

2. Install Gateway API CRDs

The TLSRoute resource is GA and has been part of the Standard Channel since v1.5.0. :

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml

Do not use --server-side here. Kong Operator's Helm chart also installs Gateway API objects via server-side apply (field manager: helm). Using kubectl apply --server-side first registers kubectl as field manager on the same fields, causing a conflict when Helm runs. If you have already applied with --server-side, run this to transfer ownership before retrying the Helm install:

kubectl apply --server-side --force-conflicts --field-manager=helm \
  -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml

Wait for the CRDs to be established:

kubectl wait --for=condition=Established crd/gateways.gateway.networking.k8s.io --timeout=60s
kubectl wait --for=condition=Established crd/tlsroutes.gateway.networking.k8s.io --timeout=60s

3. Install cert-manager

cert-manager automates TLS certificate issuance and renewal for KEG.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml

Wait for cert-manager to be ready before proceeding:

kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=5m
kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=5m

4. Install Strimzi and Deploy Kafka

Strimzi manages the Kafka cluster declaratively. The operator version must match the Kafka version in kafka/kafka-cluster.yaml (Strimzi 1.0.1 + Kafka 4.2.0).

  1. Install the Strimzi cluster operator into the kafka namespace:

    kubectl create -f 'https://strimzi.io/install/latest?namespace=kafka' -n kafka
  2. Wait for the Strimzi operator to be ready:

    kubectl wait --for=condition=Available deployment/strimzi-cluster-operator -n kafka --timeout=5m
  3. Deploy the Northwind Kafka cluster (3-node KRaft, combined controller+broker):

    kubectl apply -f kafka/kafka-cluster.yaml
  4. Wait for all brokers to be ready (this takes 2–4 minutes on first run):

    kubectl wait kafka/northwind -n kafka \
      --for=condition=Ready \
      --timeout=10m
  5. Deploy the Northwind topic definitions (Strimzi Topic Operator creates them automatically):

    kubectl apply -f kafka/topics.yaml

    Verify topics were created:

    kubectl get kafkatopic -n kafka -l strimzi.io/cluster=northwind

5. Install Kong Operator

Kong Operator manages both the Kong Gateway data plane and the KEG data plane.

helm repo add kong https://charts.konghq.com
helm repo update

helm upgrade --install kong-operator kong/kong-operator \
  -n kong-system \
  --create-namespace \
  --set image.tag=2.2 \
  --set env.ENABLE_CONTROLLER_KONNECT=true \
  --set env.ENABLE_CONTROLLER_KEGDATAPLANE=true \
  --set global.webhooks.options.certManager.enabled=true

Wait for the operator to be ready:

kubectl wait --for=condition=Available deployment/kong-operator-kong-operator-controller-manager \
  -n kong-system --timeout=5m

cert-manager integration (recommended): To have cert-manager manage the operator's webhook certificates, we added --set global.webhooks.options.certManager.enabled=true to the Helm command above. Remove it if you are handling it manually.

6. Create Konnect Auth Secret

Create your Konnect Personal Access Token in the Konnect PAT page, then store it as a Kubernetes secret:

export KONNECT_TOKEN='<YOUR_KONNECT_PAT>'

echo 'apiVersion: v1
kind: Secret
metadata:
  name: konnect-api-auth-secret
  namespace: kong
  labels:
    konghq.com/credential: konnect
    konghq.com/secret: "true"
stringData:
  token: "'$KONNECT_TOKEN'"' | kubectl apply -f -

Apply the KonnectAPIAuthConfiguration that references this secret:

kubectl apply -f keg/konnect-auth.yaml

7. Set Up TLS Certificates

KEG terminates TLS for Kafka clients. A wildcard certificate covering both virtual cluster hostnames is required.

Option A: cert-manager (Recommended)

  1. Apply the self-signed ClusterIssuer:

    kubectl apply -f certificates/cluster-issuer.yaml
  2. Apply the wildcard Certificate (creates keg-tls-secret in the kong namespace):

    kubectl apply -f certificates/keg-certificate.yaml
  3. Wait for the certificate to be issued:

    kubectl wait certificate/keg-certificate -n kong \
      --for=condition=Ready \
      --timeout=5m

Option B: Manual with openssl

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=127-0-0-1.sslip.io" \
  -addext "subjectAltName=\
DNS:*.127-0-0-1.sslip.io,\
DNS:*.retail-banking-ny.127-0-0-1.sslip.io,\
DNS:*.wealth-management-la.127-0-0-1.sslip.io"

kubectl create secret tls keg-tls-secret \
  --cert=tls.crt --key=tls.key \
  -n kong

8. Deploy Kong Event Gateway (Operator CRDs)

Apply the KEG resources in order, waiting for each to be programmed in Konnect before proceeding.

Control plane:

kubectl apply -f keg/event-gateway-cp.yaml

kubectl wait konnecteventgateway/cp-event-gateway -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

Backend cluster (points KEG at the Strimzi Kafka bootstrap):

kubectl apply -f keg/backend-cluster.yaml

kubectl wait eventgatewaybackendcluster/default-backend-cluster -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

Virtual clusters (Retail Banking NY and Wealth Management LA with topic prefixing):

kubectl apply -f keg/virtual-clusters.yaml

kubectl wait eventgatewayvirtualcluster/retail-banking-ny -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

kubectl wait eventgatewayvirtualcluster/wealth-management-la -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

Listener (accepts Kafka client connections on port 9092):

kubectl apply -f keg/listener.yaml

kubectl wait eventgatewaylistener/sni-listener -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

Listener policies (TLS termination + SNI-based routing to virtual clusters):

kubectl apply -f keg/listener-policies.yaml

kubectl wait eventgatewaylistenerpolicy/sni-tls-server -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

kubectl wait eventgatewaylistenerpolicy/sni-forward -n kong \
  --for=condition=Programmed=True \
  --timeout=10m

KEG data plane (Kong Operator creates and manages the pod + ClusterIP service):

kubectl apply -f keg/keg-dataplane.yaml

Wait for the KEG pod to be running:

kubectl wait pod -n kong \
  --for=condition=Ready \
  --selector app.kubernetes.io/name=keg \
  --timeout=10m

Consume and produce policies (optional — adds tenant headers to records):

kubectl apply -f keg/consume-produce-policies.yaml

9. Deploy Kong Gateway with TLSRoute

Kong Operator provisions a managed Kong Gateway data plane. The GatewayClass and GatewayConfiguration tell the operator which image to use and how to connect to Konnect.

GatewayConfiguration and GatewayClass:

kubectl apply -f kong/gateway-config.yaml

kubectl wait gatewayclass/kong \
  --for=condition=Accepted=True \
  --timeout=5m

Gateway (TLS passthrough listener on port 9092, hostname *.127-0-0-1.sslip.io):

kubectl apply -f kong/gateway.yaml

kubectl wait gateway/kong-keg -n kong \
  --for=condition=Accepted=True \
  --timeout=10m

TLSRoutes (one per virtual cluster, pointing at keg-dp-kafka ClusterIP service):

kubectl apply -f kong/tlsroutes.yaml

Note: kubectl wait --for=condition=Accepted does not work for Gateway API route resources. TLSRoute (and all *Route types) store their status conditions nested under status.parents[].conditions[] — one entry per parent Gateway — not at the top-level status.conditions[] that kubectl wait inspects. Verify acceptance instead with:

kubectl get tlsroute -n kong -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range .status.parents[*]}  parent: {.parentRef.name}  accepted: {range .conditions[?(@.type=="Accepted")]}{.status}{"\n"}{end}{end}{end}'

Both routes should show accepted: True before proceeding.

10. Verify Everything is Running

# All KEG operator resources should be Programmed=True
kubectl get konnecteventgateway,eventgatewaybackendcluster,eventgatewayvirtualcluster,eventgatewaylistener,eventgatewaylistenerpolicy,kegdataplane -n kong

# Gateway and TLSRoutes
kubectl get gateway,tlsroute -n kong

# Kafka cluster and topics
kubectl get kafka,kafkatopic -n kafka

# All pods running
kubectl get pods -n kong
kubectl get pods -n kafka
kubectl get pods -n kong-system

11. Smoke Test

Run these commands on your host machine, not inside a Kubernetes pod. The 127-0-0-1.sslip.io DNS trick resolves to 127.0.0.1, which minikube tunnel binds to on the host. Inside a pod, 127.0.0.1 is the pod's own loopback and the connection will be refused.

Make sure minikube tunnel is running (see LoadBalancer Access).

Option A — kafkactl (recommended)

# Retail Banking NY
kafkactl --context retail-banking-ny get brokers

# Wealth Management LA
kafkactl --context wealth-management-la get brokers

# Or use the helper script
./scripts/kafkactl-helper.sh smoke-test

Option B — kcat on the host

# Install kcat locally if needed: brew install kcat

# Retail Banking NY
kcat -b bootstrap.retail-banking-ny.127-0-0-1.sslip.io:9092 \
  -X security.protocol=SSL \
  -X enable.ssl.certificate.verification=false \
  -L

# Wealth Management LA
kcat -b bootstrap.wealth-management-la.127-0-0-1.sslip.io:9092 \
  -X security.protocol=SSL \
  -X enable.ssl.certificate.verification=false \
  -L

Expected output — brokers advertised as broker-{0,1,2}.<cluster>.127-0-0-1.sslip.io:9092:

Metadata for all topics (from broker -1: bootstrap.retail-banking-ny.127-0-0-1.sslip.io:9092/bootstrap):
 3 brokers:
  broker 0 at broker-0.retail-banking-ny.127-0-0-1.sslip.io:9092
  broker 1 at broker-1.retail-banking-ny.127-0-0-1.sslip.io:9092
  broker 2 at broker-2.retail-banking-ny.127-0-0-1.sslip.io:9092 (controller)

Cleanup

./scripts/cleanup.sh

📁 Repository Structure

kong-event-gw-kubernetes/
├── kafka/
│   ├── kafka-cluster.yaml         # Strimzi KafkaNodePool + Kafka (northwind cluster)
│   └── topics.yaml                # KafkaTopic resources for all Northwind topics
├── keg/                           # Kong Event Gateway operator CRDs (all in kong namespace)
│   ├── konnect-auth.yaml          # KonnectAPIAuthConfiguration (secretRef)
│   ├── event-gateway-cp.yaml      # KonnectEventGateway control plane
│   ├── backend-cluster.yaml       # EventGatewayBackendCluster → northwind Kafka
│   ├── virtual-clusters.yaml      # EventGatewayVirtualCluster (retail-banking-ny, wealth-management-la)
│   ├── listener.yaml              # EventGatewayListener (port 9092)
│   ├── listener-policies.yaml     # TLS server termination + SNI forwarding policies
│   ├── keg-dataplane.yaml         # KegDataPlane (ClusterIP; operator manages pod lifecycle)
│   └── consume-produce-policies.yaml  # x-kong-tenant header policies per virtual cluster
├── kong/                          # Kong Operator Gateway resources
│   ├── gateway-config.yaml        # GatewayConfiguration + GatewayClass (gateway-operator)
│   ├── gateway.yaml               # Gateway (TLS passthrough · *.127-0-0-1.sslip.io:9092)
│   └── tlsroutes.yaml             # TLSRoute per virtual cluster → keg-dp-kafka
├── certificates/
│   ├── cluster-issuer.yaml        # cert-manager ClusterIssuer (self-signed)
│   └── keg-certificate.yaml       # Certificate (wildcard, creates keg-tls-secret in kong ns)
├── examples/
│   ├── kafka-client-configs/
│   │   ├── retail-banking-ny-client.properties
│   │   └── wealth-management-la-client.properties
│   └── test-commands.md           # Detailed testing, isolation verification, diagnostics
└── scripts/
    ├── cleanup.sh                 # Full teardown
    ├── generate-wildcard-cert.sh  # Manual cert generation (alternative to cert-manager)
    └── kafkactl-helper.sh         # Kafka admin helper

⚙️ Configuration Reference

Virtual Cluster and Topic Mapping

Virtual Cluster dnsLabel Kafka Prefix Example mapping
retail-banking-ny retail-banking-ny RETAIL_NY. client: payments.card-dispatch.v1 → broker: RETAIL_NY.payments.card-dispatch.v1
wealth-management-la wealth-management-la WEALTH_LA. client: clients.sentiment-signals.v1 → broker: WEALTH_LA.clients.sentiment-signals.v1

SNI Routing

The sni-forward listener policy (in keg/listener-policies.yaml) routes by hostname suffix:

Hostname pattern Routes to
*.retail-banking-ny.127-0-0-1.sslip.io retail-banking-ny virtual cluster
*.wealth-management-la.127-0-0-1.sslip.io wealth-management-la virtual cluster

Using a Different IP

For a non-minikube cluster, replace 127-0-0-1 with your LoadBalancer IP in dotted-dash form. Example for IP 10.0.0.50:

  1. kong/gateway.yamlhostname: "*.10-0-0-50.sslip.io"
  2. keg/listener-policies.yamlsniSuffix: ".10-0-0-50.sslip.io"
  3. certificates/keg-certificate.yaml — update dnsNames entries
  4. examples/kafka-client-configs/*.properties — update bootstrap.servers

Component Versions

Component Version
Kong Operator 2.2+
Kong Gateway 3.14
Strimzi 1.0.1
Kafka 4.2.0
Gateway API CRDs 1.5.0 (standard)
cert-manager 1.18.2

🔗 Related Documentation

📄 License

Apache License 2.0 — see LICENSE.

About

Kubernetes-ready deployment for Kong Event Gatewawy (KEG) with multi-tenant Kafka routing, TLS termination, and Kong Gateway integration

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages