Skip to main content
  1. Posts/
  2. Learning ArgoCD/
  3. Platform & Infrastructure/

Gateway API: HTTPRoute + Gateway on eu-dev-rancher

·1004 words·5 mins
Ravi Singh
Author
Ravi Singh
Software engineer with 15+ years building backend systems and cloud platforms across fintech, automotive, and academia. I write about the things I build, debug, and learn — so I don’t forget them.
Learning ArgoCD - This article is part of a series.
Part 3: This Article

Gateway API: HTTPRoute + Gateway on eu-dev-rancher
#

What This Covers
#

Why Kubernetes Gateway API supersedes Ingress, how to set it up with Traefik, and how to deploy a service (svc3) using HTTPRoute instead of Ingress. Also covers the repo restructure that introduced platform/ as the home for shared cross-cutting config.


Why Gateway API?
#

Ingress was designed for one simple use case - route HTTP(S) traffic to a backend by hostname or path. Any feature beyond that required controller-specific annotations, making configurations non-portable and hard to read.

Gateway API introduces a layered model:

1
GatewayClass  →  Gateway  →  HTTPRoute
ResourceWho owns itWhat it does
GatewayClassCluster adminBinds a controller (e.g. Traefik) to this class
GatewayInfra teamDeclares a listener (port, protocol, TLS)
HTTPRouteApp teamDefines routing rules: host, path, headers → backend

Key improvements over Ingress:

  • No annotations - features like redirects, header matching, and URL rewrites are first-class fields, not controller-specific annotations
  • Native traffic splitting - backendRefs with weight per backend; no CRD hacks
  • Cross-namespace routing - HTTPRoute in alpha-dev can attach to a Gateway in ingress namespace; controlled via allowedRoutes
  • Role separation - Gateway and HTTPRoute are separate RBAC targets; platform team owns the Gateway, app team owns the route

Ingress vs HTTPRoute side by side
#

svc1 - Ingress (existing)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web  # controller-specific
spec:
  ingressClassName: traefik
  rules:
    - host: svc1.eu-dev-rancher.ravikrs.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: svc1
                port:
                  number: 80

svc3 - HTTPRoute (Gateway API)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
spec:
  parentRefs:
    - name: traefik        # which Gateway handles this route
      namespace: ingress
      sectionName: web     # which listener on that Gateway
  hostnames:
    - svc3.eu-dev-rancher.ravikrs.local
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: svc3
          port: 80
          weight: 1        # explicit - add a second backendRef to do canary splits

sectionName: web replaces the traefik.ingress.kubernetes.io/router.entrypoints: web annotation. Portable, readable, no controller knowledge required.


Repo Structure
#

New platform/ directory
#

This session also formalised the distinction between shared config and per-cluster config, replacing the old config/ folder with platform/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
platform/
  cert-manager/          ← shared: CA cert + issuers; used by all envs
    kustomization.yaml
    self-signed-issuer.yaml
    ca-cert.yaml
    ca-issuer.yaml
  gateway-api/
    crds/
      kustomization.yaml ← pulls upstream CRDs via Kustomize remote ref
    gatewayclass.yaml    ← shared: same for every Traefik env
    kustomization.yaml

Rule: platform/ holds resources that are identical across all clusters. Per-cluster resources (a Gateway with a specific namespace and listener config) go under environments/<cluster>/.

Gateway API layout in eu-dev-rancher
#

1
2
3
4
5
6
7
environments/eu-dev-rancher/
  gateway-api/
    gateway.yaml         ← env-specific: namespace=ingress, listener=web:80
    kustomization.yaml
  platform/
    gateway-api-crds.yaml     # wave 1 → platform/gateway-api/crds
    gateway-api-config.yaml   # wave 3 → platform/gateway-api + eu-dev-rancher/gateway-api

gateway-api-config is a multi-source Application - it combines the shared GatewayClass from platform/gateway-api with the env-specific Gateway from environments/eu-dev-rancher/gateway-api in a single sync:

1
2
3
sources:
  - path: platform/gateway-api            # GatewayClass
  - path: environments/eu-dev-rancher/gateway-api  # Gateway

Wave Ordering (updated)
#

1
2
3
4
5
6
7
8
Wave 0: cert-manager           Helm install
Wave 1: cert-manager-config    platform/cert-manager → CA chain
        gateway-api-crds       platform/gateway-api/crds → upstream CRDs
        reloader
Wave 2: traefik                Helm install (Gateway API provider enabled)
Wave 3: argocd-config          ArgoCD server config
        gateway-api-config     GatewayClass (platform/) + Gateway (env-specific)
Wave 4: appset                 Generates svc1, svc2, svc3

Gateway API CRDs land at wave 1 - before Traefik starts - so the kubernetesGateway provider can register on startup without needing a restart.


Key Config Changes
#

Traefik - enable Gateway API provider
#

1
2
3
4
# environments/eu-dev-rancher/platform/traefik.yaml
providers:
  kubernetesGateway:
    enabled: true

Without this, Traefik silently ignores all Gateway and HTTPRoute resources.

Gateway - allow cross-namespace routes
#

1
2
3
4
5
6
7
# environments/eu-dev-rancher/gateway-api/gateway.yaml
spec:
  listeners:
    - name: web
      allowedRoutes:
        namespaces:
          from: All

from: All lets HTTPRoute resources in alpha-dev attach to this Gateway in ingress. Without it, only routes in the same namespace can attach.


Verification
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Check Gateway API CRDs are installed
kubectl get crd gateways.gateway.networking.k8s.io
kubectl get crd httproutes.gateway.networking.k8s.io

# Check GatewayClass is accepted by Traefik
kubectl get gatewayclass traefik
# CONTROLLER should show traefik.io/gateway-controller, STATUS=Accepted

# Check Gateway is programmed
kubectl get gateway traefik -n ingress
# PROGRAMMED=True means Traefik has picked it up

# Check HTTPRoute is attached
kubectl get httproute svc3 -n alpha-dev
# ATTACHED=True, PARENT=traefik/ingress

# HTTP smoke test (after adding to /etc/hosts: 127.0.0.1 svc3.eu-dev-rancher.ravikrs.local)
curl http://svc3.eu-dev-rancher.ravikrs.local

Gotchas
#

GatewayClass must use the correct controllerName

For Traefik v3 the value is traefik.io/gateway-controller. In Traefik v2 it was different. If the GatewayClass stays in status Waiting, this is the first thing to check - Traefik is not claiming the class.

Gateway API CRDs must be installed before Traefik starts

If Traefik starts before the CRDs exist, the Gateway API provider fails to initialise and routes are silently ignored. Sync wave 1 for CRDs and wave 2 for Traefik ensures correct ordering.

sectionName must match the listener name exactly

The sectionName: web in an HTTPRoute’s parentRef must match the listener name field in the Gateway spec. Traefik maps listener names to its internal entrypoint names (also called web). Mismatch = route is not attached, no traffic flows, no error visible from the service side.

allowedRoutes.namespaces.from: All is required for cross-namespace routing

Without this, the Gateway only accepts routes from its own namespace (ingress). Services in alpha-dev will show as not attached. No error is surfaced on the HTTPRoute itself - the route just silently fails to attach.

ingress.enabled must be set to false

If both ingress.enabled: true and httpRoute.enabled: true are set, both resources are created. Having duplicate routes for the same hostname is legal but confusing. Set ingress.enabled: false explicitly when switching to HTTPRoute.

Learning ArgoCD - This article is part of a series.
Part 3: This Article