Published
January 14, 2025

A practical guide to Kubernetes Gateway API

Ahmad Ibrahim
Ahmad Ibrahim
Senior Software Engineer

Overview

What is Gateway API?

Gateway API is an official Kubernetes project designed to address cluster ingress and internal routing needs. It focuses on L4 and L7 routing, offering APIs for managing ingress, load balancing, and even experimental service mesh support.

As a direct successor to the much loved Kubernetes Ingress API, Gateway API became generally available with its v1.0.0 release on October 31, 2023. Since then, the project has continued to grow with a constant flow of contributions and releases.

Gateway API focuses on defining the standard that other providers can then implement. We’re starting to see an expanding list of implementations from some of the most popular networking projects like Envoy Gateway, Gloo Gateway, Istio, and NGINX Gateway Fabric. A comprehensive list of implementations can be found here including their current status.

Disambiguating Gateway API and API Gateway

It’s important to clarify the difference between Gateway API and an API Gateway, as understandably these terms can be confused.

  • Gateway API: A Kubernetes native API that standardizes the way we configure and manage ingress traffic routing within a cluster. It focuses on controlling traffic flow at the infrastructure level by introducing Custom Resource Definitions (CRDs) that can configure your load balancer.
  • API Gateway: The concept of anything that is responsible for managing API requests between clients and backend services. API Gateways typically offer features like authentication, rate limiting, request transformation, etc. They’re used to manage and secure API endpoints for clients.

Why not just use Ingress API?

You may be wondering why we would even need Gateway API if Ingress API already exists and seems to work well enough. 

While Ingress API is straightforward and effective for basic use cases, as your organization scales, maintenance of the Ingress resources will grow more difficult. You could find yourself in a situation where application developers are stepping on the toes of cluster operators and vice versa because they’re all making changes to the same underlying resources. 

Gateway API also addresses some of the limitations of Ingress API without sacrifice in the simplicity department. Let’s dive into some specific limitations:

  • Expressiveness: Ingress API supports simple HTTP/HTTPS routing but lacks features like header-based or query-parameter-based routing.
  • Separation of concerns: Ingress API combines routing rules, load balancing, and backend configurations in a single resource. This tight coupling can cause management headaches, especially in large or multi-tenant setups. For example, an application developer who wants to add a new route for their service needs to interact with the same API that handles backend configurations and infrastructure-level settings. This increases the likelihood of misconfigurations and complicates workflows.
  • Protocol support:  Ingress API is primarily focused on HTTP/HTTPS traffic and offers limited support for other protocols like TCP, UDP, or gRPC.
  • Extensibility: While Ingress API is extendable via annotations, the approach can be inconsistent and often vendor specific which leads to portability issues.
  • Sub-par observability: The single API approach limits feedback and status reporting. Debugging misconfigurations or runtime behavior is challenging due to the lack of granular insights.

Gateway API aims to address all of these concerns by introducing several CRDs that align with specific user personas (more on this later). This simplifies the separation of user responsibilities while enhancing flexibility and control.

Perhaps the most important reason to start using Gateway API instead of Ingress is that the Ingress API is officially frozen. This means no new features or fixes will be added to Ingress. The Ingress API is legacy at this point and continuing to use it not only results in taking on tech debt, but also means you’ll miss out on all the modern features and improvements that are being introduced through Gateway API.

Due to how Gateway API was designed with a focus on particular personas managing particular resources, there isn’t a direct one to one translation of Ingress resources to Gateway API resources. But not to worry, the Gateway API team has provided some great docs on how to migrate from Ingress to Gateway API. There are also tools available like the aptly named ingress2gateway which can help with migrating from Ingress to Gateway API. Keep in mind that you should always test and verify the results of the automated migration.

How Gateway API works

Introducing the new CRDs

The Gateway API specification introduces multiple new CRDs to enhance flexibility and scalability in managing ingress and inter-cluster routing. These include:

  • GatewayClass: Defines a class of Gateways that share common configurations, like the underlying infrastructure or controller (e.g. Kuma, Istio, or Gloo Gateway). This allows infrastructure providers to standardize Gateway behaviour across clusters.
  • Gateway: Represents an instance of a GatewayClass and defines the entry points for traffic into the cluster. It essentially defines a request for a specific load balancer config. Until the Gateway CR is created, no actual infrastructure will be provisioned.
  • Route: These CRDs specify routing behaviours for requests from the Gateway listener to a Service. There are multiple CRDs defined which add support for different protocols. The different supported RouteTypes include:
    • GRPCRoute
    • HTTPRoute
    • TCPRoute (experimental)
    • TLSRoute (experimental)
    • UDPRoute (experimental)

Each CRD is designed with a specific use case in mind. For a complete specification, refer to the official Gateway API documentation here.

Note: Gateway API has a strict graduation criteria for a feature to move from the experimental to the standard release channel. While to some it may feel frustrating to have to wait for a particular feature to reach GA, this process helps ensure that the standard channel remains stable and maintains backwards compatibility.

