Adds a Filter (allow/deny CIDRs + allow/deny domain suffixes) applied to the desired set before reconcile. Range rules match A/AAAA by value and PTR by the address encoded in the reverse-DNS owner, so both the junk A record and its reverse PTR are dropped together. Configured via -deny-ranges/-allow-ranges/ -deny-domains/-allow-domains (comma-separated) or DNS_UPDATER_* env. Purpose: keep k8s pod/service CIDRs, LB VIP ranges and other internal addresses out of the authoritative zones, and stop NOTAUTH updates for reverse zones the server does not host (10.42.x, 198.18.200.x, etc.).
4.2 KiB
dns-updater
A small Go daemon that keeps a host's DNS records current on a BIND server via
TSIG-signed RFC2136 dynamic updates. It replaces the puppet
profiles::dns::updater shell script + dns-update.path/dns-update.service
systemd pair with a single long-running service.
What it does
- Reads a desired-records file (
zone|name|type|ttl|value, the same formatprofiles::dns::recordalready emits). - Pushes the delta to the server — one TSIG-signed UPDATE message per zone,
so one bad zone (e.g. a
NOTZONE) cannot abort the others. - Re-reconciles when:
- the records file changes (inotify on the directory, so atomic replace is caught),
- a new interface address appears (DHCP assign/renew) — address removals (interface down) are ignored, so a transient link drop never disturbs records; loopback/link-local are ignored too,
- a periodic safety-net timer fires,
- it receives
SIGHUP.
- Exposes a local status API (unix socket) for facter and health checks.
Names are qualified correctly: a record name already ending in . is used
verbatim, so there is no .. empty-label bug — and a malformed record is
rejected with a clear error instead of being sent as broken wire data.
Why native RFC2136 (not nsupdate)
The daemon talks the update protocol directly (github.com/miekg/dns), so every
zone update carries a structured server RCODE and error. That is the
observability the shell version lacked — a bad name or a missing zone shows up
immediately in the logs and the status API instead of an opaque
nsupdate ... failed.
Configuration
Flags or env (see packaging/env.sample); env wins via the systemd
EnvironmentFile:
| flag | env | default |
|---|---|---|
-server |
DNS_UPDATER_SERVER |
(required) |
-key-file |
DNS_UPDATER_KEY_FILE |
/etc/dns-updater/key |
-records-file |
DNS_UPDATER_RECORDS_FILE |
/var/lib/dns-updater/records |
-state-file |
DNS_UPDATER_STATE_FILE |
/var/lib/dns-updater/applied |
-watch-interfaces |
DNS_UPDATER_WATCH_INTERFACES |
true |
-resync |
DNS_UPDATER_RESYNC |
10m |
-api |
DNS_UPDATER_API |
/run/dns-updater/api.sock |
-log-level |
DNS_UPDATER_LOG_LEVEL |
info |
-oneshot |
DNS_UPDATER_ONESHOT |
false |
-deny-ranges |
DNS_UPDATER_DENY_RANGES |
(none) |
-allow-ranges |
DNS_UPDATER_ALLOW_RANGES |
(none) |
-deny-domains |
DNS_UPDATER_DENY_DOMAINS |
(none) |
-allow-domains |
DNS_UPDATER_ALLOW_DOMAINS |
(none) |
The TSIG key file is BIND format (key "name" { algorithm ...; secret "..."; };).
Filtering
*-ranges are comma-separated CIDRs; *-domains are comma-separated FQDN
suffixes. Range rules apply to records that carry an address — A/AAAA by value,
PTR by the address encoded in the reverse-DNS owner — so both
prodnxsr01-kube-lb0 A 198.18.200.2 and the matching …200.18.198.in-addr.arpa
PTR are dropped by -deny-ranges=198.18.200.0/24. Domain rules apply to every
record by owner name. Deny wins; a non-empty allow list means "only these".
This keeps k8s/LB/internal addresses (pod/service CIDRs, LB VIP ranges) out of
the authoritative zones and stops NOTAUTH updates for zones the server does not
host.
Status API
GET /status→ JSON: health, managed-record count, last reconcile/change time, and per-zone{adds, deletes, rcode, error}.GET /healthz→ 200 when healthy, 503 otherwise.
The packaged facter fact (/opt/puppetlabs/facter/facts.d/dns_updater.sh)
queries this and emits dns_updater_healthy, dns_updater_zones_failed,
dns_updater_failed_zones, etc. Puppet already knows the desired records (it
writes the file); these facts report what actually landed on the server.
Logging
INFO on real changes (applied) and failures (reconcile partial,
zone update failed with zone + rcode); the steady-state "nothing to do" path
stays at DEBUG, so periodic resyncs and interface flaps do not spam the journal.
slog key=value output parses cleanly in VictoriaLogs.
Build / release
make build # binary
make test # unit + in-process TSIG server integration tests
make rpm # RPM via nfpm (needs the binary)
make patch # tag vX.Y.(Z+1) and push -> Woodpecker release