Kubernetes API seed provider
Este conteúdo não está disponível em sua língua ainda.
KubernetesApiSeedProvider reads the Endpoints of a named
Service from the K8s API and returns the ready pod IPs as seeds.
Works without DNS, without SRV, without manual seed-list
maintenance — the Service is your contract.
import { Cluster, ClusterOptions, KubernetesApiSeedProvider, KubernetesApiSeedProviderOptions } from 'actor-ts';
const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create() .withNamespace(process.env.K8S_NAMESPACE!) .withServiceName('actor-ts') .withSystemName('my-app') .withPort(2552);const provider = new KubernetesApiSeedProvider( kubernetesApiSeedProviderOptions,);
const seeds = await provider.lookup();const clusterOptions = ClusterOptions.create() .withHost(process.env.POD_IP!) .withPort(2552) .withSeeds(seeds);await Cluster.join( system, clusterOptions,);For every ready pod backing the actor-ts Service in the
namespace, the provider returns <pod-ip>:2552.
Configuration
Section titled “Configuration”type KubernetesApiSeedProviderOptionsType = { namespace: string; serviceName: string; systemName: string; port: number; fetchEndpoints?: () => Promise<string[]>; // override the in-cluster API pinnedAddresses?: readonly string[]; // CIDRs the pod IPs must fall inside log?: (message: string, error?: unknown) => void;};| Field | What |
|---|---|
namespace | K8s namespace to query — typically your app’s namespace. |
serviceName | The Service (or Endpoints) name whose ready pods form the cluster. |
systemName | The actor-system name stamped on each discovered address. |
port | The cluster-transport port on each backing pod. |
fetchEndpoints | Override the Endpoints-fetch function — defaults to the in-cluster API. |
pinnedAddresses | CIDRs the discovered pod IPs must fall inside. Unset means no pinning. |
log | Reports addresses dropped by pinnedAddresses. Default: no-op. |
For pods running in-cluster, only the last three fields are
optional — the framework reads the Endpoints from the standard API
at https://kubernetes.default.svc using the mounted
ServiceAccount token. The other four fields are required.
Pinning the pod CIDR
Section titled “Pinning the pod CIDR”An Endpoints object may name any IP, including one no pod in
the cluster owns. pinnedAddresses bounds what a write to that
object can redirect the bootstrap towards:
const kubernetesApiSeedProviderOptions = KubernetesApiSeedProviderOptions.create() .withNamespace(process.env.K8S_NAMESPACE!) .withServiceName('actor-ts') .withSystemName('my-app') .withPort(2552) .withPinnedAddresses(['10.244.0.0/16']) .withLog((message) => logger.warn(message));Entries here are CIDRs only — Endpoints always resolve to IPs,
so a host suffix could never match and is rejected at
construction, as are an empty list and a malformed CIDR. Addresses
outside the list are filtered out and reported through log, one
message per dropped address.
This provider is already the better-defended of the two: the default fetcher pins TLS to the ServiceAccount CA, so it does not inherit DNS’s trust problem the way the DNS provider does. RBAC that can write Endpoints is still a much cheaper find than a CA key, which is what this pin costs an attacker.
The pod’s ServiceAccount needs read access to the Service’s Endpoints in the namespace:
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: actor-ts-endpoints-reader namespace: my-apprules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: actor-ts-endpoints-reader namespace: my-appsubjects: - kind: ServiceAccount name: actor-tsroleRef: kind: Role name: actor-ts-endpoints-reader apiGroup: rbac.authorization.k8s.ioWithout these, the lookup gets a 403; Cluster.join retries
indefinitely.
What it returns
Section titled “What it returns”From the Service’s Endpoints object:
- Ready pods in
subsets[].addresses[]→ included as<podIP>:<port>. - Not-ready / pending pods (in
notReadyAddresses) → skipped (no ready IP yet). - This pod itself → may be included or not depending on readiness timing; the cluster handles self-as-seed correctly.
Cluster-wide vs replica-set
Section titled “Cluster-wide vs replica-set”Membership is defined by the Service’s own spec.selector — the
provider only needs the Service name:
# One Service fronting every cluster pod:apiVersion: v1kind: Servicemetadata: name: actor-tsspec: clusterIP: None # headless — Endpoints carry the pod IPs selector: app: actor-ts ports: - port: 2552Point the provider at that Service with
.withServiceName('actor-ts'). For most deployments, one
Service per cluster — every pod the Service selects bootstraps
into one cluster.
For role-based asymmetric clusters (workers vs. HTTP gateways), a
single Service whose selector (app=actor-ts) covers both roles
is enough; the role tag inside the cluster is separate from the
Service’s pod selection.
What happens at startup
Section titled “What happens at startup”If you’re the first pod up, the K8s API returns this pod only.
The framework’s self-bootstrap logic handles this:
Cluster.join with seeds containing only itself self-promotes
to leader.
Subsequent pods see the existing pods and join through them.
One Service per cluster
Section titled “One Service per cluster”| Service layout | Effect |
|---|---|
One Service, selector app=actor-ts | One cluster across the namespace. |
Two Services, selectors env=prod / env=staging | Separate prod and staging clusters in one namespace. |
| One Service per named cluster | Explicit cluster name; multiple clusters in one namespace. |
Stable Service design matters — the cluster’s identity is the Service you point at. Changing which pods the Service selects (or the Service name) splits the cluster.
When NOT
Section titled “When NOT”Where to next
Section titled “Where to next”- Discovery overview — the bigger picture.
- Kubernetes deployment — the full K8s recipe.
- Config seed provider — fallback for non-K8s.
- Aggregate seed provider — combine K8s with a fallback.
- Joining and seeds — how seeds are consumed.
