Skip to content

Gateway API Route Sources

This describes how to configure ExternalDNS to use Gateway API Route sources.
It is meant to supplement the other provider-specific setup tutorials.

Supported API Versions

ExternalDNS uses Gateway API CRDs, which are distributed at different versions in Standard and/or
Experimental channels as summarized below:

Resource API Version Used
by ExternalDNS
Mininmum Standard
Release Channel
Experimental
Release Channel
Gateway v1 v1.0.0 v1.0.0
HTTPRoute v1 v1.0.0 v1.0.0
GRPCRoute v1 v1.1.0 v1.1.0
ListenerSet v1 v1.5.0 v1.5.0
TLSRoute v1alpha2 v1.5.0 v1.0.0
TCPRoute v1alpha2 TBD v1.0.0
UDPRoute v1alpha2 TBD v1.0.0

Gateways and HTTPRoutes were promoted to the Standard channel in Gateway API v1.0.0 and use the
v1 API.

GRPCRoutes were promoted to the Standard channel in Gateway API v1.1.0 and use the
v1 API.

ListenerSets were promoted to the Standard channel in Gateway API v1.5.0.
They use the v1 API and allow attaching additional listeners to an existing Gateway.
Routes that reference a ListenerSet as a parentRef are automatically supported —
ExternalDNS follows the ListenerSet to its parent Gateway to resolve target addresses.
The external-dns.alpha.kubernetes.io/target annotation is also supported on ListenerSet
resources. When present, it takes precedence over the parent Gateway’s target annotation.
ListenerSet support requires the --gateway-listener-sets flag to be enabled.

