feat(ha): VIP-Preempt zurück zum PG-Primary — mit gehärtetem Health-Gate — v1.3.16

Preempt-Rückkehr (preempt_delay 120) wieder aktiv: der bevorzugte Node
(PG-Primary, Prio 200) holt die VIP nach Erholung zurück. Der Incident
2026-08-03 (halb-kaputter Node riss die VIP an sich) wird verhindert, weil
keepalived-check.sh jetzt zusätzlich fordert:
  - haproxy-Prozess aktiv
  - :443 gebunden (bedient wirklich Traffic)
Ein nicht-bedienender Node geht damit in FAULT und kann NICHT (mehr) preempten.

Außerdem: CrowdSec-Management-Whitelist (Backend api_backend) fest ins postinst
gebacken (Admin-SPA-Traffic wird nie mehr als http-crawl gebannt, IP-unabhängig,
Incident-Root-Fix) + Altlast netcell-mgmt-whitelist.yaml wird aufgeräumt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-08-03 13:39:15 +02:00
parent d395e3ea68
commit 0846eaa05b
6 changed files with 104 additions and 29 deletions

View File

@@ -54,8 +54,19 @@ type View struct {
HBRouterID int
// GW-Tracking
GWCheckIP string
// PreemptDelay > 0 → Node holt die VIP nach Erholung zurück (nach N Sekunden
// Stabilität). 0 → nopreempt (bleibt Backup). Siehe buildView.
PreemptDelay int
}
// preemptDelaySeconds: der bevorzugte Node (höhere Priorität = PG-Primary) holt
// die VIP erst nach dieser Wartezeit zurück — lange genug, dass ein frisch
// gebooteter/deployter Node erst wirklich bereit ist (Boot + Service-Start),
// bevor er überhaupt preempten darf. Zusammen mit dem gehärteten Health-Check
// (haproxy aktiv + :443 gebunden, keepalived-check.sh) verhindert das den
// Incident 2026-08-03 (halb-kaputter Node riss die VIP an sich).
const preemptDelaySeconds = 120
type generator struct {
pool *pgxpool.Pool
localID string
@@ -167,21 +178,21 @@ func (g *generator) buildView(cs *models.ClusterSettings, vips []VIPEntry, local
v.HBRouterID = 52
}
// State IMMER BACKUP + `nopreempt` auf BEIDEN Nodes. Die Priorität
// entscheidet die INITIAL-Election (primary=200 gewinnt), aber ein
// erholter Node reißt die VIP NIEMALS zurück.
// State IMMER BACKUP; die Priorität entscheidet, welcher Node die VIP
// bevorzugt hält (PG-Primary=200 > Standby=100). Mit preempt_delay holt
// der bevorzugte Node die VIP nach Erholung zurück (VIP-Affinität zum
// PG-Primary), aber erst nach preemptDelaySeconds Stabilität.
//
// WARUM kein Preempt (Incident 2026-08-03): eine frühere `preempt_delay`-
// Variante ließ den Prio-200-Node die VIP zurückholen, sobald keepalived
// ihn für „gesund" hielt. Die Track-Scripts (chk_edgeguard/chk_gateway)
// können aber „gesund" melden, während der eigentliche Dienst noch kaputt
// ist → der halb-kaputte Primary entriss dem funktionierenden Standby die
// VIP und hielt sie fest (Ausfall, bis der Primary hart abgeschaltet
// wurde). nopreempt verhindert genau das. VIP-Affinität zum Primary ist
// erst wieder vertretbar, wenn der Health-Check „Prozess up aber Dienst
// kaputt" zuverlässig als FAULT erkennt. Bis dahin gilt: Stabilität >
// Affinität. Promotion/PG-Failover bleibt manuell (edgeguard-ctl promote).
// Incident 2026-08-03 & Fix: eine frühere preempt_delay-Variante ließ den
// Prio-200-Node die VIP zurückholen, sobald der Health-Check ihn für
// „gesund" hielt — der prüfte aber NUR die edgeguard-api, nicht ob der Node
// wirklich Traffic bedient. Ein halb-kaputter Primary (api up, haproxy/Netz
// down) riss so die VIP an sich → Ausfall. Preempt ist wieder aktiv, WEIL
// keepalived-check.sh jetzt zusätzlich haproxy-aktiv + :443-gebunden fordert:
// ein nicht-bedienender Node geht in FAULT und kann NICHT preempten.
// Promotion/PG-Failover bleibt manuell (edgeguard-ctl promote).
v.State = "BACKUP"
v.PreemptDelay = preemptDelaySeconds
if local.PGRole == "standby" {
v.Priority = 100
} else if local.PGRole == "primary" || local.Role == "primary" {