Gateway to Route relationships

As mentioned above, each Gateway needs to reference a specific GatewayClass, but there are multiple ways you can link a Route to a Gateway. Here are some common configuration patterns that you can use:

  • Single Route to a single Gateway: A single Route resource is attached to a single Gateway. This is ideal for tightly scoped use cases where the Gateway serves a specific application or service.
  • Multiple Routes on a single Gateway: A single Gateway can serve multiple Route resources across different namespaces. This is a common pattern for multi-tenant environments, where teams or applications share a Gateway but have independently managed Routes.
  • Single Route on multiple Gateways: A single Route can be linked to multiple Gateways. This is particularly useful for scenarios where an application needs to be exposed across different environments (e.g., public and private networks) or through different IPs or load balancers.

Service mesh integration and experimental features

In addition to its primary use case for ingress, Gateway API also supports linking Route resources directly to services, bypassing the need for Gateway and GatewayClass resources. By doing this, you’d effectively be utilizing your provider's service mesh. This is an experimental feature being actively developed as a part of the GAMMA Initiative (Gateway API for Mesh Management and Administration).

Gateway API’s target user personas

Each of the CRDs discussed in the above section is intended to be managed by a user of a particular persona. The personas that Gateway API are designed for are:

  • Infrastructure provider: Focused on creating and managing GatewayClasses. Create controllers which decouple the mechanism for implementing Gateways from the end user.
  • Cluster operator: Responsible for configuring Gateway resources. They define cluster entry points, manage listeners, and establish gateway-level policies.
  • Application developer: Manages the Route CRDs (HTTPRoute, GRPCRoute, etc). Developers configure routing rules for their specific applications, allowing for granular control without impacting the underlying infrastructure.

Naturally, depending on the structure of your organisation, users may wear multiple persona hats. But this separation of concerns via personas allows even very mature organizations to assign users to distinct roles where they can easily operate together with little to no friction.

RBAC

Gateway API’s design naturally aligns with Kubernetes Role-Based Access Control (RBAC). A simple 3 tier model of write permissions by role can look something like this (where a Role is created for each persona):

 

 GatewayClass

 Gateway

 Route

Infrastructure provider

 ✅

 ✅

 ✅

Cluster operator

 ❌

 ✅

 ✅

Application developer

 ❌

 ❌

 ✅

 

Given that you’ve configured RBAC as defined above, you’ll know that you’ve secured your infrastructure configuration from unwanted changes by unauthorized actors. This alignment enforces secure and efficient workflows, allowing teams to focus on their responsibilities without overstepping any boundaries.

Gateway API in action

Now that we’ve spent a lot of time going over the theory, it’s time to finally see how it all works in practice. 

We’ll create a local Kind cluster, set up Kuma as the Gateway provider, and explore various routing features like path-based routing, header modifications, path rewrites, and traffic splitting. 

We chose Kuma because its Gateway controller implementation has reached GA status, but we very well could have gone through the demo with one of the other Gateway providers. You can follow along with the complete demo steps in this GitHub repository. All the Kubernetes manifests used should be viewed from there.

Prerequisites

Ensure you have the following tools installed:

Cluster setup

Start by creating a Kind cluster and installing the Gateway API CRDs, Kuma, and the necessary namespaces

kind create cluster

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

helm repo add kuma https://kumahq.github.io/charts
helm repo update
helm install --create-namespace --namespace kuma-system kuma kuma/kuma

kubectl apply -f manifests/1-kuma.yaml

The helm install of Kuma will already handle setting up our GatewayClass.

Visual illustrating the process if helm install of Kuma

Gateway creation

Now we want to create two Gateways which reference the Kuma GatewayClass

  1. A shared Gateway (shared-gw) that allows Routes from all namespaces to attach to it
  2. A dedicated Gateway (dedicated-team-c-gw) that only accepts Routes from within the same namespace (team-c-infra)
kubectl apply -f manifests/2-gateways.yaml

In another terminal we can run `sudo cloud-provider-kind` to help us simulate an external IP on our LoadBalancer type services (i.e. the services attached to our Gateways). Once this is done, just run the following command to get the external IP of our shared Gateway.

