reconcile zone apex NS off the pod IP placeholder
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

Add BindZone.spec.nameservers and sync the apex NS RRset on every reconcile.
This commit is contained in:
2026-09-26 18:27:11 +10:00
parent 03df5d56b4
commit 4a41cbc427
14 changed files with 346 additions and 39 deletions
+55
View File
@@ -114,3 +114,58 @@ func alsoNotifyList(addrs []string, key string) string {
}
return strings.Join(parts, " ")
}
// absolute qualifies a nameserver name. Unlike record owner names, a
// spec.nameservers entry is always a full domain name, never relative to the
// zone: an in-zone nameserver is spelled out in full.
func absolute(name string) string { return strings.TrimSuffix(name, ".") + "." }
// zoneNameservers resolves the names to publish in a zone's apex NS RRset: the
// declared nameservers, else the primary's stable in-cluster DNS name. The
// fallback is deliberately out-of-zone, so no pod IP is needed as glue.
func zoneNameservers(declared []string, cluster *bindv1alpha1.BindCluster) []string {
if len(declared) > 0 {
return declared
}
return []string{primaryAddress(cluster.Name, cluster.Namespace) + "."}
}
// apexNSUpdates returns the dynamic-update ops that keep a zone's apex NS RRset
// equal to nameservers, plus removal of the seed's ns1 glue once the zone
// declares its own nameservers. Ops colliding with a spec.records entry are
// dropped: records are applied afterwards and would re-add them, and the churn
// would bump the serial on every reconcile.
func apexNSUpdates(zone *bindv1alpha1.BindZone, nameservers []string) []bind.RecordUpdate {
owns := func(name, typ string) bool {
for _, rec := range zone.Spec.Records {
if strings.EqualFold(rec.Type, typ) && fqdn(rec.Name, zone.Spec.ZoneName) == fqdn(name, zone.Spec.ZoneName) {
return true
}
}
return false
}
ttl := zone.Spec.DefaultTTL
if ttl <= 0 {
ttl = 3600
}
var updates []bind.RecordUpdate
if !owns("@", "NS") {
values := make([]string, 0, len(nameservers))
for _, ns := range nameservers {
values = append(values, absolute(ns))
}
updates = append(updates, bind.RecordUpdate{FQDN: fqdn("@", zone.Spec.ZoneName), Type: "NS", TTL: ttl, Values: values})
}
// The seed's placeholder glue pins a pod IP that goes stale on the first
// reschedule; drop it once the zone names its real nameservers.
if glue := fqdn("ns1", zone.Spec.ZoneName); len(zone.Spec.Nameservers) > 0 && !owns("ns1", "A") {
published := false
for _, ns := range nameservers {
published = published || absolute(ns) == glue
}
if !published {
updates = append(updates, bind.RecordUpdate{FQDN: glue, Type: "A", Delete: true})
}
}
return updates
}