In my other blog post this week, I talk about the new Kubernetes Gateway API and the release of Envoy Gateway 0.2. In this post we’re going to get hands-on with Envoy Gateway and the Gateway API. Below are instructions that walk you step-by-step through installing Envoy Gateway, and a simple use case of exposing an HTTP app outside the cluster, through an Envoy proxy.

You don’t even need to play along at home if it’s inconvenient; I’ve included the output of every command as well, so you can see how it all works without needing a Kubernetes cluster.

And if you’re a fan of GUIs, at the end of the post I include screenshots and details of Tetrate’s proof-of-concept Envoy Gateway GUI based on Backstage, to show how easy it is to build such things against the Gateway API.

Let’s get started!

Create a Kubernetes Cluster

The first thing we need is a Kubernetes cluster to run Envoy Gateway in. Be careful using a cluster that’s used for other things (so maybe not your company’s prod environment…). The easiest and safest way is to use minikube to bring up a cluster on your local machine.

$ minikube start –driver=docker --cpus=2 --memory=2g

   minikube v1.27.0 on Arch 22.0.0 (x86_64)
❗  For more information, see:
   Using Docker Desktop driver with root privileges
   Starting control plane node minikube in cluster minikube
   Pulling base image ...
   Creating docker container (CPUs=2, Memory=2048MB) ...
   Preparing Kubernetes v1.25.2 on Docker 20.10.17 ...
	▪ Generating certificates and keys ...
	▪ Booting up control plane ...
	▪ Configuring RBAC rules ...
   Verifying Kubernetes components...
	▪ Using image
   Enabled addons: storage-provisioner
   Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

Install Envoy Gateway

Recall that Envoy Gateway is configured by the new Gateway API, rather than the old Ingress one. Gateway API hasn’t been merged into upstream Kubernetes yet, so our cluster won’t have it available. We “install” the API by deploying CRDs for it. The Envoy Gateway project provides a single file which will install Gateway API and deploy Envoy Gateway.

$ kubectl apply -f

This creates a lot of resources; I’ll address them in a couple sections. First, Gateway API: created created created created created created created created
namespace/gateway-system created created
service/gateway-api-admission-server created
deployment.apps/gateway-api-admission-server created
serviceaccount/gateway-api-admission created created created created created
job.batch/gateway-api-admission created
job.batch/gateway-api-admission-patch created

As you can see, this is mostly CRDs. But note that installing Gateway API also deployed some workload resources including Deployments, etc – the Gateway API comes with a webhook admission controller to validate the resources we deploy, which we can see with the following command:

$ kubectl get pods --namespace gateway-system

NAME                                        	READY   STATUS  	RESTARTS   AGE
gateway-api-admission-2dhk5                 	0/1 	Completed   0      	70s
gateway-api-admission-patch-dbdbc           	0/1 	Completed   1      	70s
gateway-api-admission-server-68485ffc97-gt8v4   1/1 	Running 	0      	70s

Let’s also take a look at the new CRDs adding to the cluster’s API surface.

$ kubectl api-resources | grep gateway.networking

gatewayclasses                	gc  	false    	GatewayClass
gateways                      	gtw  	true     	Gateway
httproutes                          	true     	HTTPRoute
referencegrants               	refgrant 	true     	ReferenceGrant
referencepolicies             	refpol 	true     	ReferencePolicy
tcproutes                          	true     	TCPRoute
tlsroutes                          	true     	TLSRoute
udproutes                          	true     	UDPRoute

Now let’s look at Envoy Gateway itself.

namespace/envoy-gateway-system created created
serviceaccount/envoy-gateway created created created created created created created created
configmap/envoy-gateway-config created
service/envoy-gateway created
service/envoy-gateway-metrics-service created
deployment.apps/envoy-gateway created

As you might expect, these are workload resources and associated security and networking. After a short time we can see the running controller:

$ kubectl get pods --namespace envoy-gateway-system

NAME                        	READY   STATUS	RESTARTS   AGE
envoy-gateway-dc74c4d97-pntbj   2/2 	Running   0      	35s

Install a Test App

We’ll also need something for the gateway to actually forward traffic to—something that acts like one of our apps. We can use httpbin for that, and the EG project provides convenient manifests for it.

$ kubectl apply -f

serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created

This will run in the default namespace:

$ kubectl get pods -n default

NAME                    	READY   STATUS	RESTARTS   AGE
httpbin-9dbd644c7-fhtw5 	1/1 	Running   0      	4m16s

Configure Envoy Gateway