export PROXY_IP=$(kubectl get svc --namespace shared-infra shared-gw -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

Our cluster should now look like this:

How your cluster should look after installing GatewayClass: Kuma

Let’s test the connection:

curl ${PROXY_IP}:80
This is a Kuma MeshGateway. No routes match this MeshGateway!

Nice, it works! It looks like the Cluster Operator’s job is done now. Lets move onto getting our Application Developers to set up their Routes!

HTTPRoute Creation

We now want to set up two Routes that each send traffic to two distinct services. These Routes are going to demonstrate:

  1. Path-based routing to direct requests to the appropriate service
  2. Header modification to inject a new header on each request
  3. Path rewriting to replace the matched request path to `/api`
  4. Traffic splitting between two versions of a backend service (80% to v1 and 20% to v2)

Apply the routes with:

kubectl apply -f manifests/3-routes.yaml

Once these Routes are deployed, you should be able to confirm they were accepted by inspecting their status:

Status:
  Parents:
	Conditions:
  	Last Transition Time:  2025-01-11T04:40:07Z
  	Message:
  	Observed Generation:   1
  	Reason:            	ResolvedRefs
  	Status:            	True
  	Type:              	ResolvedRefs
  	Last Transition Time:  2025-01-11T04:39:21Z
  	Message:
  	Observed Generation:   1
  	Reason:            	Accepted
  	Status:            	True
  	Type:              	Accepted

To prove that not all Routes will be accepted, we can also try applying a Route from namespace `team-b` to the `dedicated-team-c-gw`. This was the Gateway that only accepts routes from within its namespace.

Try applying the bad Route with:

kubectl apply -f manifests/3-routes-failed.yaml

You’ll notice that the Route is created, but when inspecting its status, you’ll see the below indicating that it hasn’t been accepted.

Status:
  Parents:
	Conditions:
  	Last Transition Time:  2025-01-11T04:39:29Z
  	Message:           	backend reference references a non-existent Service "team-b/team-b-failed-app"
  	Observed Generation:   1
  	Reason:            	BackendNotFound
  	Status:            	False
  	Type:              	ResolvedRefs
  	Last Transition Time:  2025-01-11T04:39:29Z
  	Message:
  	Observed Generation:   1
  	Reason:            	NotAllowedByListeners
  	Status:            	False
  	Type:              	Accepted

Our setup is starting to all come together. This is where we’re at now:

HTTPRoute Creation with GatewayClass: Kuma structure

Now it's time to deploy our backend apps that traffic will be routed to!

Deploying the backend apps

Our backend app is going to be a really simple Go server which does the following:

  1. Handle requests from the /api endpoint (the path that was rewritten)
  2. Log the value of the `X-Request-From` header which was added by the HTTPRoute
  3. Log the value of the server that handled the request to give us a rough idea of how the traffic splitting is going
  4. Return a 200 OK

The entirety of the Go server can be seen here:

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/api", apiHandler)

	port := os.Getenv("PORT")
	if port == "" {
    	port = "8080"
	}
	log.Printf("Server starting on port %s", port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("Received request on %s", r.URL.Path)

	// Print the X-Request-From header
	requestFrom := r.Header.Get("X-Request-From")
	if requestFrom == "" {
    	requestFrom = "Unknown"
	}
	log.Printf("X-Request-From: %s", requestFrom)

	// Respond to the request
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Hello from %s\n", os.Getenv("APP_NAME"))
	fmt.Fprintf(w, "Received from: %s\n", requestFrom)
}

We can build and load an image of our application onto our cluster, and then deploy the Services and Deployments to our cluster with the following commands:

docker build -t gateway-api-demo-server:latest .
kind load docker-image gateway-api-demo-server:latest
kubectl apply -f manifests/4-apps.yaml

And with that, our cluster has reached its final state:

Deploying the backend apps

Now with `cloud-provider-kind` running, we can test the setup by executing either of the following requests:

  • curl ${PROXY_IP}:80/team-a
  • curl ${PROXY_IP}:80/team-b

Executing a few of those commands in succession will yield results like this:

curl ${PROXY_IP}:80/team-a
Hello from team-a-app-v1
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-a
Hello from team-a-app-v1
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-a
Hello from team-a-app-v2
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-a
Hello from team-a-app-v1
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-b
Hello from team-b-app-v2
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-b
Hello from team-b-app-v1
Received from: Gateway-API-Demo

curl ${PROXY_IP}:80/team-b
Hello from team-b-app-v1
Received from: Gateway-API-Demo

If you run these commands enough, it’ll be abundantly clear that the majority of the requests are making their way to the v1 versions of the apps. It’s also good to see that the header added by the HTTPRoute is present and that our server was able to handle requests to /api because the request paths were rewritten.

Final thoughts and next steps

Gateway API is an important evolution in managing ingress and inter-cluster networking in Kubernetes. Its modular design and persona-driven approach provide robust tools to handle diverse routing scenarios while maintaining simplicity and flexibility. Routes can be configured in so many ways that can enable strategies like canary traffic rollout, mirroring traffic to test environments, and so on.

With its emphasis on scalability, extensibility, and separation of concerns, Gateway API empowers organizations to build resilient and efficient networking setups tailored to their needs. Whether you’re just getting started or managing a complex environment of multiple Kubernetes clusters, you shouldn’t ignore Gateway API.

Consider using Gateway API for your next project. It’s not only an improvement over Ingress API but also a forward-looking investment in Kubernetes-native networking. You can try out some of the most popular Gateway API downstream implementations via Palette. We support Cilium, Gloo, Istio, Kong, and NGINX to name a few. Check out our Packs List and get in touch for a demo.

Tags:
API
Cloud
How to
Operations
Networking
Subscribe to our newsletter
By signing up, you agree with our Terms of Service and our Privacy Policy