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.

Kubernetes - Setup External DNS - screenshot 1

We'll choose Edit DNS template and generate token for a specific zone

Kubernetes - Setup External DNS - screenshot 2

2. Setup Kubernetes Secret

Now we'll configure a Kubernetes secret by creating cluster/default/cloudflare-token.yaml

Note: If you're unfamiliar with YAML path conventions and definitions, please refer to our guide on GitOps with FluxCD: Kubernetes GitOps with FluxCD - Part 1 - Initial Setup.

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.

Note: For detailed instructions on secret management, refer to Kubernetes GitOps with FluxCD - Part 2 - Secret Management using SOPS.

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:

  1. ServiceAccount:

    • Creates a dedicated service account named external-dns in the kube-system namespace
    • This provides an identity for the External DNS process to operate within the cluster
  2. 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
  3. ClusterRoleBinding:

    • Associates the previously defined external-dns ClusterRole with the external-dns ServiceAccount
    • This binding effectively grants the External DNS service the permissions it needs
  4. 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 access
      • CF_API_EMAIL: Associated email for the Cloudflare account

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.

Kubernetes - Setup External DNS - screenshot 3

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