External-DNS¶
Watches Kubernetes Ingress and Service resources and reconciles their hostnames into Cloudflare DNS records for the tomoda.life zone. Means there is exactly one place to think about DNS — the Ingress spec — and the actual zone file follows.
Installed by k8s/envs/dev/sys/external-dns/application.yaml, configured by k8s/envs/dev/sys/external-dns/values.yaml.
Chart and source¶
| Field | Value |
|---|---|
| Helm chart | external-dns |
| Repository | https://kubernetes-sigs.github.io/external-dns/ |
| Version | 1.14.3 |
| Destination namespace | external-dns (created by Argo CD) |
| Argo CD Application | external-dns |
The Application is multi-source: the chart is pulled from the upstream Helm repo, the values.yaml is sourced from this repo via $values/k8s/envs/dev/sys/external-dns/values.yaml.
What it syncs¶
From values.yaml:
provider: cloudflare
sources:
- ingress
- service
domainFilters:
- tomoda.life
interval: 1m
policy: sync
registry: txt
txtOwnerId: k8s-dev
- Provider: Cloudflare. External-DNS talks to the Cloudflare API. There is no Cloud DNS / Route 53 integration here — Cloudflare is the only authoritative source for
tomoda.life. - Sources: Ingress + Service. Every
Ingresshost and everyLoadBalancerService's annotated hostname becomes a DNS record. In practice this is dominated by Ingresses (since the cluster has exactly one LoadBalancer Service, the Traefik one). - Domain filter. Only
tomoda.lifeand its subdomains are touched. Records outside this zone are ignored, even if they appear in cluster manifests. - Policy:
sync. External-DNS will both create and delete records to match cluster state. If an Ingress is removed, its DNS record is removed. (The alternativeupsert-onlywould leak records.) - Registry:
txtwithtxtOwnerId: k8s-dev. Every managed record gets a sibling TXT record marking ownership. External-DNS refuses to delete records it doesn't own — important when multiple things write to the same Cloudflare zone.
Cloudflare credentials¶
The Cloudflare API token is not in this repo:
extraEnv:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: external-dns-cloudflare-secret
key: api-token
The Secret external-dns-cloudflare-secret in the external-dns namespace is populated out-of-band (Cloudflare API token with Zone:DNS:Edit permissions for tomoda.life). It is not pulled by External Secrets — this is a one-time bootstrap secret. If you rotate the token, update the K8s Secret directly and restart the External-DNS pod.
Verifying¶
# Watch what External-DNS is doing
kubectl logs -n external-dns deploy/external-dns -f
# Confirm a record exists
dig +short argo-app.tomoda.life
A successful reconciliation log line names the record, the type, and the owner TXT it wrote. If a record is missing, common causes are: hostname not under tomoda.life, Ingress not yet picked up by Traefik (no LoadBalancer status address yet), or the Cloudflare token lacking permissions.
Operational notes¶
- One-minute interval. New Ingresses get DNS within ~60 seconds. Faster polls are possible by lowering
interval, but Cloudflare will rate-limit aggressive callers. - Adding a new public hostname requires nothing beyond declaring it on an Ingress under
tomoda.life. No manual DNS work. - Moving a hostname out of the cluster means deleting the Ingress (External-DNS removes the record) and then setting whatever new record you want in Cloudflare directly. Avoid leaving Ingresses behind purely as "DNS holders".