Skip to main content
ExternalDNS publishes DNS records for a cluster’s Services and Ingresses. It can write them into DNZ in two ways: Prefer the webhook when DNZ’s API is easier to reach from the cluster than its DNS port (behind a reverse proxy, HTTPS only), when you’d rather hand out an API token than a TSIG key with zone transfers, or when you want ExternalDNS’s records marked in DNZ. Prefer RFC 2136 when you’d rather not run a sidecar and DNZ’s DNS port is reachable from the pods; see Dynamic updates.

How the webhook runs

ExternalDNS’s webhook protocol has no authentication, so the webhook runs as a sidecar in ExternalDNS’s pod: it listens on 127.0.0.1:8888 (where ExternalDNS calls by default) and holds the API token; /healthz on port 8080 serves the pod’s probes. The sidecar is DNZ’s own image (terrificci/dnz) started as dnz externaldns-webhook, a client of DNZ’s API like the rest of the dnz command line, so DNZ’s server needs no setting for it.
  • Zones: every primary zone the token reaches, narrowed by DNZ_DOMAIN_FILTER.
  • Changes: one bulk request per zone, all or nothing.
  • Ownership: ExternalDNS’s TXT registry records (a-<name>) are stored as they are, so ExternalDNS only touches records it created. Records you made in DNZ stay yours.
  • Change approval: a change waiting for approval is logged with the request’s number and not sent again while it waits.
  • Records in views, disabled records and ALIAS records are left out; routing policies (setIdentifier) aren’t supported.

Set it up

1

Create an API token

Settings → API tokens → Create token with zones.read and records.write, limited to the zones ExternalDNS may manage. Or:
2

Store the token

3

Install ExternalDNS with Helm

external-dns-values.yaml
4

Check the webhook's log

It starts with the token, division and zones it found:
policy: sync also deletes records whose Service or Ingress is gone; upsert-only never deletes. txtOwnerId must differ per cluster when several clusters share a zone.

Webhook settings

Each setting is an environment variable or a flag of dnz externaldns-webhook (--help lists them). Outside Kubernetes the same command runs next to an ExternalDNS started any other way:

Try it on kind

A throwaway kind cluster publishing a Service into a DNZ zone. The pods only need to reach DNZ_URL (on Docker Desktop, http://host.docker.internal:8080 reaches a DNZ on the same machine).
hello gets an A record with the Service’s IP, tagged external-dns, and a-hello the TXT that marks it as owned by dnz-demo. Delete the Service and the next loop removes both. Clean up with kind delete cluster --name dnz-demo.

Troubleshooting

  • checking DNZ_TOKEN against DNZ and the pod restarts: the token is wrong or expired, or DNZ_SERVER isn’t reachable from the pod.
  • the token reaches no zone the domain filter allows: the token is limited to other zones, or the zone is a secondary (change it on its primary).
  • ExternalDNS logs an error with DNZ’s message (a CNAME next to other records, a name with an ALIAS): ExternalDNS retries every loop until the conflict is gone.
  • change request #… waits for approval in DNZ: change approval is on for the zone; an approver accepts it under DNS → Changes or with dnz change approve <id>.