Skip to content

Aggregate seed provider

AggregateSeedProvider wraps multiple seed providers and tries them in order, returning the first non-empty result. Useful when:

  • You want DNS first, fall back to static seeds if DNS is unreachable.
  • The same code runs in multiple environments: K8s for prod, static config for local dev.
  • You’re doing disaster recovery — primary discovery mechanism + backup.
import {
Cluster,
AggregateSeedProvider,
KubernetesApiSeedProvider,
DnsSeedProvider,
ConfigSeedProvider,
} from 'actor-ts';
const provider = new AggregateSeedProvider([
new KubernetesApiSeedProvider({ namespace, labelSelector, containerPort: 2552 }),
new DnsSeedProvider({ service: '_actor-ts._tcp.example.com' }),
new ConfigSeedProvider({ seeds: ['fallback-1:2552', 'fallback-2:2552'] }),
]);
const seeds = await provider.lookup();
await Cluster.join(system, { host, port, seeds });
lookup() →
try provider[0].lookup()
├── non-empty result + no error → return that
└── empty / error → try provider[1].lookup()
├── non-empty → return that
└── empty / error → try provider[2]...
└── ... eventually return [] if all fail

The provider stops at the first non-empty result. An empty list from one provider triggers fallback to the next; an error also triggers fallback (with the error logged at debug level).

If every provider fails or returns empty, lookup() resolves to [] — the cluster’s Cluster.join then either self-bootstraps or retries (depending on its own settings).

new AggregateSeedProvider([
new ConfigSeedProvider({ envVar: 'ACTOR_TS_SEEDS' }), // 1st: env var
new KubernetesApiSeedProvider({ // 2nd: K8s API
namespace: process.env.K8S_NAMESPACE ?? '',
labelSelector: 'app=actor-ts',
containerPort: 2552,
}),
]);

Local dev: export ACTOR_TS_SEEDS=localhost:2552 → uses the env var. In K8s, the env var isn’t set; falls through to the K8s API.

new AggregateSeedProvider([
new DnsSeedProvider({ service: '_actor-ts._tcp.example.com' }),
new ConfigSeedProvider({
seeds: ['known-stable-node-1:2552', 'known-stable-node-2:2552'],
}),
]);

Try DNS first; if it returns empty (DNS server hiccup, no records yet during bootstrapping), use known-stable static IPs.

new AggregateSeedProvider([
new KubernetesApiSeedProvider({ namespace: 'primary-region', ... }),
new KubernetesApiSeedProvider({ namespace: 'failover-region', ... }),
]);

In a multi-region deployment, try the local region first; if no pods are running locally, attempt to join the failover region’s cluster. Aggressive — only makes sense if the regions are actually meant to share state.

new AggregateSeedProvider([
new KubernetesApiSeedProvider({ ... }), // raises 403 in non-K8s
new ConfigSeedProvider({ seeds: ['10.0.0.1:2552'] }),
]);

A 403 from K8s API → logged + skipped → falls through to the config provider. This makes aggregate the right tool for “this code path runs everywhere” — the K8s lookup just becomes a noop where K8s isn’t available.

Errors logged at debug level — quiet by default. Configure the system at debug level if you want visibility into which providers tried + failed.

The first provider that returns a non-empty result is used. Test the order explicitly:

OrderEffect
K8s, then DNSK8s wins in K8s deployments; DNS is the fallback.
DNS, then K8sDNS wins everywhere; K8s only fires when DNS is unreachable.

Put the preferred provider first; the fallback last.