Accept intra-cluster NOTIFY on secondaries via allow-notify
Secondaries transfer catalog member and plain secondary zones from the primary Service ClusterIP (stable across primary pod restarts), and BIND derives a zone's implicit allow-notify from its primaries list. But the primary pod's NOTIFYs egress with its *pod* IP as source — k8s Services NAT only the inbound direction — so BIND refuses them as "refused notify from non-primary" and replication falls back to the SOA refresh timer, a 1-hour propagation delay on every dynamic zone (external-dns RFC2136 and dns-updater nsupdates alike). Render an options-scope allow-notify on secondaries covering the primary pod IP (and the transfer address, since an explicit allow-notify replaces the primaries-derived default). The cluster controller resolves the primary pod IP the same way it already does for seeding/also-notify, and the existing Pod watch re-renders the ConfigMap when the pod IP changes.
This commit is contained in:
@@ -22,6 +22,15 @@ type RenderInput struct {
|
||||
Forwards []bindv1alpha1.BindZone
|
||||
// PrimaryAddress is the in-cluster address secondaries transfer from.
|
||||
PrimaryAddress string
|
||||
// PrimaryPodAddresses are the primary pod's own IP(s). Secondaries transfer
|
||||
// from PrimaryAddress (the stable primary Service ClusterIP) but the primary
|
||||
// pod's NOTIFYs egress with its *pod* IP as the source — k8s Services only
|
||||
// NAT the inbound direction — so BIND, whose implicit allow-notify is the
|
||||
// zone's primaries list (the ClusterIP), REFUSES them as "non-primary" and
|
||||
// replication falls back to the SOA refresh timer. Secondaries render these
|
||||
// into an options-scope allow-notify so intra-cluster NOTIFYs are accepted
|
||||
// immediately. Empty leaves BIND's default behaviour unchanged.
|
||||
PrimaryPodAddresses []string
|
||||
}
|
||||
|
||||
// RenderNamedConf returns the primary and secondary named.conf contents for a
|
||||
@@ -86,6 +95,12 @@ func render(in RenderInput, isPrimary bool) string {
|
||||
b.WriteString(" allow-new-zones yes;\n")
|
||||
}
|
||||
b.WriteString(" dnssec-validation auto;\n")
|
||||
// Secondaries accept NOTIFY from the primary's pod IP(s). Catalog member and
|
||||
// plain secondary zones take their implicit allow-notify from their primaries
|
||||
// (the primary Service ClusterIP), but the primary's NOTIFYs are sourced from
|
||||
// its pod IP, so an options-scope allow-notify covering the pod IP(s) is
|
||||
// needed or every NOTIFY is refused and replication waits for the SOA refresh.
|
||||
b.WriteString(allowNotifyClause(in, isPrimary, " "))
|
||||
for _, o := range c.Spec.ExtraOptions {
|
||||
b.WriteString(" " + strings.TrimRight(o, ";") + ";\n")
|
||||
}
|
||||
@@ -277,6 +292,39 @@ func transferPrimaries(in RenderInput) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// allowNotifyClause renders an options-scope allow-notify on secondaries that
|
||||
// permits the primary pod IP(s). Zones (catalog members and plain secondaries)
|
||||
// point their primaries at the primary Service ClusterIP for stable AXFR, which
|
||||
// also becomes their implicit allow-notify — but NOTIFYs leave the primary pod
|
||||
// with its pod IP as source, so without this they are refused as "non-primary".
|
||||
// Emitted only on secondaries and only when the primary pod IP(s) are known.
|
||||
func allowNotifyClause(in RenderInput, isPrimary bool, indent string) string {
|
||||
if isPrimary {
|
||||
return ""
|
||||
}
|
||||
addrs := make([]string, 0, len(in.PrimaryPodAddresses)+1)
|
||||
seen := map[string]bool{}
|
||||
for _, a := range in.PrimaryPodAddresses {
|
||||
a = strings.TrimSpace(strings.TrimRight(a, ";"))
|
||||
if a == "" || seen[a] {
|
||||
continue
|
||||
}
|
||||
seen[a] = true
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
// Keep the transfer address (the Service ClusterIP, or the pod IP when no
|
||||
// primary Service exists) in the set: an explicit allow-notify replaces the
|
||||
// implicit primaries-derived default, so it must still cover that source.
|
||||
if a := strings.TrimSpace(strings.TrimRight(in.PrimaryAddress, ";")); a != "" && !seen[a] {
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return ""
|
||||
}
|
||||
sort.Strings(addrs)
|
||||
return fmt.Sprintf("%sallow-notify { %s };\n", indent, terminate(addrs))
|
||||
}
|
||||
|
||||
func catalogZonesClause(in RenderInput, isPrimary bool, indent string) string {
|
||||
// Only secondaries consume the catalog to auto-provision member zones.
|
||||
if in.Catalog == nil || isPrimary {
|
||||
|
||||
Reference in New Issue
Block a user