apply records before the apex NS, so in-zone glue exists first
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

named rejects an apex NS pointing at an in-zone name with no address record,
so the glue has to land in an earlier transaction.
This commit is contained in:
2026-09-26 19:41:53 +10:00
parent 08d46ccce0
commit cc714dc2b4
4 changed files with 63 additions and 28 deletions
+21 -20
View File
@@ -118,17 +118,27 @@ func (r *BindZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
return r.setPhase(ctx, &zone, "Error", "AddZoneFailed", err.Error())
}
// Converge the apex NS on every pass, not only at seed time, so a zone that
// was seeded with the placeholder moves onto its real nameservers. Only for a
// zone that declared them: otherwise the operator would fight whoever else
// manages the RRset.
// Records are applied before the apex NS: an in-zone nameserver's address
// record has to exist first, or named rejects the apex transaction with a
// post-update nameserver sanity check failure.
recordCount := 0
if isPrimaryType(zone.Spec.Type) {
creds, credErr := r.zoneUpdateCreds(ctx, &zone)
if nsDeclared {
if credErr != nil {
return r.setPhase(ctx, &zone, "Error", "NoUpdateKey", credErr.Error())
records := recordsToUpdates(zone.Spec.ZoneName, zone.Spec.Records, zone.Spec.DefaultTTL)
if isPrimaryType(zone.Spec.Type) && (len(records) > 0 || nsDeclared) {
creds, err := r.zoneUpdateCreds(ctx, &zone)
if err != nil {
return r.setPhase(ctx, &zone, "Error", "NoUpdateKey", err.Error())
}
if len(records) > 0 {
if err := r.Exec.NSUpdate(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, creds, records); err != nil {
return r.setPhase(ctx, &zone, "Error", "RecordUpdateFailed", err.Error())
}
recordCount = len(records)
}
// Converge the apex NS on every pass, not only at seed time, so a zone
// seeded with the placeholder moves onto its real nameservers. Only for a
// zone that declared them: otherwise the operator would fight whoever else
// manages the RRset.
if nsDeclared {
live, err := r.Exec.ApexNS(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, creds)
if err != nil {
return r.setPhase(ctx, &zone, "Error", "ApexNSQueryFailed", err.Error())
@@ -138,21 +148,12 @@ func (r *BindZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
}
if apex := apexNSUpdates(&zone, nameservers, live, nsTTL); len(apex) > 0 {
if err := r.Exec.NSUpdate(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, creds, apex); err != nil {
return r.setPhase(ctx, &zone, "Error", "ApexNSSyncFailed", err.Error())
return r.setPhase(ctx, &zone, "Error", "ApexNSSyncFailed",
fmt.Sprintf("%s (a nameserver inside the zone needs an address record here)", err))
}
logger.Info("apex NS converged", "zone", zone.Spec.ZoneName, "nameservers", nameservers)
}
}
// Seed static records.
if updates := recordsToUpdates(zone.Spec.ZoneName, zone.Spec.Records, zone.Spec.DefaultTTL); len(updates) > 0 {
if credErr != nil {
return r.setPhase(ctx, &zone, "Error", "NoUpdateKey", credErr.Error())
}
if err := r.Exec.NSUpdate(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, creds, updates); err != nil {
return r.setPhase(ctx, &zone, "Error", "RecordUpdateFailed", err.Error())
}
recordCount = len(updates)
}
}
// Register in the catalog so secondaries auto-provision.
+6 -1
View File
@@ -169,6 +169,9 @@ func zoneNameservers(zone *bindv1alpha1.BindZone, cluster *bindv1alpha1.BindClus
// no NS records — a primary always has one. Nothing is retracted in that case:
// retracting blind is what turns a delete into "delete the last NS", which named
// rejects outright.
// ponytail: names already published are diffed by name only, so an edit to just
// the TTL never republishes them (dig +short cannot report a TTL). Re-add the
// whole desired set each pass if TTL edits need to converge.
func apexNSUpdates(zone *bindv1alpha1.BindZone, desired, live []string, ttl int32) []bind.RecordUpdate {
apex := fqdn("@", zone.Spec.ZoneName)
add := missing(desired, live)
@@ -177,8 +180,10 @@ func apexNSUpdates(zone *bindv1alpha1.BindZone, desired, live []string, ttl int3
if len(add) > 0 {
updates = append(updates, bind.RecordUpdate{FQDN: apex, Type: "NS", TTL: ttl, Values: add, PerValue: true})
}
// missing() yields nothing against an empty live set, so an unreadable RRset
// retracts nothing on its own.
del := missing(live, desired)
if len(live) == 0 || len(del) == 0 {
if len(del) == 0 {
return updates
}
updates = append(updates, bind.RecordUpdate{FQDN: apex, Type: "NS", Values: del, PerValue: true, Delete: true})