ExternalDNS watches the cluster and writes your provider’s DNS records for you, so Services and Ingresses get hostnames without anyone hand-editing a zone.
Architecture and the RBAC prerequisites it needs to read cluster sources and write DNS entries.
Configuration patterns for cluster sources plus multi-cloud provider setups (AWS IAM roles, GCP service accounts).
Production hardening includes domain filters so two clusters can manage separate subdomains of the same domain.
Info
ExternalDNS eliminates the manual effort required to create and maintain DNS records when exposing services or ingress endpoints outside the cluster. It ensures that DNS records are dynamically updated as resources in the cluster are created, modified, or deleted.
ExternalDNS is an open-source Kubernetes incubator project (kubernetes-sigs/external-dns) that automates the orchestration of public and private DNS records directly from Kubernetes resources. This guide provides an in-depth operational blueprint for integrating, configuring, and securing ExternalDNS inside production-grade clusters.
1. Architectural Overview
Kubernetes’ built-in DNS (such as CoreDNS) resolves service names internally (*.svc.cluster.local). However, CoreDNS cannot communicate with public registrars or cloud-hosted DNS networks.
ExternalDNS bridges this gap. It operates as a control-loop controller within the cluster, querying the Kubernetes API for resources that expose endpoints, compiling desired records, and synchronizing them with external DNS providers via their respective APIs.
Core Concepts
Sources: The Kubernetes resources ExternalDNS watches. Supported sources include service, ingress, gateway-httproute, gateway-grpcroute, gateway-tlsroute, and Custom Resource Definitions (CRDs) like Linkerd or Istio VirtualServices.
Providers: The cloud or on-premise DNS APIs ExternalDNS interacts with. Supported providers include AWS Route 53, Cloudflare, Google Cloud DNS, Azure DNS, Infoblox, NS1, DigitalOcean, and PowerDNS.
Registry: The mechanism used to track DNS record ownership.
TXT Registry (Recommended): Alongside every A or CNAME record, ExternalDNS writes a companion TXT record containing an identifier string (e.g., heritage=external-dns,external-dns/owner=prod-cluster-01). This ensures multiple clusters or external manual operators do not overwrite each other’s records.
No-Op Registry: Directly synchronizes resources without tracking state. This is highly risky in multi-tenant or multi-cluster environments.
2. Cluster Prerequisites & RBAC Setup
ExternalDNS requires read access to cluster networking APIs and read/write access to its internal state tracking resources. Below is the standard Role-Based Access Control (RBAC) manifest for a standard cluster deployment.
ExternalDNS parses defined resources to extract the hostnames and target endpoints (such as public IPs or LoadBalancer hostnames) to program your DNS records.
A. Exposing Services (Type: LoadBalancer)
For simple services exposed directly via a cloud controller manager’s network load balancer, use annotations to instruct ExternalDNS:
apiVersion: v1kind: Servicemetadata: name: web-frontend namespace: production annotations: # Explicitly define target hostnames external-dns.alpha.kubernetes.io/hostname: frontend.example.com, web.example.com # Set explicit Time-To-Live (TTL) for these records (in seconds) external-dns.alpha.kubernetes.io/ttl: "300"spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 protocol: TCP selector: app: frontend
B. Exposing Ingresses (Nginx / ALB / Traefik)
ExternalDNS automatically tracks the spec.rules[*].host definitions inside Ingresses. It will associate these hostnames with the entry points (IP or Hostname) provisioned in the Ingress’s status.loadBalancer.ingress block.
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: api-ingress namespace: production annotations: kubernetes.io/ingress.class: "nginx" # Optional override if you want to route to a different target (e.g., global traffic manager) # external-dns.alpha.kubernetes.io/target: "gtm.example.com"spec: rules: - host: api.example.com # ExternalDNS watches this host entry http: paths: - path: /v1 pathType: Prefix backend: service: name: api-v1-service port: number: 80
C. Exposing Modern Gateway API Routes
Under the modern Gateway API specification, infrastructure separation isolates Gateway definition (Infra team) from Route definition (App developers). ExternalDNS seamlessly hooks into HTTPRoute resources.
Before permitting ExternalDNS to process API modifications against public DNS zones, enable dry-run to monitor updates within your container log.
extraArgs:
- --dry-run=true
Monitor the controller log using kubectl logs:
INFO[0001] Would create A record named 'app.example.com' with target '192.0.2.1'
INFO[0001] Would create TXT record named 'app.example.com' with text '"heritage=external-dns,external-dns/owner=prod-cluster-01"'
Overwrite Protection (The Multi-Tenant Guardrail)
Always configure the --txt-owner-id argument to match the exact cluster lifecycle tag.
# Scenario: Cluster A and Cluster B manage the same domain space.
Cluster A: --txt-owner-id=k8s-cluster-A
Cluster B: --txt-owner-id=k8s-cluster-B
Without unique Owner IDs, Cluster B will identify DNS records created by Cluster A as “orphaned” and delete or overwrite them based on its synchronization loop.
Strict Domain Filtering
Isolate zones by enforcing explicit domain filters on the controller container. This prevents accidental namespace deployments from modifying DNS records outside your control boundaries.
# Only allowed to create DNS updates on these specified subdomains
domainFilters:
- app.prod.example.com
- internal.example.com
Rate Limit Mitigation
Cloud API providers enforce strict request-per-second ceilings on DNS management endpoints.
Configure local DNS caching: Utilize CoreDNS caching loops in-cluster to prevent repeated external lookups.
Increase the synchronization interval: The default sync loop runs every 1 minute. For stable setups, increase this to reduce API consumption overhead.
extraArgs: - --interval=5m
Use TXT prefixes: Use a prefix for ownership TXT records to keep the parent namespace uncluttered.
extraArgs: - --txt-prefix=_externaldns.
(Creates: _externaldns.app.example.com instead of polluting app.example.com directly).