TLSRoutes were promoted to the Standard channel in Gateway API v1.5.0 but have been
available in the Experimental channel as v1alpha2 since v1.0.0.
ExternalDNS still uses the v1alpha2 API for compatibility with older CRDs but it
has been deprecated and will be removed from future releases, at which point ExternalDNS will
need to migrate to v1. (See #6247)

TCPRoute and UDPRoute remain experimental and are only available as v1alpha2 in the Experimental channel.

Hostnames

HTTPRoute and TLSRoute specs, along with their associated Gateway Listeners, contain hostnames that
will be used by ExternalDNS. However, no such hostnames may be specified in TCPRoute or UDPRoute
specs. For TCPRoutes and UDPRoutes, the external-dns.alpha.kubernetes.io/hostname annotation
is the recommended way to provide their hostnames to ExternalDNS. This annotation is also supported
for HTTPRoutes and TLSRoutes by ExternalDNS, but it’s strongly recommended that they use their
specs to provide all intended hostnames, since the Gateway that ultimately routes their
requests/connections won’t recognize additional hostnames from the annotation.

Annotations

Annotation Placement

ExternalDNS reads different annotations from different Gateway API resources:

  • Gateway annotations: Only external-dns.alpha.kubernetes.io/target is read from Gateway resources
  • ListenerSet annotations: The external-dns.alpha.kubernetes.io/target annotation is also supported on
    ListenerSet resources. When a Route references a ListenerSet, the ListenerSet target annotation takes
    precedence over the parent Gateway’s target annotation. Requires --gateway-listener-sets.
  • Route annotations: All other annotations (hostname, ttl, controller, provider-specific) are read from Route
    resources (HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute)

This separation aligns with Gateway API architecture where Gateway defines infrastructure (IP addresses, listeners)
and Routes define application-level DNS records.

Examples

Example: Cloudflare Proxied Records

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: default
  annotations:
    # ✅ Correct: target annotation on Gateway
    external-dns.alpha.kubernetes.io/target: "203.0.113.1"
spec:
  gatewayClassName: cilium
  listeners:
    - name: https
      hostname: "*.example.com"
      protocol: HTTPS
      port: 443
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-route
  annotations:
    # ✅ Correct: provider-specific annotations on HTTPRoute
    external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
    external-dns.alpha.kubernetes.io/ttl: "300"
spec:
  parentRefs:
    - name: my-gateway
      namespace: default
  hostnames:
    - api.example.com
  rules:
    - backendRefs:
        - name: api-service
          port: 8080

Example: AWS Route53 with Routing Policies

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: aws-gateway
  annotations:
    # ✅ Correct: target annotation on Gateway
    external-dns.alpha.kubernetes.io/target: "alb-123.us-east-1.elb.amazonaws.com"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: weighted-route
  annotations:
    # ✅ Correct: AWS-specific annotations on HTTPRoute
    external-dns.alpha.kubernetes.io/aws-weight: "100"
    external-dns.alpha.kubernetes.io/set-identifier: "backend-v1"
spec:
  parentRefs:
    - name: aws-gateway
  hostnames:
    - app.example.com

Common Mistakes

Incorrect: Placing provider-specific annotations on Gateway

kind: Gateway
metadata:
  annotations:
    # ❌ These annotations are ignored on Gateway
    external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
    external-dns.alpha.kubernetes.io/ttl: "300"

Incorrect: Placing target annotation on HTTPRoute

kind: HTTPRoute
metadata:
  annotations:
    # ❌ This annotation is ignored on Routes
    external-dns.alpha.kubernetes.io/target: "203.0.113.1"

external-dns.alpha.kubernetes.io/gateway-hostname-source

Why is this needed:
In certain scenarios, conflicting DNS records can arise when External DNS processes both the hostname annotations and the hostnames defined in the *Route spec. For example:

  • A CNAME record (company.public.example.com -> company.private.example.com) is used to direct traffic to private endpoints (e.g., AWS PrivateLink).
  • Some third-party services require traffic to resolve publicly to the Gateway API load balancer, but the hostname (company.public.example.com) must remain unchanged to avoid breaking the CNAME setup.
  • Without this annotation, External DNS may override the CNAME record with an A record due to conflicting hostname definitions.

Usage:
By setting the annotation external-dns.alpha.kubernetes.io/gateway-hostname-source: annotation-only, users can instruct External DNS
to ignore hostnames defined in the HTTPRoute spec and use only the hostnames specified in annotations. This ensures
compatibility with complex DNS configurations and avoids record conflicts.

Example:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  annotations:
    external-dns.alpha.kubernetes.io/gateway-hostname-source: annotation-only
    external-dns.alpha.kubernetes.io/hostname: company.private.example.com
spec:
  hostnames:
    - company.public.example.com

In this example, External DNS will create DNS records only for company.private.example.com based on the annotation, ignoring the hostnames field in the HTTPRoute spec. This prevents conflicts with existing CNAME records while enabling public resolution for specific endpoints.

For a complete list of supported annotations, see the
annotations documentation.

Manifest with RBAC

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get","watch","list"]
- apiGroups: ["gateway.networking.k8s.io"]
  resources: ["gateways","httproutes","grpcroutes","tlsroutes","tcproutes","udproutes","listenersets"]
  verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: external-dns
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: default
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: registry.k8s.io/external-dns/external-dns:v0.21.0
        args:
        # Add desired Gateway API Route sources.
        - --source=gateway-httproute
        - --source=gateway-grpcroute
        - --source=gateway-tlsroute
        - --source=gateway-tcproute
        - --source=gateway-udproute
        # Optionally, limit Routes to those in the given namespace.
        - --namespace=my-route-namespace
        # Optionally, limit Routes to those matching the given label selector.
        - --label-filter=my-route-label==my-route-value
        # Optionally, limit Route endpoints to those Gateways with the given name.
        - --gateway-name=my-gateway-name
        # Optionally, limit Route endpoints to those Gateways in the given namespace.
        - --gateway-namespace=my-gateway-namespace
        # Optionally, limit Route endpoints to those Gateways matching the given label selector.
        - --gateway-label-filter=my-gateway-label==my-gateway-value
        # Optionally, enable ListenerSet support for Routes referencing ListenerSet parentRefs.
        - --gateway-listener-sets
        # Add provider-specific flags...
        - --domain-filter=external-dns-test.my-org.com
        - --provider=google
        - --registry=txt
        - --txt-owner-id=my-identifier