Getting started with the Kubernetes Gateway API
Intermediate · 9 min read
1. The Problem
In the early days of Kubernetes, ingress controllers were the de facto standard for exposing services to the outside world. While functional, this approach introduced significant operational friction. The primary pain point was the tight coupling between the Ingress resource (a generic Kubernetes object) and the specific controller implementation (e.g., Nginx, HAProxy, Traefik).
Consider a real-world scenario: Your organization wants to migrate from an Nginx-based ingress to Envoy Proxy for better HTTP/3 support. With traditional Ingress resources, this migration often required rewriting hundreds of YAML files because different controllers interpreted the same annotations differently. Furthermore, the single-controller model meant that one team managed all ingress traffic, creating a bottleneck and a single point of failure. There was no native way to delegate authority over specific hosts or paths to different teams without complex RBAC workarounds.
The Gateway API was introduced by the Cloud Native Computing Foundation (CNCF) to solve these fragmentation issues. It separates the control plane (configuration) from the data plane (implementation), allowing multiple gateways to coexist and enabling fine-grained delegation of traffic management. This decoupling is not just a technical improvement; it is an architectural shift that aligns Kubernetes networking with modern microservices governance.
2. Core Concepts
To understand the Gateway API, we must first discard the mental model of “Ingress.” Instead, think in terms of three distinct roles:
- Gateway Class (GatewayClass): This is a cluster-scoped resource that defines the controller implementation. It acts as a template. For example, a class named “nginx-latest” tells Kubernetes which controller binary should handle the subsequent Gateway resources. It does not represent running infrastructure.
- Gateway: This is the actual instance of network infrastructure. It represents a load balancer or proxy deployed within the cluster. A Gateway binds to a specific GatewayClass. It handles the external IP address, TLS termination, and routing logic. You can have multiple Gateways (e.g., one for public web traffic, one for internal service mesh traffic) sharing the same Controller Class.
- HTTPRoute: This resource defines how traffic is routed from a Gateway to backend Services. It specifies rules based on hostnames, paths, headers, and query parameters. Crucially, HTTPRoutes can be namespaced, allowing different teams to define routes independently while attaching them to a shared Gateway.
The key innovation here is the Listener. A Gateway contains one or more Listeners, each representing a specific port and protocol combination (e.g., TCP on port 443). Listeners act as the entry point for traffic, validating incoming requests before they are processed by Routes. This separation allows for strict security boundaries and protocol-specific validation at the edge.
3. How It Works
The mechanism operates through a declarative state reconciliation loop, but with stricter validation than Ingress. Here is the step-by-step mechanical flow:
- Registration: The Gateway Controller watches for GatewayClass objects. When it finds a class matching its configuration, it registers itself as a capable provider.
- Provisioning: When a Gateway resource is created, the controller provisions the underlying infrastructure (often a cloud load balancer or a DaemonSet/Deployment of proxy pods). It allocates an IP address and opens the necessary ports defined in the Listener specs.
- Attachment: HTTPRoutes attach to Gateways via the
parentRefsfield. This is a bidirectional handshake. The Route must reference a valid Gateway, and the Gateway must have a Listener that matches the Route’s hostname and port. If there is a mismatch, the controller marks the route as “Accepted: False” in the status subresource. - Reconciliation: The controller continuously monitors the status of attached Routes. It translates high-level Kubernetes abstractions into low-level configuration for the proxy (e.g., Nginx config files or Envoy clusters). It then pushes this configuration to the data plane.
- Status Feedback: Unlike Ingress, which has limited status reporting, the Gateway API provides rich status conditions. Administrators can see exactly why a route failed (e.g., “InvalidHostname,” “NotAllowedByListeners”) directly in the resource status, enabling rapid debugging.
This architecture enables multi-tenancy. Team A can own a Gateway for production, while Team B owns a Gateway for staging. Both use the same underlying NGINX controller (GatewayClass), but their configurations are isolated. Team C can create an HTTPRoute that attaches to Team A’s Gateway without needing permission to modify the Gateway itself, provided the ClusterAdmin has configured appropriate RBAC policies.
4. Prerequisites
Before implementing the Gateway API, ensure your environment meets the following criteria:
- Kubernetes Version: The API is generally available (GA) in Kubernetes v1.28+. However, most features were beta in v1.22-v1.27. Ensure you are running a version that supports the
GatewayAPIfeature flag enabled by default. - Feature Flags: On older versions (v1.22-1.27), you must enable the
GatewayAlphaandGatewayBetafeature gates in your kube-apiserver configuration. In v1.28+, this is automatic. - Controller Installation: You need a compatible gateway controller. Popular options include
kong/kubernetes-ingress-controller,envoyproxy/gateway, andnginxinc/nginx-unprivileged. Install the controller separately from the CRDs (Custom Resource Definitions). - CRDs: Apply the official Gateway API CRD manifests. These define the schema for
GatewayClass,Gateway, andHTTPRoute.
5. Hands-On Walkthrough
We will deploy a simple setup using the Kong Gateway Controller as our implementation. Assume we have a deployment called hello-server on port 8080.
Step 1: Define the GatewayClass
This resource declares that we want to use the Kong controller. The controllerName must match the identifier expected by the installed controller.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
name: kong-class
spec:
controllerName: konghq.com/kong-gateway-gc
parametersRef:
group: gateway.k8s.io
kind: ParametersResource
name: kong-cluster-settings
Step 2: Create the Gateway Instance
This creates the actual load balancer. We define a listener for HTTPS on port 443. Note the allowedRoutes field, which restricts which namespaces can create HTTPRoutes that attach to this Gateway.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: kong-gateway
namespace: default
spec:
gatewayClassName: kong-class
listeners:
- name: https
port: 443
protocol: HTTPS
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: my-tls-cert
kind: Secret
Step 3: Define the HTTPRoute
This route directs traffic to our backend service. It attaches to the Gateway listener we just created.
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: hello-route
namespace: default
spec:
parentRefs:
- name: kong-gateway
sectionName: https
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: hello-server
port: 8080
Apply these manifests in order: kubectl apply -f gatewayclass.yaml, kubectl apply -f gateway.yaml, and kubectl apply -f httproute.yaml.
6. Verifying It Works
After applying the resources, verify the state using kubectl get commands. The power of the Gateway API lies in its detailed status output.
Check Gateway Status:
kubectl get gateway kong-gateway -o yaml
Look for the status.listeners section. You should see a condition like:
conditions:
- type: Accepted
status: "True"
reason: Accepted
If this is false, check the reason field. Common reasons include PortNotAvailable or InvalidCertificate.
Check Route Attachment:
kubectl get httproute hello-route -o yaml
Examine the status.parents array. It should show an entry referencing the Gateway, with controllerName set to your controller and conditions indicating Allowed: True. If the route is not attached, verify that the sectionName in the HTTPRoute matches the name in the Gateway Listener.
Test Connectivity:
Once the Gateway has an external IP (check kubectl get svc or the LoadBalancer IP in the Gateway status), test the endpoint:
curl -k https://<EXTERNAL_IP>/api
You should receive the response from your hello-server pod.
7. Common Mistakes
1. Mismatched Section Names: The most frequent error is failing to specify sectionName in the HTTPRoute’s parentRefs. If a Gateway has multiple listeners (e.g., HTTP on 80 and HTTPS on 443), the controller cannot guess which one the route should attach to. Always explicitly link the route to the listener.
2. Ignoring AllowedRoutes: By default, some implementations restrict route attachment to specific namespaces. If your HTTPRoute fails to appear in the Gateway status, check the allowedRoutes.namespaces.from setting in the Gateway spec. Setting it to All allows any namespace to attach, while Same restricts it to the Gateway’s own namespace.
3. Confusing GatewayClass with Gateway: Creating a GatewayClass does not start any pods. It only registers the controller. You must create a Gateway resource to trigger the provisioning of the load balancer. Many beginners create the class and wonder why nothing happens.
4. TLS Configuration Errors: The certificateRefs must point to a Secret in the same namespace as the Gateway. Additionally, the Secret must contain tls.crt and tls.key keys. Using incorrect keys or wrong namespaces will result in a InvalidCertificate status.
8. Going Further
Advanced Routing Features: The Gateway API supports sophisticated routing rules beyond simple prefix matching. You can implement header-based routing, weighted traffic splitting (canary deployments), and request mirroring using the filters field in HTTPRoutes. For example, sending 10% of traffic to a v2 backend while keeping 90% on v1 requires configuring weight attributes in backendRefs.
Security and RBAC: Because HTTPRoutes are namespaced, you can delegate route creation to development teams while restricting Gateway modification to platform engineers. Use Kubernetes RBAC to allow developers to create HTTPRoutes in their namespaces but prevent them from modifying the GatewayClass or Gateway objects. This enforces a secure multi-tenant environment.
Performance Tuning: For high-throughput scenarios, consider using eBPF-based gateways like Cilium or Tetrate Service Bridge. These offload routing logic to the kernel level, reducing latency compared to userspace proxies like Nginx. Evaluate trade-offs between flexibility (userspace proxies offer more complex middleware) and performance (kernel-level dataplanes offer lower CPU overhead).
Migration Strategy: If migrating from Ingress, use tools like kic-migrate or custom scripts to convert Ingress rules to HTTPRoutes. Be aware that some Ingress annotations have direct equivalents in Gateway filters, while others require custom controller extensions. Plan for a hybrid period where both APIs coexist during the transition.
9. Cleanup
To remove the resources created in this tutorial, delete them in reverse order of dependency to avoid orphaned references.
# Delete the HTTPRoute
kubectl delete httproute hello-route
# Delete the Gateway
kubectl delete gateway kong-gateway
# Delete the GatewayClass
kubectl delete gatewayclass kong-class
# Delete the TLS Secret (if created manually)
kubectl delete secret my-tls-cert
Additionally, if you installed the Gateway API CRDs manually, remove them:
kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
Ensure your controller is also uninstalled if it was installed specifically for this test. Verify no residual resources remain with kubectl get all --all-namespaces | grep gateway.
References
This article was drafted with AI assistance and reviewed by the DevOpsEngine editors before publication.