Now we can get on to configuring Envoy Gateway to do some request routing. The first thing we need to do is register the EG controller we just deployed, so that other resources can reference it to identify which gateway they’re configuring, in case you deploy several in one cluster. The single field, controllerName, matches a value which the controller registers with the Kubernetes cluster it’s running in.

$ kubectl apply -f - <<EOF
kind: GatewayClass
  name: my-envoy-gateway
EOF created

We can see some limited information about this new instance, and assuming our description was valid we’ll see ACCEPTED: True.

$ kubectl get gatewayclass -o wide
NAME   CONTROLLER                                  	ACCEPTED   AGE 	DESCRIPTION
eg   True   	2m38s  

Next, let’s configure a Gateway object—this opens the network port(s) we’d like the Envoy proxy(s) to listen on. For this simple demo, we’ll bind to a high port and not use TLS, so I’ve called it “insecure-port”.

$ kubectl apply -f - <<EOF
kind: Gateway
  name: insecure-port
  gatewayClassName: my-envoy-gateway
    - name: http
      protocol: HTTP
      port: 8080

When we inspect that, we’ll see it doesn’t say it’s ready. This approval is held back since we’re using a local development cluster that can’t make the kind of cloud load balancers a “real” one would be able to (hence the address field is empty too). Don’t worry about this, it’ll still work fine.

$ kubectl -n default get gateway -o wide

NAME        	CLASS             ADDRESS   READY   AGE
insecure-port   my-envoy-gateway                     2m54s

Finally we can set up routing for some HTTP traffic. In this simple example, we match any request for the vhost “”, on any path, and send it to the httpbin instance we deployed earlier.

$ kubectl apply -f - <<EOF
kind: HTTPRoute
  name: httpbin
  parentRefs: [ {name: insecure-port} ]
  hostnames: [""]
    - matches:
        - path: {type: PathPrefix, value: /}
        - {group: "", kind: Service, name: httpbin, port: 8000, weight: 1}
EOF created

Inspecting this kind of resource doesn’t currently show us much, but we can see it was successfully deployed at least.

$ kubectlget httproute -n default -o wide
httpbin	[""]   58s

One last thing to note is that only now is an instance of Envoy actually started (before it was just the controller, which doesn’t itself handle traffic). This is an optimization on EG’s behalf: lazy creation of proxies. Check the “age” field on your system and see how it’s very recent.

$ kubectl get pods -n envoy-gateway-system
NAME                        	READY   STATUS	RESTARTS   AGE
envoy-default-insecure-port-5879556bd4-r5ggw   	1/1 	Running   0      	90s
envoy-gateway-dc74c4d97-pntbj   2/2 	Running   0      	13m

Initiate Test Traffic

Like I said before, we can’t get a real cloud load balancer, since we’re using a local development cluster. But minikube has a neat feature which will expose the cluster’s services to our local machine—including the proxy that EG spun up. The 8080 port we opened will then be available on loopback.

$ minikube tunnel

That command blocks, so open a new terminal to send a request that’ll be routed to httpbin, as per the rules we deployed.

$ curl --header "Host:"

HTTP/1.1 200 OK
server: envoy
date: Fri, 07 Oct 2022 12:41:32 GMT
content-type: application/json
content-length: 175
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 14

	"headers": {
    	"Accept": "application/json, */*",
    	"Host": "",
    	"User-Agent": "curl/7.79.1",
    	"X-Envoy-Expected-Rq-Timeout-Ms": "15000"

So there we have it. It works! That was just a simple demo, but you can explore the rest of the Gateway API surface and start using more of the features – Envoy Gateway’s docs will be a good guide for that. Do be kind of Envoy Gateway at this early point—it’s still a work in progress!

An Example GUI

The instructions I gave are for the command line, but a good GUI is a great way to interact with any system. EG’s implementation of the standard Gateway API will allow a thriving ecosystem of interoperable interfaces to emerge. To kick this area off, Tetrate has built a proof-of-concept UI for EG, using Backstage, the emerging standard for developer interfaces. Below I’ll show a couple screenshots of how it looks inspecting the demo setup from above.

If you’d like to play with this, or fork it, we’ve published the code—fair warning that this is pre-alpha, and just an example of what’s possible to build in a week! If you’re at KubeCon Detroit swing by our booth and we can show it to you in person!

UI for Envoy Gateway


If you’re getting started with Istio and Envoy, check out Tetrate Academy where you’ll find a ton of free courses, on-demand workshops, as well as Tetrate’s Certified Istio Administrator exam.

For the easiest way to install, manage, and upgrade Istio, check out our open source Tetrate Istio Distro (TID). TID is a vetted, upstream distribution of Istio—a hardened image of Istio with continued support that is simpler to install, manage, and upgrade.