Careful!

You are browsing documentation for the next version of Kuma. Use this version at your own risk.

Add a builtin gateway

To get traffic from outside your mesh inside it (North/South) with Kuma you can use a builtin gateway.

In the quickstart, traffic was only able to get in the mesh by port-forwarding to an instance of an app inside the mesh. In production, you typically set up a gateway to receive traffic external to the mesh. In this guide you will add a built-in gateway in front of the demo-app service and expose it publicly.

 
---
title: service graph of the demo app with a builtin gateway on front
---
flowchart LR
  subgraph edge-gateway
    gw0(/ :8080)
  end
  demo-app(demo-app :5050)
  kv(`kv` :5050)
  gw0 --> demo-app 
  demo-app --> kv
  

Prerequisites

  • Completed quickstart to set up a zone control plane with demo application

If you are already familiar with quickstart you can set up required environment by running:

helm upgrade \
  --install \
  --create-namespace \
  --namespace kuma-system \
  kuma kuma/kuma
kubectl wait -n kuma-system --for=condition=ready pod --selector=app=-control-plane --timeout=90s
kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/refs/heads/main/k8s/001-with-mtls.yaml

Start a gateway

Create a MeshGatewayInstance

A MeshGatewayInstance configures the pods that will run the gateway.

Create it by running:

kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/refs/heads/main/kustomize/overlays/002-with-gateway/mesh-gateway-instance.yaml

The Kubernetes cluster needs to support LoadBalancer for this to work.

If you are running minikube you will want to open a tunnel with minikube tunnel -p mesh-zone.

You may not have support for LoadBalancer if you are running locally with kind or k3d. When running kind cluster you can try kubernetes-sigs/cloud-provider-kind.

Define a listener using MeshGateway

MeshGateway defines listeners for the gateway.

Define a single http listener on port 8080:

kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/refs/heads/main/kustomize/overlays/002-with-gateway/mesh-gateway.yaml

Notice how the selector selects the kuma.io/service tag of the previously defined MeshGatewayInstance.

Now look at the pods running in the namespace by running:

kubectl get pods -n kuma-demo

Observe the gateway pod:

NAME                            READY   STATUS    RESTARTS   AGE
redis-5fdb98848c-5tw62          2/2     Running   0          5m5s
demo-app-c7cd6588b-rtwlj        2/2     Running   0          5m5s
edge-gateway-66c76fd477-ncsp5   1/1     Running   0          18s

Retrieve the public url for the gateway with:

export PROXY_IP=$(kubectl get svc -n kuma-demo edge-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $PROXY_IP

Check the gateway is running:

curl -v ${PROXY_IP}:8080

Which outputs:

*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET / HTTP/1.1ó
> Host: 127.0.0.1:8080
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< content-length: 62
< content-type: text/plain
< vary: Accept-Encoding
< date: Fri, 09 Feb 2024 10:07:26 GMT
< server: Kuma Gateway
<
This is a Kuma MeshGateway. No routes match this MeshGateway!

Notice the gateway says that there are no routes configured.

Define a route using MeshHTTPRoute

MeshHTTPRoute defines HTTP routes inside your service mesh. Attach a route to an entire gateway or to a single listener by using targetRef.kind: MeshGateway

kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/refs/heads/main/kustomize/overlays/002-with-gateway/mesh-http-route.yaml

Now try to reach our gateway again:

curl -v ${PROXY_IP}:8080

which outputs:

*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< content-length: 19
< content-type: text/plain
< date: Fri, 09 Feb 2024 10:10:16 GMT
< server: Kuma Gateway
< x-envoy-upstream-service-time: 24
<
* Connection #0 to host 127.0.0.1 left intact
RBAC: access denied%

Notice the “forbidden” error. The quickstart applies restrictive default permissions, so the gateway can’t access the demo-app service.

To fix this, add a MeshTrafficPermission:

kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/refs/heads/main/kustomize/overlays/002-with-gateway/mesh-traffic-permission.yaml

Which will create resource:

echo "apiVersion: kuma.io/v1alpha1
kind: MeshTrafficPermission
metadata:
  namespace: kuma-demo 
  name: demo-app
spec:
  targetRef:
    kind: Dataplane
    labels:
      app: demo-app
  from:
    - targetRef:
        kind: MeshSubset
        tags: 
          kuma.io/service: edge-gateway_kuma-demo_svc 
      default:
        action: Allow" | kubectl apply -f -

Check it works with:

curl -XPOST -v ${PROXY_IP}:8080/api/counter

Now returns a 200 OK response:

*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> POST /api/counter HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< x-demo-app-version: v1
< date: Thu, 29 May 2025 10:14:06 GMT
< content-length: 24
< x-envoy-upstream-service-time: 91
< server: Kuma Gateway
<
{"counter":1,"zone":""}
* Connection #0 to host 127.0.0.1 left intact

Securing your public endpoint with a certificate

The application is now exposed to a public endpoint thanks to the gateway. We will now add TLS to our endpoint.

Create a certificate

Create a self-signed certificate:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=${PROXY_IP}"
echo "apiVersion: v1
kind: Secret
metadata:
  name: my-gateway-certificate
  namespace: kuma-system 
  labels:
    kuma.io/mesh: default
data:
  value: "$(cat tls.key tls.crt | base64)"
type: system.kuma.io/secret" | kubectl apply -f - 

Now update the gateway to use this certificate:

echo "apiVersion: kuma.io/v1alpha1
kind: MeshGateway
mesh: default
metadata:
  name: edge-gateway
spec:
  selectors:
    - match:
        kuma.io/service: edge-gateway_kuma-demo_svc
  conf:
    listeners:
      - port: 8080
        protocol: HTTPS
        tls:
          mode: TERMINATE
          certificates:
            - secret: my-gateway-certificate
        tags:
          port: http-8080" | kubectl apply -f -

Check the call to the gateway:

curl -X POST -v --insecure "https://${PROXY_IP}:8080/api/counter"

Which should output a successful call and indicate TLS is being used:

*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=127.0.0.1
*  start date: May 29 10:15:05 2025 GMT
*  expire date: May 29 10:15:05 2026 GMT
*  issuer: CN=127.0.0.1
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://127.0.0.1:8080/api/counter
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: 127.0.0.1:8080]
* [HTTP/2] [1] [:path: /api/counter]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> POST /api/counter HTTP/2
> Host: 127.0.0.1:8080
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< content-type: application/json; charset=utf-8
< x-demo-app-version: v1
< date: Thu, 29 May 2025 10:15:40 GMT
< content-length: 24
< x-envoy-upstream-service-time: 56
< server: Kuma Gateway
< strict-transport-security: max-age=31536000; includeSubDomains
<
{"counter":3,"zone":""}
* Connection #0 to host 127.0.0.1 left intact

Note that we’re using --insecure as we have used a self-signed certificate.

Next steps