Skip to main content

gateway-route-sync

Overview

gateway-route-sync is a Kubernetes controller that automatically synchronizes Gateway listeners with the hostnames and protocols declared in attached routes. It eliminates manual listener synchronization and enables flexible multi-protocol gateways.

The Problem

Gateway listeners define the entry points for routes, but the actual hostnames and protocols are declared only on the routes themselves. This creates operational friction:

  1. Manual listener management: You must manually define listeners for each hostname/port/protocol combination—error-prone and creates maintenance burden
  2. cert-manager TLS limitation: cert-manager derives certificate hostnames from Gateway listeners (not routes), forcing you to keep both in sync. Wildcard listeners match too broadly, and TLS certificates only support single-level wildcards (e.g., *.example.com)
  3. Protocol diversity: Different route types (HTTP, GRPC, TLS, UDP) have different listener requirements, making it hard to reason about the gateway configuration

This controller eliminates the friction by automatically generating gateway listeners from the routes attached to it, keeping configuration in one place: the routes themselves. For TLS routes, it also solves the cert-manager synchronization issue.

Note

While this controller is useful today, native Gateway API features like ListenerSet may eventually eliminate the need for explicit listener synchronization. For now, it significantly simplifies multi-route gateway management.

How It Works

Architecture

gateway-route-sync uses two reconcilers working together:

  1. WrappedGateway Reconciler: Watches WrappedGateway resources (a custom resource that acts as a base template). When triggered, it:

    • Collects all hostnames and configuration from HTTPRoute, GRPCRoute, TLSRoute, and UDPRoute resources that reference the underlying Gateway
    • Generates listeners with one listener per unique route (hostname/port/protocol combination)
    • Creates or updates the underlying Gateway resource with the collected listeners
    • Automatically infers protocol and TLS configuration from route types
  2. Route Reconciler: Watches all route types (HTTPRoute, GRPCRoute, TLSRoute, UDPRoute). When a route changes:

    • Detects which gateways it references (or previously referenced)
    • Triggers a reconciliation of the associated WrappedGateway(s)

Workflow

Create/Update Route

Route Reconciler triggered

Finds referenced Gateway

Triggers associated WrappedGateway Reconciler

WrappedGateway Reconciler collects routes

Determines port/protocol for each route

Generates Gateway with listeners (one per unique route)

Infers TLS config from protocol (HTTPS/TLS get certificates)

cert-manager sees all hostnames on listeners

Generates appropriate certificates

Key Concepts

  • WrappedGateway: A custom resource that defines the base configuration for a Gateway. Instead of directly creating Gateway resources with listeners, you create a WrappedGateway and attach routes to the generated Gateway. The controller handles Gateway creation and synchronization automatically.

  • Listener Per Route: Each unique combination of hostname/port/protocol gets its own listener, enabling per-route TLS certificate generation and flexibility.

  • Protocol Inference: Protocols are automatically determined from route types:

    • HTTPRoute → HTTPS (port 443, TLS terminate)
    • GRPCRoute → HTTPS (port 443, TLS terminate)
    • TLSRoute → TLS (port 443, TLS terminate)
    • UDPRoute → UDP (port required via annotation)
    • TCPRoute → TCP (port required via annotation)

Installation

Prerequisites

  • Kubernetes cluster with Gateway API installed (v1.0.0+)
  • A Gateway API controller (e.g., Envoy Gateway, Istio)
  • cert-manager deployed and configured (required for TLS routes; optional for UDP/TCP)
  • Helm 3.x

Deploy with Helm

Add the repository and install:

helm repo add homelab-helper https://benfiola.github.io/homelab-helper
helm repo update
helm install gateway-route-sync homelab-helper/gateway-route-sync \
--namespace gateway-system \
--create-namespace

The chart deploys:

  • A Deployment running the controller
  • A ServiceAccount with necessary RBAC permissions
  • ClusterRole and ClusterRoleBinding for Gateway API resource access
  • Custom Resource Definition (WrappedGateway)

Verify Installation

Check the deployment is running:

kubectl get deployment -n gateway-system gateway-route-sync
kubectl logs -n gateway-system -l app.kubernetes.io/name=gateway-route-sync

Usage

Creating a WrappedGateway

A WrappedGateway defines the base configuration for a Gateway. Listeners are generated automatically based on attached routes.

Example:

apiVersion: gateway-route-sync.homelab-helper.benfiola.com/v1
kind: WrappedGateway
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway
addresses:
- type: IPAddress
value: "10.0.0.1"

Attaching Routes

Routes must reference the generated Gateway resource (not the WrappedGateway) using parentRefs:

spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- example.com
- www.example.com

Port and Hostname Configuration

Port Selection

Port selection depends on route type:

  • HTTPRoute, GRPCRoute, TLSRoute: Default to port 443 (HTTPS/TLS)
  • UDPRoute, TCPRoute: Must specify port via annotation (no default)

To override the default port, annotate the route:

metadata:
annotations:
gateway-route-sync.homelab-helper.benfiola.com/ports: "8443,8444"
spec:
parentRefs:
- name: my-gateway

Hostname Configuration

  • HTTPRoute, GRPCRoute, TLSRoute: Hostnames declared in spec.hostnames
  • UDPRoute, TCPRoute: Hostnames specified via annotation (Gateway API lacks hostname support for UDP/TCP)

