Cluster-Bootstrap
Ein Kaltstart stellt jedem Node im selben Moment dieselbe Frage —
„gibt es schon einen Cluster?” — und Discovery beantwortet sie auf
jedem anders. DNS ist noch nicht propagiert; ein Pod ist Ready,
bevor seine IP im Headless Service steht; die Kubernetes-API liefert
eine unvollständige Pod-Liste. Wer auf die erste Antwort hin handelt,
bekommt Nodes, die je einen Cluster aus der Teilmenge bilden, die sie
zufällig gesehen haben.
Gossip repariert das nie. Das Ergebnis sind getrennte Cluster mit demselben Namen — jeder mit eigenem Leader, eigenen Singletons und eigener Shard-Verteilung.
Cluster-Bootstrap ist die Phase, die vor Cluster.join läuft und
dafür sorgt, dass aus einem gleichzeitigen Start höchstens ein Cluster
entsteht.
Die zwei Regeln
Abschnitt betitelt „Die zwei Regeln“1 — Stable Observation. Den Seed-Provider im Takt von
pollIntervalMs pollen und verlangen, dass die zurückgegebene Menge
(plus dieser Node) für stableMarginMs exakt identisch bleibt, bevor
darauf gehandelt wird. Jede Änderung startet die Margin neu. Ein
fehlgeschlagener Lookup ist keine leere Menge — er zählt gar nicht als
Beobachtung, damit ein DNS-Ausfall nie mit „ich bin allein” verwechselt
werden kann.
2 — Verzögerte Selbstwahl. Die stabile Menge nach Adresse sortieren.
Die niedrigste ist der Initial Seed — aber sie bildet nicht sofort
einen Cluster. Jeder Node, auch der Gewinner, joint mit den übrigen
Contact Points als Seeds; nur der Gewinner bekommt zusätzlich eine Frist
(selfElectionGraceMs), nach der er einen Cluster bildet, falls ihn bis
dahin niemand befördert hat.
Einschalten
Abschnitt betitelt „Einschalten“Der Einzeiler — bootstrapCluster führt die Phase für dich aus:
import { bootstrapCluster, ClusterBootstrapOptions } from 'actor-ts';
const bootstrapOptions = ClusterBootstrapOptions.create('my-app') .withHost(process.env.POD_IP!) .withPort(2552) .withDiscovery('kubernetes') .withStableObservation(true);const { cluster, shutdown } = await bootstrapCluster(bootstrapOptions);Statt true ein Objekt übergeben, um die Zeiten zu überschreiben:
const bootstrapOptions = ClusterBootstrapOptions.create('my-app') .withHost(process.env.POD_IP!) .withDiscovery('kubernetes') .withStableObservation({ requiredContactPoints: 3, stableMarginMs: 8_000 });Selbst steuern
Abschnitt betitelt „Selbst steuern“Wer Cluster.join direkt aufruft, führt die Beobachtung aus und gibt
beide Ergebnisse in die Optionen — die Seed-Liste und die
selfElection-Policy:
import { Cluster, ClusterOptions, NodeAddress, StableObservation, StableObservationOptions,} from 'actor-ts';
const selfAddress = new NodeAddress('my-app', process.env.POD_IP!, 2552);
const observationOptions = StableObservationOptions.create() .withSeedProvider(seedProvider) .withSelfAddress(selfAddress) .withRequiredContactPoints(3);const observation = new StableObservation(observationOptions);const targets = await observation.resolveJoinTargets();
const clusterOptions = ClusterOptions.create() .withHost(selfAddress.host) .withPort(selfAddress.port) .withSeeds(targets.seeds.map((address) => address.toString())) .withSelfElection(targets.selfElection);const cluster = await Cluster.join(system, clusterOptions);resolveJoinTargets() liefert die stabile Menge, ob dieser Node
gewonnen hat (isInitialSeed) und den selfElection-Wert zum
Weiterreichen. Diesen Wert selbst herzuleiten ist der eine Fehler, der
das Split-Brain zurückbringt — deshalb macht die Beobachtung es für dich.
Einstellungen
Abschnitt betitelt „Einstellungen“| Einstellung | Default | Bedeutung |
|---|---|---|
stableMarginMs | 5000 | Wie lange die Contact-Point-Menge unverändert bleiben muss. |
pollIntervalMs | 1000 | Wie oft der Seed-Provider gepollt wird. |
maxWaitMs | 60000 | Gesamtbudget; wird es überschritten, wirft die Phase. |
requiredContactPoints | 1 | Wie viele Contact Points eine stabile Beobachtung mindestens enthalten muss. |
selfElectionGraceMs | 10000 | Wie lange der gewählte Node wartet, bevor er einen Cluster bildet. |
Dieselben Schlüssel unter actor-ts.cluster.bootstrap.*, mit der
üblichen Präzedenz — explizite Optionen > HOCON > eingebaute
Defaults:
actor-ts.cluster.bootstrap { stable-margin = 5s poll-interval = 1s max-wait = 60s required-contact-points = 3 self-election-grace = 10s}requiredContactPoints ist der Wert, den es lohnt anzufassen. Die
Margin fängt Discovery ab, die langsam ist; nur eine Mindestanzahl
fängt Discovery ab, die stabil falsch ist — ein Resolver, der
konsequent zwei von drei Pods liefert, wird sich bereitwillig auf die
falsche Menge einpendeln. Der Default 1 existiert, damit
Einzelknoten-Entwicklung ohne Konfiguration läuft; in Produktion auf die
erwartete Replica-Anzahl setzen.
Was die Phase verweigert
Abschnitt betitelt „Was die Phase verweigert“Bootstrap oder einfacher Seed-Join?
Abschnitt betitelt „Bootstrap oder einfacher Seed-Join?“| Situation | Wahl |
|---|---|
| Feste, bekannte Adressen; ein festgelegter erster Node | Einfacher Seed-Join. |
| Lokale Entwicklung, ein Node | Einfacher Seed-Join — die leere Seed-Liste heißt bereits „ich bin der erste”. |
| Nodes starten gleichzeitig mit dynamischen Adressen (K8s Deployment, Autoscaling-Gruppe) | Bootstrap. |
| Jeder Node bekommt dieselbe Seed-Liste | Bootstrap — siehe unten. |
Die letzte Zeile übersieht man leicht. Cluster filtert diesen Node aus
seiner eigenen Seed-Liste, also bleibt — wenn jeder Node jeden Node
listet — keinem Node die leere Liste, die die gewöhnliche
Selbstwahl braucht. Niemand wird up, niemand wird Leader, niemand wird
je befördert: der Cluster steht in joining fest. Die Wahl ist das, was
die Symmetrie bricht.
- Startlatenz. Mindestens
stableMarginMs, bei einem echten Kaltstart zusätzlichselfElectionGraceMs(einmalig, von einem Node). Der Beitritt zu einem bestehenden Cluster ist nicht betroffen — die Frist läuft dort nie ab. - Discovery-Last. Ein
lookup()propollIntervalMspro Node, bis die Menge stabil ist. Der Takt ist bewusst konstant statt mit Backoff: ein wachsendes Intervall würde die Stable Margin an driftenden Punkten abtasten, und die eingesparte Last — höchstens ein paar Dutzend Lookups pro Node — wiegt die schwächere Garantie nicht auf.
Wie geht’s weiter
Abschnitt betitelt „Wie geht’s weiter“- Beitritt und Seeds — der Join-Handshake, den diese Phase füttert.
- Discovery-Überblick — die Seed-Provider, die die Beobachtung pollt.
- Downing-Strategien — Split-Brain-Auflösung, nachdem der Cluster steht.
- Weakly-up — teilweiser Fortschritt, während die Konvergenz noch aussteht.
