Kubernetes Gateway API with HTTPRoute Examples

Route HTTP and HTTPS traffic with Kubernetes Gateway API using GatewayClass, Gateway, and HTTPRoute for path matching, hostnames, weighted backends, TLS, and ReferenceGrant.

Published

Updated

Read time 14 min read

Reviewed byDeepak Prasad

GatewayClass, Gateway, and HTTPRoute directing traffic to backend Services
Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Gateway API CRDs (gateway.networking.k8s.io/v1)
Envoy Gateway v1.5.0 (implementation example)
Applies to Ubuntu, Debian, Kali Linux, Linux Mint, Pop!_OS, Raspberry Pi OS, elementary OS, Zorin OS, Parrot OS, MX Linux, RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora
Kubernetes cluster
Cert prep CKA
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm. This walkthrough uses Envoy Gateway with a NodePort Envoy Service because the lab has no cloud LoadBalancer.
Privilege Normal user for kubectl when kubeconfig is available
Scope Gateway API CRDs and controller prerequisites, GatewayClass, Gateway listeners, HTTPRoute parentRefs and backendRefs, path and hostname matching, weighted backends, HTTPS termination with a TLS Secret, and a brief ReferenceGrant example. Does not cover installing every Gateway controller, experimental route types, service mesh mode, advanced filters, auth extensions, provider LoadBalancer annotations, full Ingress migration, or controller development.
Related guides Kubernetes Services
Kubernetes networking model
CRDs and custom resources

Gateway API is the role-oriented way to publish HTTP services outside a cluster: a GatewayClass from your infrastructure, a Gateway that owns listeners, and HTTPRoutes that attach routes to those listeners. This walkthrough verifies an installed implementation, creates one Gateway, then attaches path, hostname, weighted, and TLS routes against two echo backends.

IMPORTANT
This article covers using Gateway API after a compatible controller is already installed. It does not walk through installing every Gateway implementation. Installing CRDs alone is not enough — you need a controller that creates a GatewayClass and programs a data plane. The lab examples use Envoy Gateway; the same Gateway and HTTPRoute YAML apply to other conforming implementations with their own GatewayClass name and address provisioning.

Gateway API is not built into every cluster

Gateway API resources ship as Custom Resource Definitions under gateway.networking.k8s.io. A fresh kubeadm cluster does not include them by default, and even with CRDs present you still need a controller (implementation) that:

  • Watches GatewayClass, Gateway, and Route objects
  • Publishes one or more GatewayClass names you can reference
  • Programs a data plane (proxy or load balancer) and updates Gateway status

Without that controller, kubectl apply of a Gateway may leave the object stuck without Accepted / Programmed, and no address appears for clients.

Role separation in the API matches how teams usually work:

Role Owns
Infrastructure provider Controller install and GatewayClass
Cluster operator Gateway listeners, allowed namespaces, certificates
Application developer HTTPRoute rules and backendRefs to Services

Keep controller install outside this tutorial. Once a GatewayClass shows Accepted=True, the rest of the article is portable YAML.


Understand the resource model

Traffic flows through these objects:

text
GatewayClass
   ↓ implemented by controller
Gateway
   ↓ owns listeners
HTTPRoute
   ↓ attaches through parentRefs
Service
Backend Pods
  • GatewayClass — cluster-scoped template that names the controller (spec.controllerName).
  • Gateway — namespaced entry point: which class, which ports and protocols, which hostnames, which namespaces may attach Routes.
  • HTTPRoute — application routing: parentRefs to a Gateway, optional hostnames, match rules, and backendRefs to Services.
  • Service — stable selector for backend Pods; Gateway API does not replace Services.

Verify Gateway API and its controller

Confirm the standard CRDs exist before you write YAML:

bash
kubectl get crd | grep gateway.networking.k8s.io
output
backendtlspolicies.gateway.networking.k8s.io            2026-07-27T14:17:42Z
gatewayclasses.gateway.networking.k8s.io                2026-07-27T14:17:42Z
gateways.gateway.networking.k8s.io                      2026-07-27T14:17:42Z
grpcroutes.gateway.networking.k8s.io                    2026-07-27T14:17:42Z
httproutes.gateway.networking.k8s.io                    2026-07-27T14:17:42Z
referencegrants.gateway.networking.k8s.io               2026-07-27T14:17:42Z
tcproutes.gateway.networking.k8s.io                     2026-07-27T14:17:42Z
tlsroutes.gateway.networking.k8s.io                     2026-07-27T14:17:42Z
udproutes.gateway.networking.k8s.io                     2026-07-27T14:17:42Z

