Lab Guide
Hands-on exercises deploying Gateway API resources and templating them in Helm charts.
Prerequisites
- A running Kubernetes cluster (kind, minikube, or cloud-managed)
kubectlandhelmCLI installed- Cluster admin access to install CRDs
Setup: Install Gateway API CRDs
Before creating any Gateway API resources, install the CRDs. We will use the standard channel for GA resources and the experimental channel for TLSRoute.
# Install the experimental channel (superset of standard)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml
# Verify CRDs are installed
kubectl get crds | grep gateway.networking.k8s.io
You should see gatewayclasses, gateways, httproutes, referencegrants, and tlsroutes among the results.
Note: These exercises focus on the resource manifests themselves. Without a Gateway API controller (Envoy Gateway, Istio, etc.) installed, the resources will be created but not reconciled. You can optionally install Envoy Gateway to see the full data-plane behavior:
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.2.0 -n envoy-gateway-system --create-namespace
Deploy GatewayClass, Gateway, and HTTPRoute
Create the three core Gateway API resources and connect them end to end.
Step 1 — Create a namespace and sample backend
kubectl create namespace web-apps
# Deploy a simple echo server as the backend
kubectl -n web-apps create deployment echo --image=hashicorp/http-echo \
-- -text="Hello from Gateway API"
kubectl -n web-apps expose deployment echo --port=80 --target-port=5678
Step 2 — Create a GatewayClass
Save the following as gatewayclass.yaml and apply it:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: example-gc
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
kubectl apply -f gatewayclass.yaml
kubectl get gatewayclasses
Step 3 — Create a Gateway
Save the following as gateway.yaml and apply it:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: lab-gateway
namespace: web-apps
spec:
gatewayClassName: example-gc
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
kubectl apply -f gateway.yaml
kubectl -n web-apps get gateways
Step 4 — Create an HTTPRoute
Save the following as httproute.yaml and apply it:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: web-apps
spec:
parentRefs:
- name: lab-gateway
hostnames:
- "echo.lab.local"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo
port: 80
kubectl apply -f httproute.yaml
kubectl -n web-apps get httproutes
Verification
# Check that the HTTPRoute is accepted by the Gateway
kubectl -n web-apps describe httproute echo-route
# Look for the "Accepted" and "ResolvedRefs" conditions — both should be True
kubectl -n web-apps get httproute echo-route -o jsonpath='{.status.parents[*].conditions[*].type}'
If Envoy Gateway is installed, you can also port-forward and test:
kubectl -n web-apps port-forward svc/lab-gateway 8080:80 &
curl -H "Host: echo.lab.local" http://localhost:8080/
HTTPRoute Filters and Traffic Splitting
Extend the routing setup with header modification, URL rewriting, and canary traffic splitting.
Step 1 — Deploy a second backend version
kubectl -n web-apps create deployment echo-v2 --image=hashicorp/http-echo \
-- -text="Hello from v2"
kubectl -n web-apps expose deployment echo-v2 --port=80 --target-port=5678
Step 2 — Create a route with filters and traffic splitting
Save the following as httproute-filters.yaml and apply it:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-canary-route
namespace: web-apps
spec:
parentRefs:
- name: lab-gateway
hostnames:
- "canary.lab.local"
rules:
# Rule 1: Header-based canary — x-canary: true goes to v2
- matches:
- headers:
- name: x-canary
value: "true"
filters:
- type: RequestHeaderModifier
requestHeaderModifier:
set:
- name: x-routed-to
value: "v2-canary"
backendRefs:
- name: echo-v2
port: 80
# Rule 2: Weighted split — 90/10
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo
port: 80
weight: 90
- name: echo-v2
port: 80
weight: 10
# Rule 3: URL rewrite for /legacy path
- matches:
- path:
type: PathPrefix
value: /legacy
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: echo
port: 80
kubectl apply -f httproute-filters.yaml
kubectl -n web-apps describe httproute echo-canary-route
Verification
# Confirm all three rules are present
kubectl -n web-apps get httproute echo-canary-route -o yaml | grep -A2 "type:"
# If Envoy Gateway is running, test each rule:
# Header-based canary
curl -H "Host: canary.lab.local" -H "x-canary: true" http://localhost:8080/
# Weighted split (run multiple times to see distribution)
for i in $(seq 1 20); do
curl -s -H "Host: canary.lab.local" http://localhost:8080/
done | sort | uniq -c
# URL rewrite
curl -H "Host: canary.lab.local" http://localhost:8080/legacy
Cross-Namespace Routing with ReferenceGrant and TLSRoute
Route traffic from one namespace to a Service in another using ReferenceGrant, and set up a TLSRoute for passthrough traffic.
Step 1 — Create a backend namespace and service
kubectl create namespace backend-services
kubectl -n backend-services create deployment api --image=hashicorp/http-echo \
-- -text="Hello from backend namespace"
kubectl -n backend-services expose deployment api --port=80 --target-port=5678
Step 2 — Create an HTTPRoute that references a cross-namespace Service
Save as httproute-crossns.yaml:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: crossns-route
namespace: web-apps
spec:
parentRefs:
- name: lab-gateway
hostnames:
- "api.lab.local"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api
namespace: backend-services
port: 80
kubectl apply -f httproute-crossns.yaml
# Check status — ResolvedRefs should be False (no ReferenceGrant yet)
kubectl -n web-apps describe httproute crossns-route
Step 3 — Create a ReferenceGrant
Save as referencegrant.yaml (note: deployed in the target namespace):
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-web-to-backend
namespace: backend-services
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: web-apps
to:
- group: ""
kind: Service
kubectl apply -f referencegrant.yaml
# Now check again — ResolvedRefs should be True
kubectl -n web-apps describe httproute crossns-route
Step 4 — Create a TLSRoute for passthrough
Add a TLS passthrough listener to the Gateway, then create a TLSRoute. Save as gateway-tls.yaml:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: lab-gateway
namespace: web-apps
spec:
gatewayClassName: example-gc
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
- name: tls-passthrough
port: 443
protocol: TLS
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: Same
Save the TLSRoute as tlsroute.yaml:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: tls-passthrough-route
namespace: web-apps
spec:
parentRefs:
- name: lab-gateway
sectionName: tls-passthrough
hostnames:
- "secure.lab.local"
rules:
- backendRefs:
- name: echo
port: 443
kubectl apply -f gateway-tls.yaml
kubectl apply -f tlsroute.yaml
kubectl -n web-apps get tlsroutes
Verification
# Confirm the ReferenceGrant is working
kubectl -n web-apps get httproute crossns-route -o jsonpath='{.status.parents[*].conditions[?(@.type=="ResolvedRefs")].status}'
# Expected: True
# List all Gateway API resources across namespaces
kubectl get gatewayclasses,gateways,httproutes,tlsroutes,referencegrants -A
# Verify the Gateway has both listeners
kubectl -n web-apps get gateway lab-gateway -o jsonpath='{.spec.listeners[*].name}'
# Expected: http tls-passthrough
Cleanup
kubectl delete namespace web-apps backend-services
kubectl delete gatewayclass example-gc