feat(cluster): Replikations-Reconcile + Backend-Down nur auf VIP-Master — v1.3.7
- fix(scheduler): backend.down-Check läuft nur noch wenn dieser Node den VIP hält (nodeHoldsVIP). Ein keepalived-BACKUP-Node hat KEINE VLAN-IP → erreicht die Backend-Subnetze nicht → sah bisher ALLE Backends L4-down und feuerte Dauer-Fehlalarme (Hauptquelle des alert_events-Spams). Master sieht die echten States. - feat(ctl): `cluster-reconcile-replication` — bringt Publication/Grants/ Subscription idempotent in den Soll-Zustand (Publisher: fehlende Shared- Tables ADD, node-lokale DROP, GRANT SELECT für Replikator; Subscriber: neue Tabellen leeren + REFRESH). Läuft im postinst nach migrate. - fix(packaging): postinst re-added network_interfaces/ip_addresses bei JEDEM Upgrade in die Publication (alter fester Block) → ersetzt durch den Reconcile. DAS war die Wiederkehr-Ursache. - fix(replication): waf_alerts → localOnlyTables (Event-Daten, node-lokal wie alert_events). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -693,6 +693,56 @@ func runWGClientTunnelCheck(ctx context.Context, pool *pgxpool.Pool, a *alerts.S
|
||||
|
||||
var egBackendRE = regexp.MustCompile(`^eg_backend_(\d+)$`)
|
||||
|
||||
// nodeHoldsVIP meldet true, wenn dieser Node aktuell mindestens eine
|
||||
// is_vip-Adresse lokal trägt — also der keepalived-MASTER ist. Nur der
|
||||
// Master hält die VLAN-Gateway-VIPs und erreicht damit die Backend-
|
||||
// Subnetze; ein BACKUP-Node hat KEINE VLAN-IP und sieht deshalb JEDES
|
||||
// Backend als L4-down. Spiegelt SystemHandler.VIPStatus (net.Interfaces,
|
||||
// kein Shell-out).
|
||||
func nodeHoldsVIP(ctx context.Context, pool *pgxpool.Pool) bool {
|
||||
if pool == nil {
|
||||
return false
|
||||
}
|
||||
rows, err := pool.Query(ctx,
|
||||
`SELECT address FROM ip_addresses WHERE is_vip = true AND active = true`)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer rows.Close()
|
||||
var vips []string
|
||||
for rows.Next() {
|
||||
var addr string
|
||||
if err := rows.Scan(&addr); err == nil {
|
||||
vips = append(vips, addr)
|
||||
}
|
||||
}
|
||||
if len(vips) == 0 {
|
||||
return false
|
||||
}
|
||||
local := make(map[string]bool)
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, ifc := range ifaces {
|
||||
addrs, err := ifc.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if ipnet, ok := a.(*net.IPNet); ok {
|
||||
local[ipnet.IP.String()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range vips {
|
||||
if local[v] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// runBackendDownCheck liest HAProxy-Stats via Admin-Socket und feuert
|
||||
// einen Error-Alert für jedes Backend bei dem alle Server DOWN sind
|
||||
// (und mind. einer einen echten Health-Check hat). Dedupe 12h pro Backend.
|
||||
@@ -700,6 +750,14 @@ func runBackendDownCheck(ctx context.Context, pool *pgxpool.Pool, a *alerts.Serv
|
||||
if a == nil || d == nil {
|
||||
return
|
||||
}
|
||||
// Nur auf dem VIP-Master prüfen. Ein BACKUP-Node hält die VLAN-
|
||||
// Gateway-VIPs nicht und kann die Backend-Subnetze gar nicht erreichen
|
||||
// → jeder Health-Check läuft L4TOUT → Dauer-"backend.down"-Fehlalarm
|
||||
// (Hauptquelle des alert_events-Spams). Der Master bedient den Traffic
|
||||
// und sieht die echten Backend-States.
|
||||
if !nodeHoldsVIP(ctx, pool) {
|
||||
return
|
||||
}
|
||||
dialer := net.Dialer{Timeout: 2 * time.Second}
|
||||
conn, err := dialer.DialContext(ctx, "unix", "/run/haproxy/admin.sock")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user