httproutes, gateways, gatewayclasses, and referencegrants are the ones this article uses. Extra route kinds can sit unused.

List GatewayClasses and check that one is Accepted:

bash
kubectl get gatewayclass
output
NAME   CONTROLLER                                      ACCEPTED   AGE
eg     gateway.envoyproxy.io/gatewayclass-controller   True       5m40s

CONTROLLER is the implementation identity. ACCEPTED=True means that controller validated the class.

Inspect status and the controller name in more detail:

bash
kubectl describe gatewayclass eg | sed -n '/Status:/,$p'
output
Status:
  Conditions:
    Last Transition Time:  2026-07-27T14:20:02Z
    Message:               Valid GatewayClass
    Observed Generation:   1
    Reason:                Accepted
    Status:                True
    Type:                  Accepted
Events:                    <none>

Confirm controller Pods are running. On this lab they live in envoy-gateway-system:

bash
kubectl get pods -n envoy-gateway-system
output
NAME                                                          READY   STATUS    RESTARTS   AGE
envoy-gateway-5cd69fdc84-hnhmb                                1/1     Running   0          7m15s
envoy-gateway-demo-demo-gateway-449da5df-754495df67-24gq6     2/2     Running   0          5m29s

The first Pod is the control plane. Additional Pods appear after you create Gateways — one data-plane replica set per Gateway on this implementation. Small labs can run out of memory if you create several Gateways at once.

NOTE
This lab points GatewayClass eg at an EnvoyProxy that sets envoyService.type: NodePort so clients can reach the Gateway without a cloud LoadBalancer. Cloud clusters often get a LoadBalancer Service and an EXTERNAL-IP instead. Use whichever address your Gateway status reports.

Prepare backend Services

Create a namespace and two echo Deployments so path and host demos return different Pod names. The echo image prints JSON including namespace and pod, which makes routing easy to verify.

bash
kubectl create namespace gateway-demo
output
namespace/gateway-demo created

Apply the web backend:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: echo
        image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
        ports:
        - containerPort: 3000
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 3000
EOF
output
deployment.apps/web created
service/web created

Apply the api backend the same way, with labels and names set to api:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: echo
        image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
        ports:
        - containerPort: 3000
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 3000
EOF
output
deployment.apps/api created
service/api created

Wait until both Deployments are ready, then confirm ClusterIP reachability from inside the cluster:

bash
kubectl -n gateway-demo get deploy,svc
output
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/api   1/1     1            1           8m30s
deployment.apps/web   1/1     1            1           8m30s

NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/api   ClusterIP   10.97.193.10    <none>        80/TCP    8m30s
service/web   ClusterIP   10.103.12.174   <none>        80/TCP    8m30s

Both Services are Available. Spot-check with a Job that curls each ClusterIP:

bash
kubectl -n gateway-demo create job web-check --image=curlimages/curl:8.5.0 -- curl -sS http://web/
output
job.batch/web-check created

Read the Job logs after a few seconds:

bash
kubectl -n gateway-demo logs job/web-check
output
{
 "path": "/",
 "host": "web",
 "method": "GET",
 "proto": "HTTP/1.1",
 "headers": {
  "Accept": [
   "*/*"
  ],
  "User-Agent": [
   "curl/8.5.0"
  ]
 },
 "namespace": "gateway-demo",
 "ingress": "",
 "service": "",
 "pod": "web-8cddb5978-zj7np"
}

The pod field starts with web-, so the Service selector is healthy. Repeat with api-check against http://api/ if you want the same proof for the second backend. Do not dig into Service troubleshooting here — fix selector and port problems before attaching Routes.


Create a Gateway

Create a Gateway that uses class eg, listens for HTTP on port 80, and only allows Routes from the same namespace:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: demo-gateway
spec:
  gatewayClassName: eg
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
EOF
output
gateway.gateway.networking.k8s.io/demo-gateway created

Watch until PROGRAMMED is True:

bash
kubectl -n gateway-demo get gateway demo-gateway
output
NAME           CLASS   ADDRESS          PROGRAMMED   AGE
demo-gateway   eg      192.168.56.109   True         7m46s

ADDRESS shows node IPs on this NodePort lab. Cloud LoadBalancer implementations usually show a single external address instead.

Read the conditions that matter for day-two checks:

bash
kubectl -n gateway-demo get gateway demo-gateway -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason} {"\n"}{end}'
output
Accepted=True Accepted 
Programmed=True Programmed

