Zum Inhalt springen
Deutsch

Aggregate Seed Provider

AggregateSeedProvider umwickelt mehrere Seed Provider und probiert sie der Reihe nach, gibt das erste nicht-leere Ergebnis zurück. Nützlich, wenn:

  • Du erst DNS willst, mit Fallback auf statische Seeds, wenn DNS nicht erreichbar ist.
  • Derselbe Code in mehreren Umgebungen läuft: K8s für Prod, statische Config für lokales Dev.
  • Du Disaster Recovery machst — primärer Discovery-Mechanismus
    • Backup.
import {
Cluster,
ClusterOptions,
AggregateSeedProvider,
KubernetesApiSeedProvider,
KubernetesApiSeedProviderOptions,
DnsSeedProvider,
DnsSeedProviderOptions,
ConfigSeedProvider,
ConfigSeedProviderOptions,
} from 'actor-ts';
const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create()
.withNamespace(namespace)
.withServiceName(labelSelector)
.withSystemName('my-app')
.withPort(2552);
const dnsSeedProviderOptions = DnsSeedProviderOptions.create()
.withHostname('_actor-ts._tcp.example.com')
.withSystemName('my-app')
.withUseSrv();
const configSeedProviderOptions = ConfigSeedProviderOptions.create()
.withSeeds(['fallback-1:2552', 'fallback-2:2552'])
.withSystemName('my-app');
const provider = new AggregateSeedProvider([
new KubernetesApiSeedProvider(
kubernetesApiSeedProviderOptions,
),
new DnsSeedProvider(
dnsSeedProviderOptions,
),
new ConfigSeedProvider(
configSeedProviderOptions,
),
]);
const seeds = await provider.lookup();
const clusterOptions = ClusterOptions.create()
.withHost(host)
.withPort(port)
.withSeeds(seeds);
await Cluster.join(system, clusterOptions);

nicht leer + kein Fehler

leer / Fehler

nicht leer

leer / Fehler

lookup

provider[0].lookup versuchen

Ergebnis?

provider[1].lookup versuchen

Ergebnis?

provider[2]... versuchen

...irgendwann [] zurückgeben

wenn alle fehlschlagen

Ergebnis zurückgeben

Ergebnis zurückgeben

Der Provider stoppt beim ersten nicht-leeren Ergebnis. Eine leere Liste von einem Provider triggert den Fallback zum nächsten; ein Fehler triggert ebenfalls einen Fallback (der Fehler wird an einen optionalen Log-Callback übergeben — standardmäßig ein No-op).

Wenn jeder Provider fehlschlägt oder leer zurückgibt, resolved lookup() zu []Cluster.join des Clusters bootstrapt sich dann entweder selbst oder retried (abhängig von seinen eigenen Settings).

const k8sSeedProviderOptions = KubernetesApiSeedProviderOptions.create()
.withNamespace(process.env.K8S_NAMESPACE ?? '')
.withServiceName('actor-ts')
.withSystemName('my-app')
.withPort(2552);
new AggregateSeedProvider([
seedsFromEnv('ACTOR_TS_SEEDS', 'my-app'), // 1.: Env-Var
new KubernetesApiSeedProvider(k8sSeedProviderOptions), // 2.: K8s-API
]);

Lokales Dev: export ACTOR_TS_SEEDS=localhost:2552 → nutzt die Env-Var. In K8s ist die Env-Var nicht gesetzt; fällt durch zur K8s-API.

const dnsSeedProviderOptions = DnsSeedProviderOptions.create()
.withHostname('_actor-ts._tcp.example.com')
.withSystemName('my-app')
.withUseSrv();
const configSeedProviderOptions = ConfigSeedProviderOptions.create()
.withSeeds(['known-stable-node-1:2552', 'known-stable-node-2:2552'])
.withSystemName('my-app');
new AggregateSeedProvider([
new DnsSeedProvider(
dnsSeedProviderOptions,
),
new ConfigSeedProvider(
configSeedProviderOptions,
),
]);

Erst DNS probieren; wenn leer zurückkommt (DNS-Server-Schluckauf, beim Bootstrap noch keine Records), bekannte stabile statische IPs nutzen.

const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create()
.withNamespace('primary-region')
.withServiceName('actor-ts')
.withSystemName('my-app')
.withPort(2552);
const kubernetesApiSeedProvider2Options = KubernetesApiSeedProviderOptions.create()
.withNamespace('failover-region')
.withServiceName('actor-ts')
.withSystemName('my-app')
.withPort(2552);
new AggregateSeedProvider([
new KubernetesApiSeedProvider(
kubernetesApiSeedProviderOptions,
),
new KubernetesApiSeedProvider(
kubernetesApiSeedProvider2Options,
),
]);

In einem Multi-Region-Deployment erst die lokale Region probieren; wenn lokal keine Pods laufen, versuchen, dem Failover-Region-Cluster beizutreten. Aggressiv — macht nur Sinn, wenn die Regionen tatsächlich State teilen sollen.

const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create()
.withNamespace('my-app')
.withServiceName('actor-ts')
.withSystemName('my-app')
.withPort(2552);
const configSeedProviderOptions = ConfigSeedProviderOptions.create()
.withSeeds(['10.0.0.1:2552'])
.withSystemName('my-app');
new AggregateSeedProvider([
new KubernetesApiSeedProvider(
kubernetesApiSeedProviderOptions,
), // wirft 403 außerhalb K8s
new ConfigSeedProvider(
configSeedProviderOptions,
),
]);

Ein 403 von der K8s-API → übersprungen → fällt durch zum Config Provider. Das macht Aggregate zum richtigen Werkzeug für “dieser Code-Pfad läuft überall” — das K8s-Lookup wird einfach zum No-op, wo K8s nicht verfügbar ist.

AggregateSeedProvider nimmt ein optionales zweites Konstruktor-Argument — einen Log-Callback (message: string, err?: unknown) => void. Er ist standardmäßig ein No-op, sodass Provider-Fehler ohne übergebenen Callback stillschweigend verschluckt werden. Übergib eine Log-Funktion, um Sichtbarkeit zu bekommen, welche Provider es probiert / nicht geschafft haben:

new AggregateSeedProvider(
[
new KubernetesApiSeedProvider(kubernetesApiSeedProviderOptions),
new ConfigSeedProvider(configSeedProviderOptions),
],
(message, error) => console.warn(`[seed] ${message}`, error),
);

Der erste Provider, der ein nicht-leeres Ergebnis zurückgibt, wird genutzt. Teste die Reihenfolge explizit:

ReihenfolgeEffekt
K8s, dann DNSK8s gewinnt in K8s-Deployments; DNS ist der Fallback.
DNS, dann K8sDNS gewinnt überall; K8s feuert nur, wenn DNS nicht erreichbar ist.

Den bevorzugten Provider zuerst; den Fallback zuletzt.