Example UDP route with hostname and port:

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: UDPRoute
metadata:
name: dns
annotations:
gateway-route-sync.homelab-helper.benfiola.com/ports: "53"
gateway-route-sync.homelab-helper.benfiola.com/hostname: "dns.example.com"
spec:
parentRefs:
- name: my-gateway
rules:
- backendRefs:
- name: coredns
port: 53

The controller automatically creates listeners for each unique route (hostname/port/protocol combination) declared across all attached routes. For detailed Gateway API route configuration, refer to the Gateway API documentation.

Monitoring Status

Check the WrappedGateway status to verify synchronization:

kubectl get wrappedgateway
kubectl describe wrappedgateway my-gateway

The status shows:

  • Conditions: Ready condition with reason codes indicating success or failure
  • lastReconciledTime: When the gateway was last synced
  • observedGeneration: Tracks which spec generation was processed

Example:

status:
conditions:
- type: Ready
status: "True"
reason: ReconciliationSucceeded
observedGeneration: 1
lastReconciledTime: "2024-01-15T10:30:00Z"
observedGeneration: 1

View the generated Gateway and its listeners:

kubectl get gateway my-gateway -o yaml

Configuration

CLI Flags and Environment Variables

The controller is invoked as:

homelab-helper gateway-route-sync [flags]

Available flags and their environment variable equivalents:

FlagEnvironment VariableDefaultDescription
--health-addressHEALTH_ADDRESS:8081Address for health/readiness probes (/healthz, /readyz)
--metrics-addressMETRICS_ADDRESS:8080Address for Prometheus metrics endpoint (/metrics)
--leader-electionLEADER_ELECTIONfalseEnable leader election for HA deployments
--kubeconfigKUBECONFIG""Path to kubeconfig; uses in-cluster config if empty

Helm Chart Values

deployment:
image:
tag: "" # Defaults to chart version; override with specific tag
replicas: 1
resources:
null
# Example:
# limits:
# cpu: 200m
# memory: 256Mi
# requests:
# cpu: 100m
# memory: 128Mi

WrappedGateway Reference

Spec

The WrappedGateway spec is identical to the Gateway spec from the Kubernetes Gateway API. The controller generates listeners automatically from attached routes, so you only need to configure the base gateway properties.

Example:

spec:
gatewayClassName: my-gateway
addresses:
- type: IPAddress
value: "10.0.0.1"

Status

Communicates WrappedGateway reconciliation status.

status:
conditions:
- type: Ready
status: "True" | "False"
reason: <reason code>
message: <error message if status is False>
observedGeneration: 1
lastReconciledTime: <RFC3339 timestamp>
observedGeneration: 1

Condition Reasons:

ReasonStatusMeaning
ReconciliationSucceededTrueGateway successfully synced with route hostnames
RoutesFetchFailedFalseFailed to list attached routes
GatewayFetchFailedFalseFailed to fetch/create the underlying Gateway
GatewaySyncFailedFalseFailed to update Gateway with collected hostnames
GatewayStatusFailedFalseFailed to update WrappedGateway status
FinalizerFailedFalseFinalizer cleanup failed during deletion

Annotations Reference

The following annotations control route behavior:

AnnotationRoute TypesRequiredDescription
gateway-route-sync.homelab-helper.benfiola.com/portsUDPRoute, TCPRouteYesComma-separated port numbers for the route (no default)
gateway-route-sync.homelab-helper.benfiola.com/hostnameUDPRoute, TCPRouteNoHostname for external-dns integration (Gateway API lacks hostname support for UDP/TCP)

Troubleshooting

Gateway Not Creating

Symptom: WrappedGateway exists but no Gateway resource created.

Check conditions:

kubectl describe wrappedgateway my-gateway

If Ready=False with reason GatewayFetchFailed or GatewaySyncFailed, check controller logs:

kubectl logs -n gateway-system -l app.kubernetes.io/name=gateway-route-sync

Common causes:

  • ServiceAccount lacks permissions (check RBAC)
  • Invalid gatewayClassName reference

Routes Not Being Picked Up

Symptom: Routes are attached but hostnames not appearing in Gateway listeners.

Check Route conditions:

kubectl describe httproute my-route

Check that:

  • Route parentRefs references the Gateway resource (not WrappedGateway)
  • Route has hostnames field populated (for HTTP/GRPC/TLS routes) or hostname annotation set (for UDP/TCP routes)
  • Route uses supported kind (HTTPRoute, GRPCRoute, TLSRoute, UDPRoute)
  • For UDP/TCP routes: ports are specified via annotation gateway-route-sync.homelab-helper.benfiola.com/ports
  • For UDP/TCP routes with external-dns: hostname is specified via annotation gateway-route-sync.homelab-helper.benfiola.com/hostname

Check controller logs for route processing:

kubectl logs -n gateway-system -l app.kubernetes.io/name=gateway-route-sync | grep -i route

Enable Debug Logging

Run controller with debug logging:

kubectl set env -n gateway-system deployment/gateway-route-sync LOG_LEVEL=debug

Limitations

  • Each unique route (hostname/port/protocol combination) gets one listener. If you need multiple backend services on the same hostname/port, use route rules within a single route.
  • Multi-cluster scenarios require separate deployments.

See Also