From fd8125247ccaadf22323a9a884b5be6fa64153cc Mon Sep 17 00:00:00 2001 From: Debian Date: Sun, 7 Jun 2026 18:56:32 +0200 Subject: [PATCH] =?UTF-8?q?fix(cluster):=20VIP-Failover=20am=20Upstream=20?= =?UTF-8?q?zuverl=C3=A4ssig=20=E2=80=94=20GARP=20+=20Priming-Ping=20?= =?UTF-8?q?=E2=80=94=20v1.2.103?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ) → 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) --- VERSION | 2 +- internal/keepalived/keepalived.conf.tpl | 8 ++++++++ internal/keepalived/keepalived_test.go | 11 +++++++++++ packaging/scripts/keepalived-master.sh | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ff710f5..760f327 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.102 \ No newline at end of file +1.2.103 \ No newline at end of file diff --git a/internal/keepalived/keepalived.conf.tpl b/internal/keepalived/keepalived.conf.tpl index 98e6c1b..da4e24b 100644 --- a/internal/keepalived/keepalived.conf.tpl +++ b/internal/keepalived/keepalived.conf.tpl @@ -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 { diff --git a/internal/keepalived/keepalived_test.go b/internal/keepalived/keepalived_test.go index abb6ea1..f7a1f3a 100644 --- a/internal/keepalived/keepalived_test.go +++ b/internal/keepalived/keepalived_test.go @@ -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) { diff --git a/packaging/scripts/keepalived-master.sh b/packaging/scripts/keepalived-master.sh index 7086722..282071c 100644 --- a/packaging/scripts/keepalived-master.sh +++ b/packaging/scripts/keepalived-master.sh @@ -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.