Skip to content

Gateway DNS dual-writes CNAME and A/AAAA for v4./v6. — PowerDNS 422 hot-loop #315

Description

@mattdjenkinson

Summary

For gateway v4. / v6. hostnames, NSO ends up with two writers on the same FQDN: Gateway DNS creates CNAMEs to the apex, while another path (ExternalDNS off gateway addresses) creates A/AAAA. PowerDNS rejects the CNAME (422 Conflicts with pre-existing RRset), and dnsrecordset-powerdns retries forever.

Staging hit this on project datum-cloud (claude-test-connector-proxy, customer-api-l06tj1). Live resolution still works via the A/AAAA records; the damage is a stuck reconcile loop that trips ControllerReconcileErrorRatioCritical for dnsrecordset-powerdns (alert coverage expanded in #306 / NSO alerts applied 2026-07-22).

How we got here (staging)

  1. Both CNAME and A/AAAA existed for the same v4. / v6. names (messy dual ownership; CNAME side already failing).
  2. 2026-07-10 ~11:35 UTC — Domain RDAP refresh for prism.staging.env.datum.net briefly set status.nameservers to parent *.datumdomains.net (no overlap with DNSZone *.datum-staging.net). HasDNSAuthority failed, so garbageCollectDNSRecordSets deleted the four CNAMEs even though Gateway listeners/addresses had not changed.
  3. 2026-07-11 ~14:54 UTC — next RDAP refresh restored the correct NS. Authority returned; NSO recreated the CNAMEs into the still-present A/AAAAs. Hot loop continues.

Actors for the Jul 10–11 delete/recreate were controllers only (control@networking.datumapis.com). No human edited the DNSRecordSets in that window.

Root cause

Two bugs that compound:

  1. Dual-write. Gateway DNS (gateway_dns_controller.go) programs CNAMEs for claimed hostnames including v4. / v6. from status addresses. ExternalDNS (or the A/AAAA path feeding it) also programs those same names as address records. Conflict detection only flags same-hostname DNSRecordSets with a different managed-by, so same-FQDN A/AAAA from the other path do not block CNAME create.
  2. GC on a single authority miss. ensureDNSRecordSets omits hostnames from desiredRecordSetNames when HasDNSAuthority is false, then GC deletes them. A transient Domain NS flap is enough to reap records the Gateway still wants.

dns-operator surfaces this as repeated 422s (related: datum-cloud/dns-operator#56 for cross-type conflict handling). Stopping the dual-write and softening GC is the NSO-side prevention.

Acceptance

  • Exactly one writer owns v4. / v6. gateway hostnames (CNAME-to-apex or A/AAAA, not both). Prefer documenting which path is canonical and disabling the other.
  • Conflict detection treats any conflicting RRset / DNSRecordSet at the same FQDN as a blocker (or clears it deliberately), not only differently-labeled peers.
  • Transient Domain authority loss does not delete Gateway DNSRecordSets on a single failed check (sustained failure / backoff, or pause programming without reap).
  • Unit or e2e coverage: recreate-after-authority-restore does not produce CNAME+A/AAAA at the same name; optional case that a Domain NS blip does not GC wanted CNAMEs.

Related

  • Alert that pages this: NSO feat: generalize controller-health alerts to all controllers #306 (generic ControllerReconcileErrorRatioCritical)
  • dns-operator#56 — PowerDNS 422 when writing over a pre-existing RRset (defense in depth, not the dual-write fix)
  • dns-operator#59 — queue saturation (separate; this incident is error ratio from 422 retries, not wait latency)

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions