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
+9
View File
@@ -206,6 +206,9 @@ func catalogZonesClause(in RenderInput, isPrimary bool, indent string) string {
if len(primaries) == 0 && in.PrimaryAddress != "" {
primaries = []string{in.PrimaryAddress}
}
if len(primaries) == 0 {
return ""
}
var b strings.Builder
b.WriteString(indent + "catalog-zones {\n")
b.WriteString(fmt.Sprintf("%s zone \"%s\" default-primaries { %s };\n", indent, in.Catalog.Spec.ZoneName, terminate(primaries)))
@@ -227,6 +230,12 @@ func renderCatalogZoneDecl(in RenderInput, isPrimary bool, indent string) string
if len(primaries) == 0 && in.PrimaryAddress != "" {
primaries = []string{in.PrimaryAddress}
}
if len(primaries) == 0 {
// Primary IP not known yet; omit the secondary catalog zone rather than
// emit an invalid empty primaries list. A Pod-triggered reconcile renders
// it once the primary pod has an IP.
return ""
}
var b strings.Builder
b.WriteString(fmt.Sprintf("%szone \"%s\" {\n", indent, cat.Spec.ZoneName))
b.WriteString(indent + " type secondary;\n")
+27
View File
@@ -53,6 +53,33 @@ func TestRenderCatalogOnSecondaryOnly(t *testing.T) {
}
}
func TestRenderCatalogOmittedWhenPrimaryIPUnknown(t *testing.T) {
// Primary IP not known yet and no explicit default-primaries: the secondary
// must not emit a catalog-zones / secondary catalog zone with an empty
// primaries list (which BIND rejects at config load).
in := RenderInput{
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal"}},
PrimaryAddress: "",
}
_, secondary := RenderNamedConf(in)
if strings.Contains(secondary, "catalog-zones") || strings.Contains(secondary, "primaries {") {
t.Fatalf("secondary must omit catalog primaries when the primary IP is unknown:\n%s", secondary)
}
}
func TestRenderCatalogUsesPrimaryIP(t *testing.T) {
in := RenderInput{
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
Catalog: &bindv1alpha1.BindCatalogZone{Spec: bindv1alpha1.BindCatalogZoneSpec{ZoneName: "catalog.internal"}},
PrimaryAddress: "10.42.0.7",
}
_, secondary := RenderNamedConf(in)
if !strings.Contains(secondary, "primaries { 10.42.0.7; }") {
t.Fatalf("secondary should point primaries at the primary pod IP:\n%s", secondary)
}
}
func TestRenderACL(t *testing.T) {
in := RenderInput{
Cluster: newCluster(bindv1alpha1.ModeAuthoritative),
+10 -8
View File
@@ -26,14 +26,15 @@ func (e *Executor) ZoneExists(ctx context.Context, namespace, pod, zone, view st
return err == nil
}
// WriteSeedZone writes a minimal loadable zone file (SOA + apex NS) to path,
// creating parent directories. It is only safe to call when creating a zone, as
// it overwrites any existing file.
func (e *Executor) WriteSeedZone(ctx context.Context, namespace, pod, zone, path, primaryNS string, serial int64) error {
// WriteSeedZone writes a minimal loadable zone file (SOA + apex NS + glue) to
// path, creating parent directories. The apex NS is the in-zone name ns1, and a
// glue A record pointing at primaryIP is included so BIND's check-integrity
// accepts the zone (an in-zone NS without an address record is a load error).
// It is only safe to call when creating a zone, as it overwrites any existing
// file. This is a placeholder that is replaced once real records are loaded.
func (e *Executor) WriteSeedZone(ctx context.Context, namespace, pod, zone, path, primaryIP string, serial int64) error {
origin := dot(zone)
if primaryNS == "" {
primaryNS = "ns1." + origin
}
ns := "ns1." + origin
content := fmt.Sprintf(`$TTL 3600
@ IN SOA %s hostmaster.%s (
%d ; serial
@@ -42,7 +43,8 @@ func (e *Executor) WriteSeedZone(ctx context.Context, namespace, pod, zone, path
1209600 ; expire
300 ) ; minimum
@ IN NS %s
`, dot(primaryNS), origin, serial, dot(primaryNS))
ns1 IN A %s
`, ns, origin, serial, ns, primaryIP)
cmd := []string{"sh", "-c", fmt.Sprintf("mkdir -p \"$(dirname '%s')\" && cat > '%s'", path, path)}
if out, err := e.Exec(ctx, namespace, pod, cmd, content); err != nil {