Kubernetes - Setup External DNS
Table of Contents
This post is part of our ongoing series of posts for Kubernetes. In this post we are focusing on setting up External DNS, a critical component for automating DNS record management. Although we are working on a local Kubernetes cluster, we have implemented prerequisites in Kubernetes - Routing external traffic to local Kubernetes cluster to enable access to our internal cluster via public ip.
We are using Cloudflare as our DNS Provider and implementing this solution using declarative GitOps principles with Flux.
1. Setup Cloudflare API Key
First, let’s generate an API key from Cloudflare by navigating to Manage Account > Account API Tokens
in the Cloudflare dashboard.
We’ll choose Edit DNS
template and generate token for a specific zone
2. Setup Kubernetes Secret
Now we’ll configure a Kubernetes secret by creating cluster/default/cloudflare-token.yaml
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-token
namespace: kube-system
type: Opaque
stringData:
CF_API_TOKEN: ***
CF_API_EMAIL: ***
Let’s encrypt this sensitive data with SOPS and commit the changes to our Git repository.
4. Setup External DNS
Now we’ll configure the External DNS deployment by creating cluster/default/external-dns.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: kube-system
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns:v0.14.2
args:
- --source=ingress
- --domain-filter=***
- --provider=cloudflare
resources:
requests:
memory: "64Mi"
cpu: "20m"
limits:
memory: "256Mi"
cpu: "200m"
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-token
key: CF_API_TOKEN
- name: CF_API_EMAIL
valueFrom:
secretKeyRef:
name: cloudflare-token
key: CF_API_EMAIL
Explanation of External DNS Configuration
This YAML manifest consists of four Kubernetes resources that work together to enable automatic DNS record management:
-
ServiceAccount:
- Creates a dedicated service account named
external-dns
in thekube-system
namespace - This provides an identity for the External DNS process to operate within the cluster
- Creates a dedicated service account named
-
ClusterRole:
- Defines the permissions required by External DNS to function properly
- Allows read-only access (
get
,watch
,list
) to:- Services, endpoints, and pods: To monitor for changes to service endpoints
- Ingresses: To detect new or modified ingress resources
- Nodes: To gather information about the cluster nodes
-
ClusterRoleBinding:
- Associates the previously defined
external-dns
ClusterRole with theexternal-dns
ServiceAccount - This binding effectively grants the External DNS service the permissions it needs
- Associates the previously defined
-
Deployment:
- Creates the actual External DNS controller pod with specific configuration:
- Uses the
Recreate
strategy to ensure clean upgrades - Deploys the official
k8s.gcr.io/external-dns/external-dns:v0.14.2
image - Configures important command-line arguments:
--source=ingress
: Instructs External DNS to watch for Ingress resources--domain-filter=***
: Limits DNS operations to a specific domain (redacted in the example)--provider=cloudflare
: Specifies Cloudflare as the DNS provider
- Sets resource constraints to optimize performance:
- Requests modest resources (64Mi memory, 20m CPU)
- Sets reasonable limits (256Mi memory, 200m CPU)
- Injects Cloudflare credentials from the previously created secret:
CF_API_TOKEN
: Authentication token for Cloudflare API accessCF_API_EMAIL
: Associated email for the Cloudflare account
- Uses the
- Creates the actual External DNS controller pod with specific configuration:
With this configuration, External DNS will continuously monitor the Kubernetes cluster for new or modified Ingress resources. When it detects changes, it automatically creates, updates, or deletes the corresponding DNS records in Cloudflare according to the annotations and specifications in those resources. This eliminates the need for manual DNS management and ensures that your services are always accessible via their correct domain names.
Let’s commit these changes to our Git repository and verify that External DNS is deployed successfully.
As we can see from the notifications in Discord, External DNS has been configured successfully.
Let’s examine the pod status as well:
kubectl -n kube-system get pods
NAME READY STATUS RESTARTS AGE
coredns-ccb96694c-s67q5 1/1 Running 4 (60m ago) 4d2h
external-dns-7cbfcc8d94-zwck7 1/1 Running 0 2m3s
helm-install-traefik-8q42l 0/1 Completed 1 4d2h
helm-install-traefik-crd-nq5tj 0/1 Completed 0 4d2h
local-path-provisioner-5cf85fd84d-pxdcv 1/1 Running 4 (60m ago) 4d2h
metrics-server-5985cbc9d7-qn4dr 1/1 Running 4 (60m ago) 4d2h
svclb-traefik-aadbbe88-gjtbx 2/2 Running 8 (60m ago) 4d2h
svclb-traefik-aadbbe88-vzd2t 2/2 Running 6 (60m ago) 4h10m
traefik-5d45fc8cc9-g97pg 1/1 Running 4 (60m ago) 4d2h
As confirmed, External DNS is up and running. Let’s verify the logs to ensure proper functionality:
kubectl -n kube-system logs pod/external-dns-7cbfcc8d94-zwck7
time="2025-02-28T11:26:25Z" level=info msg="Instantiating new Kubernetes client"
time="2025-02-28T11:26:25Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2025-02-28T11:26:25Z" level=info msg="Created Kubernetes client https://10.43.0.1:443"
time="2025-02-28T11:26:27Z" level=info msg="All records are already up to date"
time="2025-02-28T11:27:27Z" level=info msg="All records are already up to date"
time="2025-02-28T11:28:28Z" level=info msg="All records are already up to date"
The logs indicate that External DNS is functioning properly.
5. Verify Setup
Now we’ll validate our setup by configuring an ingress for a sample application we created in Kubernetes GitOps with FluxCD - Part 3 - Automated Image Updates.
First, let’s configure a service by creating apps/sample-app/service.yaml
apiVersion: v1
kind: Service
metadata:
name: sample-app-service
namespace: default
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
selector:
app: sample-app
Next, let’s configure the ingress by creating apps/sample-app/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sample-app-ingress
namespace: default
annotations:
external-dns.alpha.kubernetes.io/target: "******"
spec:
rules:
- host: sample-app.***
http:
paths:
- pathType: Prefix
backend:
service:
name: sample-app-service
port:
number: 80
path: /
Since we’re working with a local cluster, we’ve implemented a solution to route external traffic to our cluster as detailed in Kubernetes - Routing external traffic to local Kubernetes cluster. The public IP of our external server is specified in the external-dns.alpha.kubernetes.io/target
.
Now, let’s modify the kustomization file at apps/sample-app/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
+ - service.yaml
+ - ingress.yaml
Let’s commit these changes and verify that External DNS has created the necessary DNS records:
kubectl -n kube-system logs pod/external-dns-7cbfcc8d94-zwck7
time="2025-02-28T11:42:34Z" level=info msg="Changing record." action=CREATE record=**** ttl=1 type=A zone=****
time="2025-02-28T11:42:35Z" level=info msg="Changing record." action=CREATE record=**** ttl=1 type=TXT zone=****
time="2025-02-28T11:42:35Z" level=info msg="Changing record." action=CREATE record=**** ttl=1 type=TXT zone=****
The logs confirm that the DNS records have been successfully created.
Finally, let’s verify access to our application using curl:
curl http://sample-app.**
Greetings From K8S App : Version 2
We have successfully configured External DNS, automating the creation and management of DNS records for our Kubernetes services.
What next ?
Future posts will explore advanced Kubernetes and GitOps patterns with FluxCD, including:
- Setting up Cert Manager
- Push based reconciliation triggers with Webhook receivers for FluxCD
Stay tuned for each of these topics.
References
- Kubernetes Documentation - https://kubernetes.io/docs/
- External DNS Documentation - https://kubernetes-sigs.github.io/external-dns/latest/
- FluxCD Documentation - https://fluxcd.io/docs/