From f3c76f6d18ad9ecdaef356c03cb0e30ebd576142 Mon Sep 17 00:00:00 2001 From: Debian Date: Fri, 12 Jun 2026 13:42:21 +0200 Subject: [PATCH] =?UTF-8?q?fix(cluster):=20selbstheilende=20public=5Fip=20?= =?UTF-8?q?+=20advert=5Fint=202=20(Flapping)=20=E2=80=94=20v1.2.107?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei keepalived-Flapping-Restursachen: 1) Strukturell: der Cluster-Push (autoRegister, Primary→Secondary) trägt KEINE public_ip → der Empfänger AgentRegisterPeer ließ sie NULL → Peer fehlte im nft-peer_ipv4-Set → VRRP-Adverts (eth0/VI_1) nur via conntrack → Flapping. (utm-1 lernte utm-2 korrekt via Joiner-Client-IP in preRegisterJoiner; die Gegenrichtung fehlte.) Fix: AgentRegisterPeer fällt bei leerem req.PublicIP auf die mTLS-Client-IP (c.ClientIP()) zurück — die EIGEN-IP des Peers, nicht die VIP. Selbstheilend, überlebt Re-Joins/Failover. 2) advert_int 1 → 2 (Master-Down ~6s statt ~3s): reißt nicht mehr bei kurzen VM-/Heartbeat-Hiccups (VI_HB). Trade-off: Failover-Erkennung ~6s. Tests: advert_int 2 im Render. Co-Authored-By: Claude Opus 4.8 (1M context) --- VERSION | 2 +- internal/handlers/cluster.go | 8 ++++++++ internal/keepalived/keepalived.conf.tpl | 4 ++-- internal/keepalived/keepalived_test.go | 12 ++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 51bdc7f..349813b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.106 \ No newline at end of file +1.2.107 \ No newline at end of file diff --git a/internal/handlers/cluster.go b/internal/handlers/cluster.go index 3b24169..f58eca4 100644 --- a/internal/handlers/cluster.go +++ b/internal/handlers/cluster.go @@ -925,6 +925,14 @@ func (h *ClusterHandler) AgentRegisterPeer(c *gin.Context) { if req.PublicIP != "" { v := req.PublicIP n.PublicIP = &v + } else if ip := c.ClientIP(); ip != "" { + // Der Push-Payload (autoRegister, Primary→Secondary) trägt KEINE + // public_ip → sonst bliebe sie NULL und der Peer fehlt im nft- + // peer_ipv4-Set → VRRP-Adverts nur via conntrack → Flapping. Der + // pushende Peer verbindet sich über mTLS von seiner EIGEN-IP (nicht + // der VIP — der Kernel nimmt die primäre Interface-IP als Source), + // genau wie preRegisterJoiner die Joiner-IP übernimmt. Selbstheilend. + n.PublicIP = &ip } if req.InternalIP != "" { v := req.InternalIP diff --git a/internal/keepalived/keepalived.conf.tpl b/internal/keepalived/keepalived.conf.tpl index f01f7f7..9e0676f 100644 --- a/internal/keepalived/keepalived.conf.tpl +++ b/internal/keepalived/keepalived.conf.tpl @@ -44,7 +44,7 @@ vrrp_instance VI_1 { interface {{ .Interface }} virtual_router_id {{ .RouterID }} priority {{ .Priority }} - advert_int 1 + advert_int 2 nopreempt {{ if .SrcIP }} unicast_src_ip {{ .SrcIP }} unicast_peer { @@ -71,7 +71,7 @@ vrrp_instance VI_HB { interface {{ .HBInterface }} virtual_router_id {{ .HBRouterID }} priority {{ .Priority }} - advert_int 1 + advert_int 2 nopreempt {{ if .HBSrcIP }} unicast_src_ip {{ .HBSrcIP }} unicast_peer { diff --git a/internal/keepalived/keepalived_test.go b/internal/keepalived/keepalived_test.go index 70441a7..767cf74 100644 --- a/internal/keepalived/keepalived_test.go +++ b/internal/keepalived/keepalived_test.go @@ -54,6 +54,18 @@ func TestTemplateGARPRefresh(t *testing.T) { } } +// advert_int 2 (statt 1): Master-Down ~6s — reißt nicht bei kurzen +// VM-/Heartbeat-Hiccups (Flapping-Schutz im virtualisierten Cluster). +func TestTemplateAdvertInt(t *testing.T) { + out := render(t, testView()) + if strings.Contains(out, "advert_int 1\n") { + t.Fatalf("advert_int sollte 2 sein (nicht 1):\n%s", out) + } + if c := strings.Count(out, "advert_int 2"); c != 2 { + t.Fatalf("erwarte advert_int 2 in beiden Instanzen, gefunden: %d", c) + } +} + // Track-Scripts dürfen KEIN weight haben: in einer vrrp_sync_group ignoriert // keepalived gewichtete Scripts → Health-Checks wären wirkungslos. Ohne weight // wirken sie als FAULT-Trigger.