Accepted means the controller took the Gateway. Programmed=True means the configuration was successfully sent to the data plane and should become ready soon. Check the Gateway ADDRESS field separately for a usable address. Also check listener status with kubectl describe gateway when Routes fail to attach — look for listener Accepted, Programmed, and ResolvedRefs.

Find the NodePort clients should use on this lab:

bash
kubectl -n envoy-gateway-system get svc -l gateway.envoyproxy.io/owning-gateway-name=demo-gateway
output
NAME                                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
envoy-gateway-demo-demo-gateway-449da5df   NodePort   10.103.124.64   <none>        80:31074/TCP   7m16s

Here external HTTP is http://<node-ip>:31074. Your NodePort number will differ.


Create a basic HTTPRoute

Attach a Route that sends all matching traffic to web through parentRefs:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web-basic
spec:
  parentRefs:
  - name: demo-gateway
  rules:
  - backendRefs:
    - name: web
      port: 80
EOF
output
httproute.gateway.networking.k8s.io/web-basic created

Confirm parent conditions:

bash
kubectl -n gateway-demo describe httproute web-basic | sed -n '/Status:/,$p'
output
Status:
  Parents:
    Conditions:
      Last Transition Time:  2026-07-27T14:30:12Z
      Message:               Route is accepted
      Observed Generation:   1
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
      Last Transition Time:  2026-07-27T14:30:12Z
      Message:               Resolved all the Object references for the Route
      Observed Generation:   1
      Reason:                ResolvedRefs
      Status:                True
      Type:                  ResolvedRefs
    Controller Name:         gateway.envoyproxy.io/gatewayclass-controller
    Parent Ref:
      Group:  gateway.networking.k8s.io
      Kind:   Gateway
      Name:   demo-gateway
Events:       <none>

Both Accepted and ResolvedRefs should be True. Then curl the Gateway address (replace the NodePort if yours differs):

Manifest and API checks with curl are covered in the curl command guide.

bash
curl -sS http://192.168.56.109:31074/ | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["namespace"], d["pod"])'
output
gateway-demo web-8cddb5978-zj7np

Traffic reached the web Pod through the Gateway. Delete this catch-all before the path demo so rules do not compete:

bash
kubectl -n gateway-demo delete httproute web-basic
output
httproute.gateway.networking.k8s.io "web-basic" deleted from gateway-demo namespace

Route by path

Create one HTTPRoute with two rules. /api is listed first for readability, but its position does not make it win. Gateway API selects the longest matching PathPrefix, so /api takes precedence over / regardless of rule order.

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: site-paths
spec:
  parentRefs:
  - name: demo-gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api
    backendRefs:
    - name: api
      port: 80
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web
      port: 80
EOF
output
httproute.gateway.networking.k8s.io/site-paths created

Common path match types:

Type Behavior
PathPrefix Matches the path and everything under it
Exact Matches only that exact path
RegularExpression Implementation-dependent regex matching

This first example stays on PathPrefix and does not use rewrite filters.

Curl /api and read which Pod answered:

bash
curl -sS http://192.168.56.109:31074/api | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["path"], d["namespace"], d["pod"])'
output
/api gateway-demo api-d85c97648-whc5t

The api Pod handled /api. Curl / next:

bash
curl -sS http://192.168.56.109:31074/ | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["path"], d["namespace"], d["pod"])'
output
/ gateway-demo web-8cddb5978-zj7np

The web Pod handled /. If a real app returns 404 on a prefixed path, the Route still worked — the backend rejected the path. Echo backends avoid that confusion for demos.


Route by hostname

Hostname selection is the intersection of three things:

  • The Gateway listener hostname (if set)
  • The HTTPRoute hostnames list
  • The HTTP Host header (or TLS SNI for HTTPS)

When the listener omits hostname, any Route hostname can attach. DNS (or /etc/hosts / curl -H 'Host: ...') must still send the name you intend.

Create two Routes on the same Gateway:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: www-host
spec:
  parentRefs:
  - name: demo-gateway
  hostnames:
  - www.lab.example
  rules:
  - backendRefs:
    - name: web
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-host
spec:
  parentRefs:
  - name: demo-gateway
  hostnames:
  - api.lab.example
  rules:
  - backendRefs:
    - name: api
      port: 80
EOF
output
httproute.gateway.networking.k8s.io/www-host created
httproute.gateway.networking.k8s.io/api-host created

Send the Host header without changing DNS:

