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:
- Manual listener management: You must manually define listeners for each hostname/port/protocol combination—error-prone and creates maintenance burden
- 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) - 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.
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:
-
WrappedGateway Reconciler: Watches
WrappedGatewayresources (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
-
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:
Readycondition 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:
| Flag | Environment Variable | Default | Description |
|---|---|---|---|
--health-address | HEALTH_ADDRESS | :8081 | Address for health/readiness probes (/healthz, /readyz) |
--metrics-address | METRICS_ADDRESS | :8080 | Address for Prometheus metrics endpoint (/metrics) |
--leader-election | LEADER_ELECTION | false | Enable leader election for HA deployments |
--kubeconfig | KUBECONFIG | "" | 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:
| Reason | Status | Meaning |
|---|---|---|
ReconciliationSucceeded | True | Gateway successfully synced with route hostnames |
RoutesFetchFailed | False | Failed to list attached routes |
GatewayFetchFailed | False | Failed to fetch/create the underlying Gateway |
GatewaySyncFailed | False | Failed to update Gateway with collected hostnames |
GatewayStatusFailed | False | Failed to update WrappedGateway status |
FinalizerFailed | False | Finalizer cleanup failed during deletion |
Annotations Reference
The following annotations control route behavior:
| Annotation | Route Types | Required | Description |
|---|---|---|---|
gateway-route-sync.homelab-helper.benfiola.com/ports | UDPRoute, TCPRoute | Yes | Comma-separated port numbers for the route (no default) |
gateway-route-sync.homelab-helper.benfiola.com/hostname | UDPRoute, TCPRoute | No | Hostname 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
gatewayClassNamereference
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
parentRefsreferences the Gateway resource (not WrappedGateway) - Route has
hostnamesfield 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
- Kubernetes Gateway API — Complete reference for HTTPRoute, GRPCRoute, TLSRoute, and Gateway resources
- cert-manager Documentation
- cert-manager Issue Tracking ListenerSet Support