Summary
Removing a DNSRecordSet does not reliably remove the record from PowerDNS. If the operator process restarts during the delete window, the PowerDNS RRset is orphaned — nothing re-triggers its cleanup — so a stale CNAME/ALIAS/A/etc. keeps resolving after the owning resource is gone.
The delete logic, while the operator is running, is correct: it targets the exact (zone, type, name) RRset and retries through API errors (a 422 does not drop anything). The gap is durability across a restart, plus a dead cleanup path that was never implemented.
Root cause
- The PowerDNS-writing controller registers no finalizer.
DNSRecordSetPowerDNSReconciler (internal/controller/dnsrecordset_powerdns_controller.go) has no finalizer at all. Deletion is driven only by an in-memory watch DeleteFunc + workqueue; the actual removal happens when reconcile sees no live CR owns the tuple:
// dnsrecordset_powerdns_controller.go:126-127
if owner == nil {
pdnsErr = r.PDNS.DeleteRRSet(ctx, zone.Spec.DomainName, req.RecordSetType, req.RecordSetName)
- The downstream finalizer removes itself without cleaning PowerDNS.
downstreamRSFinalizer = "dns.networking.miloapis.com/finalize-dnsrecordset-downstream" (dnsrecordset_downstream_controller.go:33) is dropped unconditionally on delete, with no PDNS call:
// dnsrecordset_downstream_controller.go:64-73
if controllerutil.ContainsFinalizer(&rs, downstreamRSFinalizer) {
base := rs.DeepCopy()
controllerutil.RemoveFinalizer(&rs, downstreamRSFinalizer) // no PDNS cleanup
if err := r.Patch(ctx, &rs, client.MergeFrom(base)); err != nil { ... }
}
- The cleanup helper is a dead comment.
cleanupPDNSForRecordSet is referenced only by a doc comment (dnsrecordset_downstream_controller.go:212) and is not defined anywhere in internal/.
So once the CR leaves etcd, the queued PDNS delete is the only thing standing. A crash/restart in that window loses it; zone-level re-enqueue only fans out to CRs that still exist, so nothing re-drives the orphaned RRset.
Impact
Orphaned provider records (CNAME/ALIAS especially) that survive proxy/hostname teardown — keep resolving to a canonical name that no longer routes, and can block a later resource from re-claiming the same name.
Fix direction
Give the record a finalizer that guarantees PDNS deletion before removal — either on the PowerDNS controller, or implement cleanupPDNSForRecordSet and gate downstreamRSFinalizer removal on its success (retry on error rather than removing unconditionally).
Related
Verified on main @ 81f7549.
Summary
Removing a
DNSRecordSetdoes not reliably remove the record from PowerDNS. If the operator process restarts during the delete window, the PowerDNS RRset is orphaned — nothing re-triggers its cleanup — so a staleCNAME/ALIAS/A/etc. keeps resolving after the owning resource is gone.The delete logic, while the operator is running, is correct: it targets the exact
(zone, type, name)RRset and retries through API errors (a 422 does not drop anything). The gap is durability across a restart, plus a dead cleanup path that was never implemented.Root cause
DNSRecordSetPowerDNSReconciler(internal/controller/dnsrecordset_powerdns_controller.go) has no finalizer at all. Deletion is driven only by an in-memory watchDeleteFunc+ workqueue; the actual removal happens when reconcile sees no live CR owns the tuple:downstreamRSFinalizer = "dns.networking.miloapis.com/finalize-dnsrecordset-downstream"(dnsrecordset_downstream_controller.go:33) is dropped unconditionally on delete, with no PDNS call:cleanupPDNSForRecordSetis referenced only by a doc comment (dnsrecordset_downstream_controller.go:212) and is not defined anywhere ininternal/.So once the CR leaves etcd, the queued PDNS delete is the only thing standing. A crash/restart in that window loses it; zone-level re-enqueue only fans out to CRs that still exist, so nothing re-drives the orphaned RRset.
Impact
Orphaned provider records (CNAME/ALIAS especially) that survive proxy/hostname teardown — keep resolving to a canonical name that no longer routes, and can block a later resource from re-claiming the same name.
Fix direction
Give the record a finalizer that guarantees PDNS deletion before removal — either on the PowerDNS controller, or implement
cleanupPDNSForRecordSetand gatedownstreamRSFinalizerremoval on its success (retry on error rather than removing unconditionally).Related
ALIAS(www.ab.dk) observed in prodDNSRecordSetlifecycleVerified on
main@81f7549.