bash
curl -sS -H 'Host: www.lab.example' http://192.168.56.109:31074/ | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["host"], d["pod"])'
output
www.lab.example web-8cddb5978-zj7np
bash
curl -sS -H 'Host: api.lab.example' http://192.168.56.109:31074/ | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["host"], d["pod"])'
output
api.lab.example api-d85c97648-whc5t

Same Gateway address, different backends based on Host. If a Route hostname never intersects the listener hostname, the Route stays unattached even when YAML looks valid.


Split traffic between backends

Weighted backendRefs are a built-in Gateway API capability for canary-style splits. Create a Route that prefers web at weight 70 and api at weight 30:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: split-traffic
spec:
  parentRefs:
  - name: demo-gateway
  hostnames:
  - split.lab.example
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: web
      port: 80
      weight: 70
    - name: api
      port: 80
      weight: 30
EOF
output
httproute.gateway.networking.k8s.io/split-traffic created

Sample ten requests and print the Pod name only:

bash
for i in 1 2 3 4 5 6 7 8 9 10; do curl -sS -H 'Host: split.lab.example' http://192.168.56.109:31074/ | python3 -c 'import sys,json; print(json.load(sys.stdin)["pod"])'; done
output
api-d85c97648-whc5t
web-8cddb5978-zj7np
web-8cddb5978-zj7np
api-d85c97648-whc5t
web-8cddb5978-zj7np
api-d85c97648-whc5t
web-8cddb5978-zj7np
web-8cddb5978-zj7np
api-d85c97648-whc5t
web-8cddb5978-zj7np

Both backends appear. Do not expect exactly seven web hits in ten curls — short samples are noisy, and implementations may deviate slightly from the configured ratio. Treat weights as relative preference, not a promise of exact percentages.


Configure HTTPS

TLS termination lives on the Gateway listener. The certificate Secret must be in a namespace the Gateway is allowed to read — typically the Gateway namespace for same-namespace refs.

Create a lab TLS Secret (self-signed is fine for demos; production uses a real issuer):

bash
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
  -keyout /tmp/tls.key -out /tmp/tls.crt \
  -subj "/CN=secure.lab.example" \
  -addext "subjectAltName=DNS:secure.lab.example"

The command writes /tmp/tls.key and /tmp/tls.crt with no stdout on success.

bash
kubectl -n gateway-demo create secret tls secure-lab-tls --cert=/tmp/tls.crt --key=/tmp/tls.key
output
secret/secure-lab-tls created

Create an HTTPS Gateway that terminates TLS with that Secret:

bash
kubectl -n gateway-demo apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: secure-gateway
spec:
  gatewayClassName: eg
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: secure.lab.example
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: secure-lab-tls
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: secure-web
spec:
  parentRefs:
  - name: secure-gateway
  hostnames:
  - secure.lab.example
  rules:
  - backendRefs:
    - name: web
      port: 80
EOF
output
gateway.gateway.networking.k8s.io/secure-gateway created
httproute.gateway.networking.k8s.io/secure-web created

Wait until Programmed is True. On small labs, free memory first if a previous Gateway left the proxy Pod Pending (NoResources):

bash
kubectl -n gateway-demo get gateway secure-gateway
output
NAME             CLASS   ADDRESS          PROGRAMMED   AGE
secure-gateway   eg      192.168.56.108   True         3m28s

Find the HTTPS NodePort and curl with --resolve so TLS hostname matching works without DNS:

bash
kubectl -n envoy-gateway-system get svc -l gateway.envoyproxy.io/owning-gateway-name=secure-gateway
output
NAME                                         TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)         AGE
envoy-gateway-demo-secure-gateway-af8bf1ed   NodePort   10.98.26.20   <none>        443:32385/TCP   4m31s
bash
curl -skS --resolve secure.lab.example:32385:192.168.56.109 https://secure.lab.example:32385/ | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["host"], d["namespace"], d["pod"])'
output
secure.lab.example:32385 gateway-demo web-8cddb5978-zj7np

TLS terminated at the Gateway; the backend still spoke plain HTTP on port 80. For more on creating TLS Secrets, see Kubernetes Secrets.


Cross-namespace route attachment

Two separate controls matter:

  • Route attachment — Gateway allowedRoutes.namespaces decides which namespaces may attach HTTPRoutes to a listener (Same, All, or a selector).
  • Backend references — an HTTPRoute that points at a Service in another namespace needs a ReferenceGrant in the backend namespace. Cross-namespace Service access is not automatic.

Optional lab: create gateway-apps with a remote-web Service, then an HTTPRoute in gateway-demo that references it.

Without a ReferenceGrant, ResolvedRefs fails:

