Introduction
In the evolving landscape of Kubernetes, managing incoming traffic efficiently and securely is paramount for modern applications. For years, Kubernetes Ingress has been the de-facto standard for HTTP and HTTPS routing, but its limitations, especially in advanced traffic management scenarios, have become increasingly apparent. Enter the Kubernetes Gateway API – a robust, extensible, and role-oriented API designed to overcome these challenges and provide a more powerful way to expose services to the outside world.
The Gateway API introduces a new set of resources, including GatewayClass, Gateway, and HTTPRoute, to provide granular control over traffic routing. Among these, HTTPRoute stands out as the workhorse for defining advanced HTTP routing patterns. It allows developers and operators to specify complex matching rules, manipulate requests and responses, and implement sophisticated traffic management strategies like canary deployments, A/B testing, and blue/green rollouts with unparalleled flexibility. This guide will dive deep into the HTTPRoute resource, exploring its capabilities and demonstrating how to leverage it for advanced traffic routing patterns in your Kubernetes clusters.
If you’re still navigating the transition from Ingress to Gateway API, we highly recommend checking out our comprehensive guide: Kubernetes Gateway API vs Ingress: The Complete Migration Guide. This resource will provide you with the foundational knowledge needed to fully appreciate the power and flexibility of HTTPRoute as we explore its advanced features.
TL;DR: Advanced HTTPRoute Patterns
The Kubernetes Gateway API’s HTTPRoute resource offers powerful, fine-grained control over HTTP traffic routing. It surpasses Ingress by enabling advanced patterns like path, header, and query parameter matching, request/response modification, and sophisticated traffic splitting for canary deployments or A/B testing. This guide covers setting up a Gateway API controller, defining a Gateway, and then creating various HTTPRoute configurations to manage traffic effectively.
Key Commands:
# Install a Gateway API controller (e.g., Nginx Gateway)
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.service.type=LoadBalancer \
--set-string controller.config.enable-gateway-api=true
# Deploy a sample application
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/docs/examples/gateway/httproute/echoserver.yaml
# Define a Gateway
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
EOF
# Example: Basic HTTPRoute
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: basic-http-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
# Example: PathPrefix and Header Matching
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: advanced-http-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
headers:
- name: X-Version
value: "v2"
backendRefs:
- name: echoserver-v2
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
# Get Gateway IP
kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}'
Prerequisites
Before we embark on our journey into advanced HTTPRoute patterns, ensure you have the following:
- Kubernetes Cluster: A running Kubernetes cluster (v1.20+ is recommended for Gateway API, though v1.22+ is where it became generally available). You can use Minikube, Kind, a cloud-managed cluster (EKS, GKE, AKS), or any other Kubernetes distribution.
kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation instructions.- Helm: The package manager for Kubernetes, used to install the Gateway API controller. Install instructions can be found on the Helm website.
- Gateway API CRDs: The Custom Resource Definitions (CRDs) for the Gateway API must be installed in your cluster. Most Gateway API controllers will install these automatically, but it’s good to be aware.
- A Gateway API Controller: You need an implementation of the Gateway API. Popular choices include Nginx, Istio, Cilium, or Envoy Gateway. For this tutorial, we will use the Nginx Gateway API controller as it’s straightforward to set up for demonstration purposes. For a more sophisticated service mesh implementation with Gateway API support, consider exploring our Istio Ambient Mesh Production Guide.
- Basic Kubernetes Knowledge: Familiarity with Kubernetes concepts like Pods, Deployments, Services, and Namespaces is assumed.
Step-by-Step Guide: Advanced HTTPRoute Patterns
Step 1: Install a Gateway API Controller
The first step is to install a Gateway API controller in your cluster. This controller watches for Gateway API resources (GatewayClass, Gateway, HTTPRoute, etc.) and translates them into the underlying infrastructure configuration (e.g., Nginx configuration, Envoy configuration). For this guide, we’ll use the Nginx Gateway controller, which can be easily installed via Helm.
We’ll install the Nginx Ingress Controller, configured to also act as a Gateway API controller. This setup is common as many Ingress controllers are evolving to support Gateway API. We deploy it into its own namespace, ingress-nginx, and expose it via a LoadBalancer service to make it accessible from outside the cluster.
# Add the Nginx Ingress Helm repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install the Nginx Ingress Controller with Gateway API support enabled
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.service.type=LoadBalancer \
--set-string controller.config.enable-gateway-api=true
Verify:
Confirm that the Nginx controller pods are running and the LoadBalancer service has an external IP or hostname assigned.
kubectl get pods -n ingress-nginx
Expected Output (may vary slightly):
NAME READY STATUS RESTARTS AGE
ingress-nginx-controller-5c9c9b46c-abcde 1/1 Running 0 2m
kubectl get svc -n ingress-nginx ingress-nginx-controller
Expected Output (EXTERNAL-IP will be pending for a short while on some cloud providers):
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.96.100.123 34.123.45.67 80:30000/TCP,443:30001/TCP 2m
Make a note of the EXTERNAL-IP; this will be your Gateway’s public address.
Step 2: Deploy Sample Applications
To demonstrate various routing patterns, we need some sample applications. We’ll deploy a simple echoserver application which returns details about the request it received. We’ll deploy two versions to simulate different services or versions of a service.
First, deploy a standard echoserver. This will be our default backend.
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
labels:
app: echoserver
spec:
replicas: 1
selector:
matchLabels:
app: echoserver
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: k8s.gcr.io/echoserver:1.4
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
selector:
app: echoserver
ports:
- port: 80
targetPort: 8080
EOF
Next, let’s deploy a “v2” version of the echoserver, which we’ll use for advanced routing scenarios like header matching or traffic splitting. We’ll label it differently so we can target it specifically.
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver-v2
labels:
app: echoserver-v2
spec:
replicas: 1
selector:
matchLabels:
app: echoserver-v2
template:
metadata:
labels:
app: echoserver-v2
spec:
containers:
- name: echoserver
image: k8s.gcr.io/echoserver:1.4 # Using the same image, but we'll differentiate via routing
ports:
- containerPort: 8080
env:
- name: NODE_NAME
value: "echoserver-v2-pod"
EOF
Verify:
Ensure both deployments and services are running:
kubectl get deploy,svc
Expected Output:
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/echoserver 1/1 1 1 1m
deployment.apps/echoserver-v2 1/1 1 1 1m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/echoserver ClusterIP 10.96.10.100 <none> 80/TCP 1m
service/echoserver-v2 ClusterIP 10.96.10.101 <none> 80/TCP 1m
Step 3: Define a Gateway
A Gateway resource represents a load balancer or proxy that handles incoming traffic. It binds to a GatewayClass (which is provided by the controller) and defines listeners for specific ports and protocols.
We’ll create a simple HTTP Gateway listening on port 80. The gatewayClassName must match the name of the GatewayClass provided by your controller (e.g., nginx for the Nginx Gateway controller).
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
---
apiVersion: v1
kind: Service
metadata:
name: my-gateway-lb # This is optional, but some controllers create a Service for the Gateway.
# For Nginx Gateway, the LoadBalancer service created in Step 1 is often used directly.
# However, defining a Gateway explicitly tells the controller which LoadBalancer service to manage/associate.
spec:
type: LoadBalancer # For Nginx, this might not be strictly needed if controller manages Service directly
selector:
gateway.networking.k8s.io/gateway-name: my-gateway # Match the gateway controller's pods
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
Note: With the Nginx Gateway controller installed in Step 1, the ingress-nginx-controller service already acts as the LoadBalancer. The Gateway resource primarily configures the controller’s behavior. Some Gateway API implementations might create a dedicated LoadBalancer service for each Gateway resource. The Nginx Gateway API controller typically uses the service created during its installation. We’ve added a placeholder Service definition for clarity, but the actual LoadBalancer IP will come from the controller’s service.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All # Allow HTTPRoutes from any namespace to attach to this Gateway
EOF
Verify:
Check the status of your Gateway. It should eventually show an address.
kubectl get gateway my-gateway -o yaml
Expected Output (look for status.addresses):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: default
spec:
# ... (spec details) ...
status:
addresses:
- type: IPAddress
value: 34.123.45.67 # Your LoadBalancer IP
conditions:
- lastTransitionTime: "2023-10-27T10:00:00Z"
message: Gateway is accepted by controller
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2023-10-27T10:00:00Z"
message: Listener Ready
observedGeneration: 1
reason: Ready
status: "True"
type: Ready
listeners:
- attachedRoutes: 0
conditions:
- lastTransitionTime: "2023-10-27T10:00:00Z"
message: Listener is Running
observedGeneration: 1
reason: Ready
status: "True"
type: Ready
name: http
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRoute
Retrieve the external IP for testing:
export GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
echo "Gateway IP: $GATEWAY_IP"
Step 4: Basic HTTPRoute – Hostname and Path Matching
Let’s start with a foundational HTTPRoute that routes all traffic for a specific hostname to our echoserver service. This is analogous to a basic Ingress rule. The parentRefs link the HTTPRoute to our Gateway, and hostnames specify which domain this route applies to.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: basic-http-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com" # Use a domain you control or map via /etc/hosts
rules:
- matches:
- path:
type: PathPrefix
value: / # Match all paths
backendRefs:
- name: echoserver
port: 80
EOF
Apply this route:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: basic-http-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
Verify:
Update your local /etc/hosts file (or DNS) to point example.com to your GATEWAY_IP. Then, make a request.
# Add to /etc/hosts (replace with your actual IP)
echo "$GATEWAY_IP example.com" | sudo tee -a /etc/hosts
curl -H "Host: example.com" http://$GATEWAY_IP/
Expected Output (showing details from the echoserver):
Hostname: echoserver-7fbc6c478c-abcde
Pod Information:
- no pod information available -
Server values:
server_version=nginx/1.10.0
Request Information:
client_address=10.0.0.1
method=GET
path=/
headers:
Host: example.com
User-Agent: curl/7.81.0
X-Forwarded-For: 10.0.0.1, 10.1.2.3
# ... other headers ...
Step 5: Advanced Matching – Path, Header, and Query Parameter
HTTPRoute allows for highly granular traffic matching. You can combine multiple match types within a single rule. Let’s create a rule that routes traffic to echoserver-v2 if the path starts with /v2 AND a specific header X-Version: v2 is present. Otherwise, it falls back to the default echoserver.
This is extremely powerful for A/B testing or rolling out new APIs to specific clients. For more advanced network security and isolation, consider how these routing rules can complement Kubernetes Network Policies to secure your microservices.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: advanced-match-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
headers:
- name: X-Version
value: "v2"
queryParams:
- name: debug
value: "true"
backendRefs:
- name: echoserver-v2
port: 80
- matches: # Default rule for anything else
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
Apply this route:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: advanced-match-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v2
headers:
- name: X-Version
value: "v2"
queryParams:
- name: debug
value: "true"
backendRefs:
- name: echoserver-v2
port: 80
- matches:
- path:
type: PathPrefix
value: / # Default rule
backendRefs:
- name: echoserver
port: 80
EOF
Verify:
Test with different requests:
# Should go to echoserver (default)
curl -H "Host: example.com" http://$GATEWAY_IP/
curl -H "Host: example.com" http://$GATEWAY_IP/v2
# Should go to echoserver-v2 (matching all conditions)
curl -H "Host: example.com" -H "X-Version: v2" http://$GATEWAY_IP/v2?debug=true
Expected Output for default:
Hostname: echoserver-7fbc6c478c-abcde
# ... (no mention of echoserver-v2-pod) ...
Expected Output for v2 route:
Hostname: echoserver-v2-deployment-abcde # Or similar pod name
Pod Information:
NODE_NAME: echoserver-v2-pod # This is our custom env var
# ...
Request Information:
headers:
Host: example.com
X-Version: v2
uri: /v2?debug=true
Step 6: Traffic Splitting (Canary Deployments)
One of the most powerful features of HTTPRoute is its ability to split traffic between multiple backends based on weights. This is crucial for implementing canary deployments, A/B testing, or gradual rollouts. We’ll split traffic for the /canary path, sending 90% to echoserver and 10% to echoserver-v2.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: traffic-split-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "canary.example.com" # A new hostname for canary testing
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
weight: 90 # 90% of traffic
- name: echoserver-v2
port: 80
weight: 10 # 10% of traffic
EOF
Apply this route:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: traffic-split-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "canary.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
weight: 90
- name: echoserver-v2
port: 80
weight: 10
EOF
Verify:
Add canary.example.com to your /etc/hosts file, pointing to the same GATEWAY_IP.
echo "$GATEWAY_IP canary.example.com" | sudo tee -a /etc/hosts
Now, repeatedly hit the endpoint and observe the responses. You should see approximately 1 out of 10 requests hitting echoserver-v2.
for i in $(seq 1 20); do curl -s -H "Host: canary.example.com" http://$GATEWAY_IP/ | grep "Hostname\|NODE_NAME"; done
Expected Output (mix of both):
Hostname: echoserver-7fbc6c478c-abcde
Hostname: echoserver-7fbc6c478c-abcde
Hostname: echoserver-v2-deployment-abcde
NODE_NAME: echoserver-v2-pod
Hostname: echoserver-7fbc6c478c-abcde
# ... and so on ...
Step 7: Request and Response Header Modification
HTTPRoute also allows you to modify HTTP headers both on the request before it reaches the backend and on the response before it’s sent back to the client. This is useful for injecting correlation IDs, security tokens, or removing sensitive information.
Let’s add a custom request header X-Request-ID to all requests and remove the Server header from responses for a specific path.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-mod-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "headers.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Request-ID
value: "generated-by-gateway"
- type: ResponseHeaderModifier
responseHeaderModifier:
remove:
- "Server" # The echoserver usually adds a Server header
backendRefs:
- name: echoserver
port: 80
EOF
Apply this route:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-mod-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "headers.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Request-ID
value: "generated-by-gateway"
- type: ResponseHeaderModifier
responseHeaderModifier:
remove:
- "Server"
backendRefs:
- name: echoserver
port: 80
EOF
Verify:
Add headers.example.com to your /etc/hosts.
echo "$GATEWAY_IP headers.example.com" | sudo tee -a /etc/hosts
Make a request and inspect the request headers received by the backend (echoserver output) and the response headers returned to your client.
curl -v -H "Host: headers.example.com" http://$GATEWAY_IP/
Expected Output (partially, look for X-Request-ID in the echoserver output and absence of Server in curl’s response headers):
# In the echoserver output (body of the curl response):
Request Information:
headers:
Host: headers.example.com
X-Request-Id: generated-by-gateway
# ...
# In curl's verbose output (headers received by client):
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Fri, 27 Oct 2023 11:30:00 GMT
# ... (NO 'Server' header) ...
Step 8: URL Rewrite
URL rewriting allows you to change the path or hostname of a request before it’s forwarded to the backend service. This is useful for exposing simpler URLs to clients while maintaining complex internal service structures, or for migrating APIs.
Let’s rewrite requests to /old-path to /new-path before sending them to the echoserver.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rewrite-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "rewrite.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /old-path
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-path
backendRefs:
- name: echoserver
port: 80
- matches: # Default rule for other paths on this host
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
Apply this route:
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rewrite-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "rewrite.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /old-path
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-path
backendRefs:
- name: echoserver
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echoserver
port: 80
EOF
Verify:
Add rewrite.example.com to your /etc/hosts.
echo "$GATEWAY_IP rewrite.example.com" | sudo tee -a /etc/hosts
Request the /old-path and observe that the backend receives /new-path.
curl -H "Host: rewrite.example.com" http://$GATEWAY_IP/old-path/some-resource
Expected Output (look for path in the echoserver output):
Request Information:
method=GET
path=/new-path/some-resource # The path was rewritten!
# ...
Production Considerations
