Fix zone provisioning: seed glue + IP primaries
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

Two bugs made every provisioned zone fail to load:

1. The seed zone's apex NS (ns1.<zone>) is in-zone but had no address
   record, so BIND check-integrity refused to load it and rndc addzone
   reverted. Add a glue A record pointing at the primary pod IP.
2. Secondaries rendered primaries/default-primaries with the primary's
   DNS name, but BIND only accepts IP addresses there (it read the name
   as a remote-servers list and failed config load, crash-looping the
   secondary). Render the primary pod IP instead, and watch Pods so the
   config re-renders when that IP appears or changes.

- bind.WriteSeedZone writes 'ns1 IN A <primaryIP>' glue
- controllers resolve primaryPodIP and pass it to the seed (requeue if
  the primary has no IP yet)
- BindCluster renders PrimaryAddress from pod-0's IP and watches Pods
- render omits catalog primaries when the IP is unknown (no empty list)
This commit is contained in:
2026-07-03 21:33:31 +10:00
parent bba8c6302f
commit fb103a9e95
8 changed files with 84 additions and 13 deletions
@@ -50,7 +50,11 @@ func (r *BindCatalogZoneReconciler) Reconcile(ctx context.Context, req ctrl.Requ
// Ensure the catalog zone exists on the primary.
if !r.Exec.ZoneExists(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, "") {
if err := r.Exec.WriteSeedZone(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, bind.CatalogFilePath(catalog.Spec.ZoneName), "", 1); err != nil {
primaryIP := primaryPodIP(ctx, r.Client, cluster)
if primaryIP == "" {
return r.fail(ctx, &catalog, "PrimaryNoIP", "waiting for primary pod IP")
}
if err := r.Exec.WriteSeedZone(ctx, catalog.Namespace, primaryPod, catalog.Spec.ZoneName, bind.CatalogFilePath(catalog.Spec.ZoneName), primaryIP, 1); err != nil {
return r.fail(ctx, &catalog, "SeedFailed", err.Error())
}
}
+10 -1
View File
@@ -160,7 +160,10 @@ func (r *BindClusterReconciler) reconcileKeysSecret(ctx context.Context, c *bind
}
func (r *BindClusterReconciler) reconcileConfigMap(ctx context.Context, c *bindv1alpha1.BindCluster) error {
in := bind.RenderInput{Cluster: c, PrimaryAddress: primaryAddress(c.Name, c.Namespace)}
// BIND primaries/default-primaries need the primary's IP address, not a DNS
// name, so render with pod-0's current IP (empty until it is scheduled; the
// Pod watch re-renders when it appears or changes).
in := bind.RenderInput{Cluster: c, PrimaryAddress: primaryPodIP(ctx, r.Client, c)}
var acls bindv1alpha1.BindACLList
if err := r.List(ctx, &acls, client.InNamespace(c.Namespace)); err == nil {
@@ -387,6 +390,12 @@ func (r *BindClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&corev1.Service{}).
Owns(&corev1.ConfigMap{}).
Owns(&corev1.Secret{}).
Watches(&corev1.Pod{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
// Re-render named.conf when a cluster pod's IP appears or changes, so
// secondaries always point primaries/default-primaries at the current
// primary pod IP.
return mapToCluster(o.GetLabels()[clusterLabel], o.GetNamespace())
})).
Watches(&bindv1alpha1.BindACL{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
return mapToCluster(o.(*bindv1alpha1.BindACL).Spec.ClusterRef, o.GetNamespace())
})).
+5 -1
View File
@@ -63,7 +63,11 @@ func (r *BindPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
if !r.Exec.ZoneExists(ctx, policy.Namespace, primaryPod, policy.Spec.ZoneName, policy.Spec.ViewRef) {
if err := r.Exec.WriteSeedZone(ctx, policy.Namespace, primaryPod, policy.Spec.ZoneName, bind.ZoneFilePath(policy.Spec.ZoneName), "", 1); err != nil {
primaryIP := primaryPodIP(ctx, r.Client, cluster)
if primaryIP == "" {
return r.fail(ctx, &policy, "PrimaryNoIP", "waiting for primary pod IP")
}
if err := r.Exec.WriteSeedZone(ctx, policy.Namespace, primaryPod, policy.Spec.ZoneName, bind.ZoneFilePath(policy.Spec.ZoneName), primaryIP, 1); err != nil {
return r.fail(ctx, &policy, "SeedFailed", err.Error())
}
}
+6 -2
View File
@@ -73,8 +73,12 @@ func (r *BindZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
}
created := !r.Exec.ZoneExists(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, zone.Spec.ViewRef)
if created && zone.Spec.Type == bindv1alpha1.ZonePrimary || (created && zone.Spec.Type == "") {
if err := r.Exec.WriteSeedZone(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, bind.ZoneFilePath(zone.Spec.ZoneName), "", 1); err != nil {
if created && (zone.Spec.Type == bindv1alpha1.ZonePrimary || zone.Spec.Type == "") {
primaryIP := primaryPodIP(ctx, r.Client, cluster)
if primaryIP == "" {
return r.setPhase(ctx, &zone, "Pending", "PrimaryNoIP", "waiting for primary pod IP")
}
if err := r.Exec.WriteSeedZone(ctx, zone.Namespace, primaryPod, zone.Spec.ZoneName, bind.ZoneFilePath(zone.Spec.ZoneName), primaryIP, 1); err != nil {
return r.setPhase(ctx, &zone, "Error", "SeedFailed", err.Error())
}
}
+12
View File
@@ -87,6 +87,18 @@ func primaryReady(ctx context.Context, c client.Client, cluster *bindv1alpha1.Bi
return false
}
// primaryPodIP returns the pod IP of a cluster's primary pod (ordinal 0), or an
// empty string if the pod has no IP yet. BIND's primaries/default-primaries
// only accept IP addresses (not hostnames), and zone seeding needs the address
// for glue, so the operator resolves the pod IP rather than using a DNS name.
func primaryPodIP(ctx context.Context, c client.Client, cluster *bindv1alpha1.BindCluster) string {
var pod corev1.Pod
if err := c.Get(ctx, client.ObjectKey{Namespace: cluster.Namespace, Name: primaryPodName(cluster.Name)}, &pod); err != nil {
return ""
}
return pod.Status.PodIP
}
// resolveTSIG reads the material of a BindTSIGKey into TSIG credentials.
func resolveTSIG(ctx context.Context, c client.Client, namespace, keyRef string) (bind.TSIGCreds, error) {
var creds bind.TSIGCreds