output
Message:  Failed to process route rule 0 backendRef 0: Backend ref to Service gateway-apps/remote-web not permitted by any ReferenceGrant.
Reason:   RefNotPermitted
Status:   False
Type:     ResolvedRefs

Grant the reference from the backend namespace:

bash
kubectl -n gateway-apps apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-demo
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: gateway-demo
  to:
  - group: ""
    kind: Service
EOF
output
referencegrant.gateway.networking.k8s.io/allow-gateway-demo created

After the grant, ResolvedRefs becomes True and curls with Host: cross.lab.example reach the remote namespace Pod (namespace=gateway-apps in the echo JSON). Keep this as an optional pattern — most app Routes stay in the same namespace as their Services.


Gateway API vs Ingress

Gateway API Ingress
Multiple role-oriented resources One main Ingress resource
Extensible route types (HTTPRoute and others) HTTP/HTTPS-focused
Explicit status and attachment conditions Controller-dependent extensions
Rich listener and delegation model Simpler application routing model

Ingress remains common and exam-relevant. For host, path, and TLS examples on Ingress, use Expose services with Ingress. Gateway API is the newer portable model when your cluster has a conforming implementation.


Common Gateway API problems

Symptom Likely cause Fix
No gateway.networking.k8s.io CRDs CRDs not installed Install Gateway API CRDs from the release your controller supports
No GatewayClass, or never Accepted No compatible controller, or wrong controllerName Install/configure an implementation; match gatewayClassName to an Accepted class
Gateway Accepted but not Programmed Data plane pending, no address, or NoResources Check controller logs and proxy Pods; free capacity; wait for LoadBalancer/NodePort
HTTPRoute not Accepted Wrong parentRef, listener mismatch, or namespace not allowed Align parentRefs, listener protocol/port/hostname, and allowedRoutes
Hostnames never match Route hostname does not intersect listener hostname Align listener hostname, Route hostnames, and client Host/SNI
ResolvedRefs false on same-namespace Service Missing Service or wrong port Fix backendRefs name and port; confirm Endpoints exist
Cross-namespace backend RefNotPermitted No ReferenceGrant Create ReferenceGrant in the Service namespace
Gateway address empty Provider did not allocate LB/NodePort Check implementation Service; configure LB or NodePort as required
404 from backend on a matching path App rejects the path; Route may still be correct Confirm with an echo backend or app-aware rewrites (out of scope here)

What's Next


References


Summary

You verified Gateway API CRDs and an Accepted GatewayClass, then published traffic through a Gateway and HTTPRoutes instead of a single Ingress object. The useful status checks are the same every time: Gateway Accepted and Programmed, HTTPRoute Accepted and ResolvedRefs, then curl through the address your implementation assigned.

Path and hostname rules are portable across conforming controllers; weighted backends are a short canary tool, not a promise of exact percentages from a few curls. HTTPS terminates on the Gateway listener with a Secret reference, and cross-namespace Service backends need an explicit ReferenceGrant. When a Route looks correct but never attaches, compare listener hostname, allowed namespaces, and parentRefs before blaming the backend.

Next, keep practicing on the same kubeadm lab: tighten listener hostnames for production-like DNS, or compare the same host and path rules on Ingress using the Ingress guide linked above. Clean up demo namespaces when you are done so leftover Gateways do not consume proxy capacity on small clusters.


Frequently Asked Questions

1. Are Gateway API resources built into every Kubernetes cluster?

No. Gateway API resources arrive as CRDs, and you still need a compatible controller that implements them. Installing the CRDs alone does not create a working data plane or publish a Gateway address.

2. What is the difference between GatewayClass and Gateway?

GatewayClass is cluster-scoped and names the controller that implements a class of gateways. A Gateway is namespaced, references one GatewayClass, and defines listeners such as HTTP on port 80 or HTTPS on port 443.

3. Why is my HTTPRoute Accepted but traffic still fails?

Accepted means the parent Gateway accepted the attachment. Traffic can still fail when ResolvedRefs is false, hostnames do not intersect the listener, the backend Service or port is wrong, or the Gateway has no usable address yet.

4. Do weighted backendRefs guarantee exact percentages in a short curl loop?

No. Weights express relative preference. Short samples are implementation-dependent and can look uneven. Use weights for intended share, not for proving exact percentages from a handful of requests.

5. When do I need a ReferenceGrant?

Use ReferenceGrant when an HTTPRoute in one namespace references a Service in another namespace. Cross-namespace backend access is denied until the backend namespace explicitly grants that reference.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)