Nov 01, 2021
Using External DNS instead of a Load Balancer for Kubernetes
by Ryan Welch · 4 Min Read
For a bit of background, I’ve been a happy DigitalOcean customer for a long time. Their services are reasonably priced, and when they introduced their managed Kubernetes platform, I jumped on board right away. Overall, the experience has been great—except for one persistent limitation: their load balancers don’t support UDP.
Up to now, I’ve worked around this by either not using UDP or by exposing a port directly on the node. The problem with this workaround is that I have to manually direct my traffic to the node instead of relying on the load balancer to manage the list of backend nodes automatically.
Since I don’t handle massive amounts of traffic, the primary reason I used a load balancer was for easier traffic management and automatic discovery of active cluster nodes. But at $10/month—and with no UDP support—justifying it became tough. So I began looking for alternatives to the feature I really needed: the auto-discovery of active nodes in the cluster.
That’s where External DNS comes in.
I currently use DigitalOcean’s managed Kubernetes (which only charges for the compute nodes). However, this approach should work with any managed Kubernetes service. For self-managed clusters, you might prefer something like MetalLB ↗ if you need an on-prem or bare-metal load balancer solution.
External DNS is a daemon that runs in your cluster and watches for any Ingress or Service objects. It then automatically synchronizes your external DNS records. This setup lets you drop the DigitalOcean load balancer and expose the Nginx Ingress Controller directly via public DNS. You’ll still get automatic updates whenever a node joins or leaves your cluster—just without the cost or limitations of the load balancer.
I use Terraform to manage my Helm charts, but these steps should work fine with any standard Helm deployment method.
resource "kubernetes_namespace" "ingress" { metadata { name = "ingress" }}
resource "helm_release" "ingress_external_dns" { name = "external-dns" repository = "https://charts.bitnami.com/bitnami" chart = "external-dns" # Latest version from 'helm search repo external-dns' version = "5.4.11" namespace = kubernetes_namespace.ingress.metadata[0].name
values = [ file("config/external-dns.values.yaml") ]}Below is an example of what you might include in your external-dns.values.yaml file. I’m using the service source instead of ingress so I can manually configure the discovered domains. This allows me to create a wildcard entry for subdomains instead of letting External DNS create an individual entry for each subdomain.
## @param sources [array] K8s resources to observe for DNS entriessources: - service # - ingress
## @param provider DNS provider where the records will be createdprovider: digitalocean
digitalocean: apiToken: "<your DO token>"
## @param domainFilters Limit possible target zones by domain suffixesdomainFilters: - ryanwelch.co.uk
## @param policy How DNS records are synchronized (sync, upsert-only)policy: sync
## @param registry Registry method to use (txt, aws-sd, noop)registry: "txt"
txtOwnerId: "ingess-external-dns"The domainFilters section ensures External DNS only updates the specified domains. Make sure to set apiToken with your actual DigitalOcean token in a secure manner (e.g., environment variables or a secrets manager).
Next, configure the Nginx Ingress Controller to use a NodePort service type instead of a LoadBalancer. This way, the Ingress Controller will listen on local ports of each node, and External DNS can then create A records that point directly to those nodes.
controller: service: enabled: true # Use NodePort instead of LoadBalancer type: "NodePort" externalTrafficPolicy: "Local" annotations: # Annotations for External DNS "external-dns.alpha.kubernetes.io/hostname": "ryanwelch.co.uk,*.ryanwelch.co.uk"With this configuration, your domain (ryanwelch.co.uk and subdomains like *.ryanwelch.co.uk) will automatically map to the node IP addresses running the Ingress Controller.
One of the big advantages of this approach is that you can more easily expose non-standard TCP and UDP ports directly from your nodes. External DNS will handle creating or updating DNS records whenever node addresses change, providing many of the benefits of a traditional load balancer without the additional cost or UDP limitations.
By replacing a load balancer with External DNS and NodePort services, you can cut costs and support UDP traffic (which DigitalOcean load balancers don’t currently offer). This setup works especially well for lower-traffic applications where simplicity and cost savings matter more than the features of a dedicated load balancer.
Last edited Mar 15