Introduction
In the dynamic landscape of modern cloud-native applications, efficient and flexible traffic management is paramount. Kubernetes, the de facto standard for container orchestration, offers various mechanisms for exposing services, but none provide the granular control and extensibility needed for complex routing scenarios quite like the Kubernetes Gateway API. Designed as a successor and more powerful alternative to the venerable Ingress API, Gateway API introduces a robust, role-oriented, and extensible approach to network traffic management, particularly for HTTP/HTTPS services.
At the heart of the Gateway API lies the `HTTPRoute` resource, which is your primary tool for defining how HTTP and HTTPS traffic is routed from a `Gateway` to your Kubernetes services. Beyond basic path-based or host-based routing, `HTTPRoute` unlocks a treasure trove of advanced patterns: from sophisticated header matching and query parameter manipulation to traffic splitting for A/B testing, blue/green deployments, and canary releases. Mastering `HTTPRoute` is crucial for anyone looking to build resilient, scalable, and highly observable applications on Kubernetes. This guide will deep-dive into these advanced patterns, providing practical, copy-paste-ready examples to elevate your traffic routing game. For a broader understanding of migrating from Ingress, check out our Gateway API Migration Guide.
TL;DR
The Kubernetes Gateway API’s HTTPRoute resource offers powerful, advanced traffic routing. It allows for host-based, path-based, header-based, and query parameter-based matching, traffic splitting, and request/response manipulation. This guide demonstrates how to deploy a Gateway and `HTTPRoute`s to implement these patterns for sophisticated application delivery.
# Install Gateway API CRDs (if not already present)
kubectl apply -k "https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.0.0"
# Install a Gateway Controller (e.g., NGINX Gateway Fabric)
helm upgrade --install ngf oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric --namespace nginx-gateway --create-namespace
# Deploy a Gateway
kubectl apply -f <your-gateway.yaml>
# Deploy services and HTTPRoutes
kubectl apply -f <your-application-services.yaml>
kubectl apply -f <your-httproute-advanced.yaml>
# Get Gateway address
kubectl get gateway <your-gateway-name> -n <your-namespace> -o jsonpath='{.status.addresses[0].value}'
# Test routing patterns (replace IP with Gateway address)
curl -H "X-Version: v2" http://<GATEWAY_IP>/api/v1/users
curl -H "User-Agent: mobile" http://<GATEWAY_IP>/app/v1/data
curl http://<GATEWAY_IP>/weighted-split
Prerequisites
To follow this tutorial, you’ll need the following:
- Kubernetes Cluster: A running Kubernetes cluster (v1.24+ recommended for Gateway API v1.0.0 support). Minikube, Kind, or any cloud provider cluster will work.
kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation instructions.helm: The Kubernetes package manager, used for deploying the Gateway API controller. Install it from the Helm website.- Basic Kubernetes Knowledge: Familiarity with Deployments, Services, and Namespaces.
- Basic Gateway API Concepts: An understanding of
GatewayClassandGatewayresources. If you’re new, our Gateway API Migration Guide is a great starting point. curlor similar HTTP client: For testing the routing patterns.
Step-by-Step Guide: Advanced HTTPRoute Patterns
This guide will walk you through deploying a Gateway API controller, setting up a Gateway, and then creating various `HTTPRoute` configurations to demonstrate advanced traffic routing. We’ll use the NGINX Gateway Fabric as our Gateway controller for this example, but the `HTTPRoute` configurations are largely controller-agnostic.
1. Install Gateway API CRDs and a Gateway Controller
First, we need to ensure the Gateway API Custom Resource Definitions (CRDs) are installed in your cluster. These CRDs define the GatewayClass, Gateway, HTTPRoute, and other resources. Then, we’ll deploy a Gateway controller that implements these CRDs. We’ll use NGINX Gateway Fabric as an example controller, which is a popular choice for its performance and feature set. Other controllers like Istio (especially with Istio Ambient Mesh) or Kong Gateway also support Gateway API.
# Install Gateway API CRDs (v1.0.0 is stable)
kubectl apply -k "https://github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.0.0"
# Add the NGINX Gateway Fabric Helm repository
helm repo add nginx-gateway-fabric https://nginxinc.github.io/nginx-gateway-fabric/
# Update Helm repositories
helm repo update
# Install NGINX Gateway Fabric controller
# We'll install it in its own namespace for isolation.
helm upgrade --install ngf nginx-gateway-fabric/nginx-gateway-fabric \
--namespace nginx-gateway --create-namespace \
--set gatewayClass.controllerName=nginx.org/gateway-controller
Verify
Check if the Gateway API CRDs are installed and the NGINX Gateway Fabric pods are running.
kubectl get crd | grep gatewayapi.k8s.io
kubectl get pods -n nginx-gateway
Expected Output:
# Example CRD output (may vary slightly based on version)
gatewayclasses.gateway.networking.k8s.io 2023-10-26T14:30:00Z
gateways.gateway.networking.k8s.io 2023-10-26T14:30:00Z
httproutes.gateway.networking.k8s.io 2023-10-26T14:30:00Z
...
# Example Pods output
NAME READY STATUS RESTARTS AGE
nginx-gateway-fabric-controller-5c9f8d68-2l8sk 1/1 Running 0 2m
2. Deploy a Gateway and Sample Applications
Next, we’ll define a GatewayClass and a Gateway resource. The GatewayClass references our NGINX controller, and the Gateway resource defines the entry point for external traffic, specifying listeners (e.g., HTTP on port 80). We’ll also deploy a few simple NGINX applications that we can use to demonstrate different routing patterns.
# gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: nginx.org/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: default # Can be in any namespace, but for simplicity, default
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All # Allow HTTPRoutes from any namespace to attach
---
# application-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-v1
labels:
app: app
version: v1
spec:
replicas: 2
selector:
matchLabels:
app: app
version: v1
template:
metadata:
labels:
app: app
version: v1
spec:
containers:
- name: app
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
env:
- name: MESSAGE
value: "Hello from App V1!"
---
apiVersion: v1
kind: Service
metadata:
name: app-v1-svc
spec:
selector:
app: app
version: v1
ports:
- protocol: TCP
port: 80
targetPort: 80
---
# application-v2.yaml (for traffic splitting, header matching)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-v2
labels:
app: app
version: v2
spec:
replicas: 2
selector:
matchLabels:
app: app
version: v2
template:
metadata:
labels:
app: app
version: v2
spec:
containers:
- name: app
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
env:
- name: MESSAGE
value: "Hello from App V2!"
---
apiVersion: v1
kind: Service
metadata:
name: app-v2-svc
spec:
selector:
app: app
version: v2
ports:
- protocol: TCP
port: 80
targetPort: 80
kubectl apply -f gateway.yaml
kubectl apply -f application-v1.yaml
kubectl apply -f application-v2.yaml
Verify
Ensure the Gateway is provisioned and has an IP address, and that the application pods are running.
kubectl get gateway my-gateway -o wide
kubectl get pods -l app=app
Expected Output:
# Example Gateway output (IP will vary)
NAME CLASS ADDRESS PROGRAMMED AGE
my-gateway nginx 192.168.1.10 True 2m
# Example Pods output
NAME READY STATUS RESTARTS AGE
app-v1-56789abcd-efghj 1/1 Running 0 1m
app-v1-56789abcd-klmno 1/1 Running 0 1m
app-v2-pqrstuvw-xyz12 1/1 Running 0 1m
app-v2-pqrstuvw-34567 1/1 Running 0 1m
Note the ADDRESS of your Gateway. This is the IP you’ll use for testing.
3. Basic Host and Path Matching
Before diving into advanced patterns, let’s establish a baseline with basic host and path matching. This `HTTPRoute` will route traffic for example.com and api.example.com to different services based on the host and path prefix.
# httproute-basic.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: basic-routing
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "example.com"
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: app-v2-svc
port: 80
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: app-v1-svc
port: 80
kubectl apply -f httproute-basic.yaml
Verify
You’ll need to simulate host headers for these tests, as we likely don’t have DNS configured for example.com pointing to our Gateway IP. Replace <GATEWAY_IP> with your Gateway’s external IP address.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
# Test host example.com, path /
curl -H "Host: example.com" http://$GATEWAY_IP/
curl -H "Host: example.com" http://$GATEWAY_IP/some/other/path
# Test host api.example.com, path /api/v1
curl -H "Host: api.example.com" http://$GATEWAY_IP/api/v1/users
curl -H "Host: api.example.com" http://$GATEWAY_IP/api/v1/products
Expected Output:
# For example.com requests
Hello from App V1!
# For api.example.com/api/v1 requests
Hello from App V2!
4. Header-Based Routing
Header-based routing allows you to direct traffic to different services based on specific HTTP headers present in the request. This is incredibly useful for A/B testing, feature flagging, or routing internal tools. For instance, you might route requests with a specific X-Version header to a new version of your application.
# httproute-header-matching.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-routing
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "app.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
headers:
- name: X-Version
value: v2
type: Exact # Match header exactly
backendRefs:
- name: app-v2-svc
port: 80
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- name: app-v1-svc
port: 80
kubectl apply -f httproute-header-matching.yaml
Verify
We’ll test sending requests with and without the X-Version: v2 header. Remember to use the `app.example.com` host header.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
# Request without X-Version header (should go to v1)
curl -H "Host: app.example.com" http://$GATEWAY_IP/api/v1/users
# Request with X-Version: v2 header (should go to v2)
curl -H "Host: app.example.com" -H "X-Version: v2" http://$GATEWAY_IP/api/v1/users
Expected Output:
# For request without header
Hello from App V1!
# For request with X-Version: v2 header
Hello from App V2!
5. Query Parameter-Based Routing
Similar to header matching, `HTTPRoute` can also match based on query parameters. This is useful for routing traffic based on specific parameters in the URL, such as feature flags (e.g., ?feature=new-ui) or debugging requests.
# httproute-query-param-matching.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: query-param-routing
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "debug.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /app
queryParams:
- name: debug
value: true
type: Exact # Match query param exactly
backendRefs:
- name: app-v2-svc # Route debug requests to v2
port: 80
- matches:
- path:
type: PathPrefix
value: /app
backendRefs:
- name: app-v1-svc
port: 80
kubectl apply -f httproute-query-param-matching.yaml
Verify
Test with and without the ?debug=true query parameter. Use the `debug.example.com` host header.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
# Request without debug query param (should go to v1)
curl -H "Host: debug.example.com" http://$GATEWAY_IP/app/data
# Request with debug=true query param (should go to v2)
curl -H "Host: debug.example.com" http://$GATEWAY_IP/app/data?debug=true
Expected Output:
# For request without query param
Hello from App V1!
# For request with debug=true query param
Hello from App V2!
6. Traffic Splitting (Weighted Round Robin)
Traffic splitting is a cornerstone of modern deployment strategies like canary releases and A/B testing. `HTTPRoute` allows you to distribute traffic across multiple backend services based on a weight, enabling gradual rollouts or comparing different versions. This is a powerful feature for reducing risk and validating new features in production. For advanced cluster scaling, consider tools like Karpenter for Cost Optimization.
# httproute-traffic-splitting.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: weighted-traffic
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "split.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: app-v1-svc
port: 80
weight: 80 # 80% of traffic to v1
- name: app-v2-svc
port: 80
weight: 20 # 20% of traffic to v2
kubectl apply -f httproute-traffic-splitting.yaml
Verify
Repeatedly send requests to the Gateway. You should observe responses from both `app-v1` and `app-v2`, with `app-v1` appearing more frequently. Use the `split.example.com` host header.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
echo "Testing weighted traffic split (80% to v1, 20% to v2):"
for i in $(seq 1 10); do
curl -s -H "Host: split.example.com" http://$GATEWAY_IP/
done
Expected Output:
# You should see a mix, predominantly "Hello from App V1!" with some "Hello from App V2!"
Hello from App V1!
Hello from App V1!
Hello from App V2!
Hello from App V1!
Hello from App V1!
Hello from App V1!
Hello from App V1!
Hello from App V2!
Hello from App V1!
Hello from App V1!
7. Request and Response Header Manipulation
The Gateway API allows you to modify HTTP request and response headers. This is incredibly powerful for injecting context, setting security policies, or transforming responses before they reach the client. For example, you might add a unique request ID, set CORS headers, or remove sensitive information.
# httproute-header-manipulation.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-manipulation
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "transform.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Request-ID
value: "generated-by-gateway"
set:
- name: X-App-Source
value: "gateway-api"
remove:
- User-Agent # Remove User-Agent header from request to backend
- type: ResponseHeaderModifier
responseHeaderModifier:
add:
- name: X-Gateway-Processed
value: "true"
set:
- name: Cache-Control
value: "no-cache, no-store, must-revalidate"
remove:
- X-Powered-By # Remove potentially revealing header from response
backendRefs:
- name: app-v1-svc
port: 80
kubectl apply -f httproute-header-manipulation.yaml
Verify
We’ll use curl -v to inspect the request and response headers. The NGINX backend application doesn’t directly show received request headers in its body, but the Gateway controller will process them. We will observe the *response* headers directly.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
# Make a request and observe response headers
curl -v -H "Host: transform.example.com" http://$GATEWAY_IP/
Expected Output (snippet from curl -v):
...
< HTTP/1.1 200 OK
< X-Gateway-Processed: true # Added by Gateway
< Cache-Control: no-cache, no-store, must-revalidate # Set by Gateway
< Date: ...
< Content-Type: text/plain
< Content-Length: 19
< Connection: keep-alive
<
Hello from App V1!
You should see X-Gateway-Processed: true and Cache-Control: no-cache, no-store, must-revalidate in the response headers. The X-Powered-By header (if present from the backend) should be removed. The request headers modified by the Gateway would be visible if you had a backend service that logs or reflects all incoming headers.
8. URL Rewriting
URL rewriting allows you to change the path of a request before it reaches the backend service. This is useful for exposing a service at a different path than its internal path, or for simplifying external URLs. For example, you might expose /public externally, but internally route it to /internal-api.
# httproute-url-rewrite.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: url-rewrite
spec:
parentRefs:
- name: my-gateway
namespace: default
hostnames:
- "rewrite.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /old-path
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-path # Rewrites /old-path to /new-path
backendRefs:
- name: app-v1-svc
port: 80
# Another rule for a different rewrite type (e.g., replace hostname)
- matches:
- path:
type: PathPrefix
value: /host-rewrite
filters:
- type: URLRewrite
urlRewrite:
hostname: "app-v2-svc.default.svc.cluster.local" # Rewrites hostname to internal service FQDN
path:
type: ReplaceFullPath
replaceFullPath: / # Routes to root of new hostname
backendRefs:
- name: app-v2-svc
port: 80
kubectl apply -f httproute-url-rewrite.yaml
Verify
The NGINX demo application doesn’t reflect the requested path in its output, so we need to infer the rewrite by checking which service responds. If /old-path routes to app-v1-svc and /host-rewrite routes to app-v2-svc, we know the rewrite is working as the target backend receives the modified request.
GATEWAY_IP=$(kubectl get gateway my-gateway -o jsonpath='{.status.addresses[0].value}')
# Test path rewrite: /old-path should go to app-v1-svc
curl -H "Host: rewrite.example.com" http://$GATEWAY_IP/old-path/some-subpath
# Test host and path rewrite: /host-rewrite should go to app-v2-svc
curl -H "Host: rewrite.example.com" http://$GATEWAY_IP/host-rewrite
Expected Output:
# For /old-path
Hello from App V1!
# For /host-rewrite
Hello from App V2!
Production Considerations
Implementing advanced `HTTPRoute` patterns in production requires careful planning and attention to several key areas:
- Observability: Robust monitoring and logging are crucial. Ensure your Gateway controller and backend services emit metrics (e.g., Prometheus) and logs that can be easily collected and analyzed. This is essential for understanding traffic flow, identifying issues, and validating routing rules. Tools leveraging eBPF for Observability with Hubble can provide deep network insights.
- Security:
- TLS Configuration: Always use HTTPS for production traffic. Configure TLS termination on your Gateway using `TLSRoute` or directly within `Gateway` listeners.
- Authorization: Integrate with external authorization systems (e.g., OPA, OAuth2 proxies) if `HTTPRoute`’s native capabilities are insufficient.
- Network Policies: Use Kubernetes Network Policies to restrict traffic flow between your Gateway controller and backend services, as well as between services themselves. Our Network Policies Security Guide provides comprehensive hardening strategies.
- Supply Chain Security: Ensure the Gateway controller images and configurations are secured. Projects like Sigstore and Kyverno can help enforce image signing and policy-as-code for your deployments.
- Performance & Scalability:
- Gateway Controller Sizing: Properly size your Gateway controller instances (CPU, memory) to handle anticipated traffic loads.
- Horizontal Scaling: Ensure your Gateway controller can scale horizontally to meet demand.
- Backend Scaling: Implement Horizontal Pod Autoscalers (HPAs) for your backend services to ensure they can handle the routed traffic.
- Testing Strategy:
- Unit/Integration Tests: Test your `HTTPRoute` configurations in development environments.
- Automated E2E Tests: Implement end-to-end tests that hit your Gateway and verify correct routing and responses.
- Canary/A/B Testing: Leverage `HTTPRoute`’s traffic splitting capabilities for safe, gradual rollouts and experimentation.
- Backup and Disaster Recovery: Regularly back up your Kubernetes cluster state, including Gateway API resources. Plan for how to restore your traffic routing in case of a disaster.
- GitOps: Manage your Gateway API resources (
GatewayClass,Gateway,HTTPRoute, etc.) using GitOps principles. Store all configurations in a Git repository and use tools like Argo CD or Flux CD for automated deployment and synchronization.
Troubleshooting
Here are some common issues you might encounter when working with Gateway API `HTTPRoute` and their solutions.
1. Gateway Not Provisioned / Pending Address
Issue: Your `Gateway` resource remains in `Pending` status or doesn’t get an `ADDRESS` assigned.
Solution:
- Check GatewayClass: Ensure the `GatewayClass` specified in your `Gateway` exists and its `controllerName` matches the controller you installed (e.g., `nginx.org/gateway-controller`).
- Check Controller Pods: Verify that your Gateway controller pods are running without errors in their designated namespace (`nginx-gateway` in our example).
kubectl get gatewayclass kubectl get pods -n nginx-gateway kubectl logs -f -n nginx-gateway <nginx-gateway-fabric-pod-name> - Service Type: If running on a cloud provider, ensure your controller’s service (often a `LoadBalancer` type) can provision an external IP. Check cloud provider logs if the `LoadBalancer` creation fails. If local (Minikube, Kind), ensure your environment supports `LoadBalancer` services (e.g., Minikube’s tunnel or MetalLB for Kind).
2. HTTPRoute Not Attaching to Gateway
Issue: Your `HTTPRoute` doesn’t seem to be routing traffic, and its `status.parents` field might show `Programmed: False` or `Accepted: False`.
Solution:
- `parentRefs` Mismatch: Verify that the `parentRefs` in your `HTTPRoute` correctly specify the `name` and `namespace` of your `Gateway`.
- `allowedRoutes` Configuration: Check the `allowedRoutes` section of your `Gateway` listener. It dictates which namespaces can attach `HTTPRoute`s. If `from: Same` is used, the `HTTPRoute` must be in the same namespace as the `Gateway`. `from: All` allows any namespace.
kubectl describe httproute <your-httproute-name> kubectl describe gateway <my-gateway> - Hostname Conflicts: Ensure there are no conflicting `HTTPRoute`s claiming the same hostname/path combination with higher precedence.
3. Traffic Not Reaching Backend Service
Issue:
