Lesson 11 Gateway API
Theme:

Lab Guide

Hands-on exercises deploying Gateway API resources and templating them in Helm charts.

Prerequisites

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
Exercise 1

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/
Exercise 2

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
Exercise 3

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