Zum Inhalt springen
Deutsch

Kubernetes-API Seed Provider

KubernetesApiSeedProvider fragt die K8s-API nach Pods, die einem Label Selector entsprechen, und gibt deren IPs als Seeds zurück. Funktioniert ohne DNS, ohne SRV, ohne manuelle Seed-Listen-Pflege — der Selector ist dein Vertrag.

import { Cluster, KubernetesApiSeedProvider } from 'actor-ts';
const provider = new KubernetesApiSeedProvider({
namespace: process.env.K8S_NAMESPACE!,
labelSelector: 'app=actor-ts',
containerPort: 2552,
});
const seeds = await provider.lookup();
await Cluster.join(system, {
host: process.env.POD_IP!,
port: 2552,
seeds,
});

Für jeden Pod, der app=actor-ts im Namespace matcht, gibt der Provider <pod-ip>:2552 zurück.

interface KubernetesApiSeedProviderSettings {
namespace: string;
labelSelector: string;
containerPort: number;
apiBaseUrl?: string; // In-Cluster-Default überschreiben
serviceAccountToken?: string; // In-Cluster-Default überschreiben
}
FeldWas
namespaceK8s-Namespace, der abgefragt wird — typischerweise dein App-Namespace.
labelSelectorDer Selector für matching Pods (app=actor-ts, role=compute,env=prod etc.).
containerPortDer Cluster-Transport-Port auf jedem matching Pod.
apiBaseUrlDefault https://kubernetes.default.svc (in-cluster).
serviceAccountTokenDefault das In-Cluster-Token unter /var/run/secrets/.../token.

Für Pods, die in-cluster laufen, sind nur die ersten drei Pflicht. Das Framework liest API-URL und SA-Token von den Standard-Locations.

Das ServiceAccount des Pods braucht Read-Zugriff auf Pods im Namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: actor-ts-pod-reader
namespace: my-app
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: actor-ts-pod-reader
namespace: my-app
subjects:
- kind: ServiceAccount
name: actor-ts
roleBinding:
kind: Role
name: actor-ts-pod-reader
apiGroup: rbac.authorization.k8s.io

Ohne diese bekommt das Lookup einen 403; Cluster.join retried endlos.

Für jeden Pod, der dem Selector entspricht:

  • Running Pods mit nicht-leerem status.podIP → eingeschlossen als <podIP>:<containerPort>.
  • Pending / nicht laufende Pods → übersprungen (noch keine IP).
  • Dieser Pod selbst → kann inkludiert sein oder nicht, je nach Start-Race; der Cluster behandelt Self-as-Seed korrekt.
# Alle Pods in der App matchen:
labelSelector: 'app=actor-ts'
# Nur eine bestimmte Rolle matchen:
labelSelector: 'app=actor-ts,role=compute'
# Über mehrere Deployments hinweg matchen:
labelSelector: 'cluster=my-app'

Der Selector definiert die Cluster-Membership beim Bootstrap. Für die meisten Deployments ein Selector pro Cluster — alle Pods, die ihn tragen, bootstrappen in einen Cluster.

Für rollenbasierte asymmetrische Cluster (role=compute für Worker, role=gateway für HTTP) deckt ein einzelner Selector (app=actor-ts) beide ab; das Rollen-Tag innerhalb des Clusters ist getrennt vom Discovery-Selector.

ja

nein, erster Pod

Pod startet → Cluster.join aufgerufen

KubernetesApiSeedProvider.lookup

GET /api/v1/namespaces/<ns>/pods

?labelSelector=<sel>

nach Running filtern

+ nicht-leere podIP

[...podIp:port] zurückgeben

Cluster.join probiert jeden Seed

existierender Cluster?

erfolgreich joinen

selbst bootstrappen

Wenn du der erste Pod hochkommst, gibt die K8s-API nur diesen Pod zurück. Die Self-Bootstrap-Logik des Frameworks behandelt das: Cluster.join mit Seeds, die nur sich selbst enthalten, befördert sich selbst zum Leader.

Nachfolgende Pods sehen die existierenden Pods und joinen über sie.

Selector-MusterEffekt
app=actor-tsEin Cluster über den Namespace.
app=actor-ts,env=prodSeparate Prod- und Staging-Cluster in einem Namespace.
cluster=my-app-cluster-1Expliziter Cluster-Name; mehrere Cluster in einem Namespace.

Stabiles Selector-Design ist wichtig — die Identität des Clusters liegt implizit im Selector. Den Selector zu ändern splittet den Cluster.