Streamline Kubernetes DNS Management: Effortless Lifecycle Control with ExternalDNS

Chimbu Chinnadurai
6 min readMay 25, 2023

Are your kubernetes workloads DNS records management creating too much manual work and a lot of downtimes due to frequent endpoint changes? If yes, then external DNS is for you.

ExternalDNS is a kubernetes add-on which you can deploy in any kubernetes cluster and seamlessly manage the entire lifecycle of the DNS records. It allows you to control DNS records dynamically via Kubernetes resources in a DNS provider-agnostic way.

This article covers how to install ExternalDNS in GKE and use the add-on to manage the lifecycle of DNS records. The installation is based on Helm, but you can also use kustomize.

Prerequisites

Configure Workload Identity for ExternalDNS

ExternalDNS requires access to the Cloud DNS zone to manage DNS records. Run the below commands to set up the resources for workload identity. Replace the variables with actual values.

#Creare a GCP service account
gcloud iam service-accounts create external-dns --project=$GCP_PROJECT_ID

#Defaut DNS admin role is used for this demo but it is not recommended for production usecase, create a custom role with minimal permissions
gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \
--member "serviceAccount:external-dns@$GCP_PROJECT_ID.iam.gserviceaccount.com" \
--role "roles/dns.admin"

#Bind the kubernetes service account to GCP service account
gcloud iam service-accounts add-iam-policy-binding external-dns@$GCP_PROJECT_ID.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:$GCP_PROJECT_ID.svc.id.goog[external-dns/external-dns]"

Install ExternalDNS

Add the external-dns repo to Helm.

helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/

Custom values.yaml, Replace the variables with actual values and update domainFilters.

# Default values for external-dns.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

image:
repository: registry.k8s.io/external-dns/external-dns
# Overrides the image tag whose default is v{{ .Chart.AppVersion }}
tag: ""
pullPolicy: IfNotPresent

imagePullSecrets: []

nameOverride: ""
fullnameOverride: ""

commonLabels: {}

serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account.The GCP IAM serviceaccount name will be added to implement workload identity
annotations:
iam.gke.io/gcp-service-account: external-dns@$GCP_PROJECT_ID.iam.gserviceaccount.com
# Labels to add to the service account
labels: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""

rbac:
# Specifies whether RBAC resources should be created
create: true
additionalPermissions: []

# Annotations to add to the Deployment
deploymentAnnotations: {}

podLabels: {}

# Annotations to add to the Pod
podAnnotations: {}

shareProcessNamespace: false

podSecurityContext:
fsGroup: 65534

securityContext:
runAsNonRoot: true
runAsUser: 65534
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]

# Defaults to `ClusterFirst`.
# Valid values are: `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`.
dnsPolicy:

priorityClassName: ""

terminationGracePeriodSeconds:

serviceMonitor:
enabled: false
# force namespace
# namespace: monitoring

# Fallback to the prometheus default unless specified
# interval: 10s

## scheme: HTTP scheme to use for scraping. Can be used with `tlsConfig` for example if using istio mTLS.
# scheme: ""

## tlsConfig: TLS configuration to use when scraping the endpoint. For example if using istio mTLS.
## Of type: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig
# tlsConfig: {}

# bearerTokenFile:
# Fallback to the prometheus default unless specified
# scrapeTimeout: 30s

## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
additionalLabels: {}

## Used to pass annotations that are used by the Prometheus installed in your cluster to select Service Monitors to work with
## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
annotations: {}

## Metric relabel configs to apply to samples before ingestion.
## [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs)
metricRelabelings: []
# - action: keep
# regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
# sourceLabels: [__name__]

## Relabel configs to apply to samples before ingestion.
## [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
relabelings: []
# - sourceLabels: [__meta_kubernetes_pod_node_name]
# separator: ;
# regex: ^(.*)$
# targetLabel: nodename
# replacement: $1
# action: replace

targetLabels: []

env: []

livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 2
successThreshold: 1

readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1

service:
port: 7979
annotations: {}

extraVolumes: []

extraVolumeMounts: []

resources: {}

nodeSelector: {}

tolerations: []

affinity: {}

topologySpreadConstraints: []

logLevel: info
logFormat: text

interval: 1m
triggerLoopOnEvent: false

namespaced: false

sources:
- service
- ingress

policy: sync

registry: txt
txtOwnerId: ""
txtPrefix: ""
txtSuffix: ""

## List of domains that can be managed. Should be managed by Google Cloud DNS
domainFilters: ["external.dns.decom."]

provider: google

extraArgs: ["--google-project=$GCP_PROJECT_ID"]

secretConfiguration:
enabled: false
mountPath: ""
subPath: ""
data: {}

deploymentStrategy:
type: Recreate

Run the below command to install the chart with the custom values file.

helm upgrade --install external-dns external-dns/external-dns --values values.yaml --namespace external-dns --create-namespace --wait --debug

Verify the installation and check the logs to ensure the application runs without errors.

ExternalDNS pod logs

Deploy Sample application

Run the below command to deploy a sample nginx application in the external-dns-test namespace.

kubectl run nginx --image nginx --namespace external-dns-test

Expose the nginx pod via Kubernetes service type loadbalancer. To expose the service via ingress, create a service type NodePort and an ingress object.

kubectl expose pod nginx --namespace external-dns-test --target-port 80 --port 80 --type LoadBalancer

For kubernetes services, ExternalDNS will look for the annotation external-dns.alpha.kubernetes.io/hostname on the service and use the loadbalancer IP, it also will look for the annotation external-dns.alpha.kubernetes.io/internal-hostname on the service and use the service IP.

For ingress objects, ExternalDNS will create a DNS record based on the hosts specified for the ingress object, as well as the external-dns.alpha.kubernetes.io/hostname annotation.

For ingresses, you can optionally force ExternalDNS to create records based on either the hosts specified or the external-dns.alpha.kubernetes.io/hostname annotation. This behaviour is controlled by setting the external-dns.alpha.kubernetes.io/ingress-hostname-source annotation on that ingress to either defined-hosts-only or annotation-only.

Add the annotation to the nginx service with the required DNS record name value.

kubectl annotate service -n external-dns-test nginx external-dns.alpha.kubernetes.io/hostname=chimbu.external.dns.decom.
ExternalDNS pod logs
Records are successfully inserted into Cloud DNS managed zone

In the above images, you can see DNS records are successfully deleted automatically by external-dns in the required Cloud DNS zone.

Remove the annotation to test the sync action.

kubectl annotate service -n external-dns-test nginx external-dns.alpha.kubernetes.io/hostname-
ExternalDNS pod logs
Records are successfully removed from Cloud DNS managed zone

In the above images, you can see DNS records are successfully deleted automatically by external-dns in the required Cloud DNS zone.

Conclusion

ExternalDNS eliminates the manual DNS record management and automatically creates/updates the DNS records for you, which will minimize downtime if there is any change in IP address.

Refer to the official Github repository and FAQ for all the supported DNS providers and key concepts of ExternalDNS.

--

--