fix(cluster): VIP-Failover am Upstream zuverlässig — GARP + Priming-Ping — v1.2.103

Nach v1.2.102 (VIP nicht mehr statisch gebunden) wandert die Public-VIP .100
erstmals wirklich per keepalived zwischen den Node-MACs. Der Hosting-Upstream
lernte die neue MAC aber nicht zuverlässig → .100 nach Schwenk von außen
unerreichbar (vorher maskiert, weil .100 statisch dauerhaft auf utm-1 lag).

Zwei Mechanismen ergänzt:
1) keepalived.conf.tpl global_defs: vrrp_garp_master_repeat 5 +
   vrrp_garp_master_refresh 60 (+ vrrp_garp_interval/gna 0, aus Legacy-Template
   verloren gegangen) → forciertes GARP beim Wechsel + periodische Auffrischung,
   damit die VIP-MAC am Switch nicht altert.
2) keepalived-master.sh: neuer Master pingt den Default-Gateway aus jeder
   Public-IP/VIP an (ping -I <vip>) → Upstream sieht Traffic VON der VIP und
   lernt die MAC sofort. Manche Hoster relearnen nur so, nicht via GARP.

Test: keepalived_test.go prüft GARP-Direktiven im Render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-06-07 18:56:32 +02:00
parent 7b6409b631
commit fd8125247c
4 changed files with 35 additions and 1 deletions

View File

@@ -1 +1 @@
1.2.102
1.2.103

View File

@@ -2,6 +2,14 @@ global_defs {
router_id {{ .RouterID }}
script_user root
enable_script_security
# GARP: beim Master-Wechsel Gratuitous-ARP forciert senden (repeat) UND
# periodisch auffrischen (master_refresh) sonst lässt der Upstream-Switch
# die VIP-MAC altern und die Failover-IP wird nach Minuten unerreichbar.
# Ergänzt durch den Priming-Ping in keepalived-master.sh (Traffic AUS der VIP).
vrrp_garp_interval 0
vrrp_gna_interval 0
vrrp_garp_master_repeat 5
vrrp_garp_master_refresh 60
}
vrrp_script chk_edgeguard {

View File

@@ -43,6 +43,17 @@ func TestTemplateNopreemptOnBothInstances(t *testing.T) {
}
}
// GARP muss forciert + periodisch aufgefrischt werden, sonst altert die
// VIP-MAC am Upstream-Switch und die Failover-IP wird unerreichbar.
func TestTemplateGARPRefresh(t *testing.T) {
out := render(t, testView())
for _, want := range []string{"vrrp_garp_master_refresh", "vrrp_garp_master_repeat"} {
if !strings.Contains(out, want) {
t.Fatalf("global_defs sollte %q enthalten:\n%s", want, out)
}
}
}
// gw-Check darf nicht zu zucken (fall 5, nicht fall 2) — ein kurzer Upstream-
// Blip soll keinen Failover erzwingen.
func TestTemplateGatewayCheckNotTwitchy(t *testing.T) {

View File

@@ -7,6 +7,21 @@
logger -t keepalived -p daemon.warning \
"MASTER: VIP übernommen — PG-Rolle ist noch '$(cat /var/lib/edgeguard/pg_role 2>/dev/null || echo standby)'. Für PG-Failover: edgeguard-ctl promote"
# ── Upstream-ARP/Routing für die Failover-VIP(s) aktualisieren ──
# Manche Hoster lernen die neue MAC einer Failover-IP NICHT zuverlässig über
# Gratuitous-ARP, sondern erst, wenn sie Traffic VON der IP sehen. Ohne das ist
# die VIP nach einem Schwenk von außen unerreichbar. Wir pingen daher den
# Default-Gateway aus jeder Public-IP (inkl. VIP) an (source via -I), damit der
# Upstream die MAC sofort umlernt. Hintergrund-Pings, damit notify nicht blockt.
WAN_DEV="$(ip -4 route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')"
WAN_GW="$(ip -4 route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="via"){print $(i+1); exit}}')"
if [ -n "$WAN_DEV" ] && [ -n "$WAN_GW" ]; then
for vip in $(ip -4 -o addr show dev "$WAN_DEV" scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1); do
ping -I "$vip" -c 3 -W 1 "$WAN_GW" >/dev/null 2>&1 &
done
logger -t keepalived -p daemon.info "MASTER: Upstream-ARP via Ping aus VIP(s) auf $WAN_GW ($WAN_DEV) angestoßen"
fi
# Dienste reloaden/starten damit sie die neu aktiven VIPs binden.
# Squid + Unbound + HAProxy binden beim Start an spezifische IPs — war der Dienst
# während des BACKUP-Zustands gecrasht oder gestoppt, muss er gestartet werden.