Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca37ee226 | ||
|
|
86aee33308 |
@@ -168,7 +168,8 @@ func main() {
|
||||
if st != nil && st.Completed {
|
||||
// Auto-create /etc/edgeguard/node.conf falls fehlt.
|
||||
_, _ = cluster.EnsureLocalConfig("")
|
||||
if _, err := cluster.EnsureSelfRegistered(ctx, clusterStore, st.FQDN, "primary", version); err != nil {
|
||||
if _, err := cluster.EnsureSelfRegistered(ctx, clusterStore, st.FQDN,
|
||||
localClusterRole(ctx, pool, st), version); err != nil {
|
||||
slog.Warn("self-register in ha_nodes failed", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -927,3 +928,47 @@ func randomEphemeralSecret() []byte {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// localClusterRole ermittelt die eigene Cluster-Rolle für die node-lokale
|
||||
// ha_nodes-Zeile.
|
||||
//
|
||||
// Befund 2026-09-11: Hier stand fest "primary" — für JEDEN Node, bei jedem
|
||||
// API-Start. ha_nodes ist node-lokal (nicht repliziert), also trug sich auch
|
||||
// ein per Join dazugekommener Standby bei sich selbst als "primary" ein. In
|
||||
// der Cluster-Ansicht DIESES Nodes erschienen dadurch beide Knoten als
|
||||
// Primary, und eine Korrektur direkt in der DB hielt nur bis zum nächsten
|
||||
// Neustart.
|
||||
//
|
||||
// Nicht kosmetisch: keepalived.go nutzt `role` als Fallback, wenn pg_role
|
||||
// nicht 'standby' ist. Ein Standby, der sich selbst "primary" nennt, ist
|
||||
// damit genau der Zustand, der 2026-05 schon einmal einen Split-Brain
|
||||
// ausgelöst hat (beide Knoten Priorität 200, höhere IP gewinnt).
|
||||
//
|
||||
// Verlässlich ist — wie in cluster_repair.go dokumentiert — die
|
||||
// Replikations-Topologie selbst: nur der Primary hat die PUBLICATION, nur
|
||||
// der Standby die SUBSCRIPTION. Beide Kataloge darf der edgeguard-DB-User
|
||||
// lesen. Das ist zugleich selbstheilend: nach `edgeguard-ctl promote` hat
|
||||
// der neue Primary die Publication und meldet sich ab dem nächsten Start
|
||||
// korrekt als "primary" — anders als eine Ableitung aus setup.json, die
|
||||
// den Promote überschreiben würde.
|
||||
func localClusterRole(ctx context.Context, pool *pgxpool.Pool, st *setup.State) string {
|
||||
if pool != nil {
|
||||
var hasPub, hasSub bool
|
||||
if err := pool.QueryRow(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM pg_publication WHERE pubname = 'edgeguard_shared')`,
|
||||
).Scan(&hasPub); err == nil && hasPub {
|
||||
return "primary"
|
||||
}
|
||||
if err := pool.QueryRow(ctx,
|
||||
`SELECT EXISTS(SELECT 1 FROM pg_subscription WHERE subname = 'edgeguard_sub')`,
|
||||
).Scan(&hasSub); err == nil && hasSub {
|
||||
return "peer"
|
||||
}
|
||||
}
|
||||
// Keine Replikation eingerichtet: ein per Join dazugekommener Node ist
|
||||
// trotzdem kein Primary, alles andere (Founder/Single-Node) schon.
|
||||
if st != nil && st.IsClusterNode {
|
||||
return "peer"
|
||||
}
|
||||
return "primary"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user