6a07f91ea1
Dynamically-updated primary zones were only reaching the secondary pods on the hardcoded 1h SOA refresh: the operator emitted no NOTIFY, and a zone's only apex NS is the primary itself, so default 'notify yes' reached no one. Queries load-balanced across the serve VIP hit stale secondaries and returned NXDOMAIN (negatively cached downstream for the 300s SOA minimum), so records flapped for up to an hour after every update. Add 'notify explicit' + 'also-notify' with the secondary pod IPs to primary zone stanzas so an update NOTIFYs the secondaries for an immediate IXFR. Applied via modzone, so existing zones pick it up on the next reconcile. Also shorten the seed SOA refresh/retry/minimum as a fallback for missed NOTIFYs and to shrink stale-NXDOMAIN negative caching.
83 lines
3.2 KiB
Go
83 lines
3.2 KiB
Go
package bind
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ZoneFilePath returns the on-pod path of a zone database file.
|
|
func ZoneFilePath(zone string) string {
|
|
return fmt.Sprintf("%s/zones/db.%s", DataDir, strings.TrimSuffix(zone, "."))
|
|
}
|
|
|
|
// CatalogFilePath returns the on-pod path of a catalog zone database file.
|
|
func CatalogFilePath(zone string) string {
|
|
return fmt.Sprintf("%s/catalog/db.%s", DataDir, strings.TrimSuffix(zone, "."))
|
|
}
|
|
|
|
// ZoneExists reports whether a zone is currently loaded on the pod.
|
|
func (e *Executor) ZoneExists(ctx context.Context, namespace, pod, zone, view string) bool {
|
|
args := []string{"zonestatus", zone}
|
|
if view != "" {
|
|
args = append(args, "in", view)
|
|
}
|
|
_, err := e.Rndc(ctx, namespace, pod, args...)
|
|
return err == nil
|
|
}
|
|
|
|
// 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)
|
|
ns := "ns1." + origin
|
|
// Short refresh/retry so a secondary that misses a NOTIFY (e.g. its pod IP
|
|
// changed and the primary's also-notify was briefly stale) still converges
|
|
// in minutes, not the hour a 3600s refresh would impose. minimum is the
|
|
// negative-cache TTL: keep it low so a stale-secondary NXDOMAIN does not
|
|
// stick in downstream resolvers for long. NOTIFY (also-notify on the
|
|
// primary) remains the fast path; these are the fallback.
|
|
content := fmt.Sprintf(`$TTL 3600
|
|
@ IN SOA %s hostmaster.%s (
|
|
%d ; serial
|
|
300 ; refresh
|
|
60 ; retry
|
|
1209600 ; expire
|
|
60 ) ; minimum
|
|
@ IN NS %s
|
|
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 {
|
|
return fmt.Errorf("seed zone %s: %w (out: %s)", zone, err, out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddCatalogMember registers a member zone in a catalog zone by adding the
|
|
// catalog PTR record, so secondaries auto-provision it.
|
|
func (e *Executor) AddCatalogMember(ctx context.Context, namespace, pod, catalogZone, memberZone string, creds TSIGCreds) error {
|
|
hash := catalogHash(memberZone)
|
|
owner := fmt.Sprintf("%s.zones.%s", hash, dot(catalogZone))
|
|
updates := []RecordUpdate{{
|
|
FQDN: owner,
|
|
Type: "PTR",
|
|
TTL: 3600,
|
|
Values: []string{dot(memberZone)},
|
|
}}
|
|
return e.NSUpdate(ctx, namespace, pod, catalogZone, creds, updates)
|
|
}
|
|
|
|
// RemoveCatalogMember deregisters a member zone from a catalog zone.
|
|
func (e *Executor) RemoveCatalogMember(ctx context.Context, namespace, pod, catalogZone, memberZone string, creds TSIGCreds) error {
|
|
hash := catalogHash(memberZone)
|
|
owner := fmt.Sprintf("%s.zones.%s", hash, dot(catalogZone))
|
|
updates := []RecordUpdate{{FQDN: owner, Type: "PTR", Delete: true}}
|
|
return e.NSUpdate(ctx, namespace, pod, catalogZone, creds, updates)
|
|
}
|