HTTP-Endpunkte
Der Management-Server stellt einen kleinen Satz operativer
Endpunkte bereit. Die meisten sind read-only; die Admin-Endpunkte
(/cluster/down, /cluster/leave) sind Opt-in und standardmäßig aus.
Sicherheit (security audit #8): die read-only-Endpunkte legen dennoch
interne Topologie offen — Member-Adressen (host:port), Rollen, den Leader
und die Shard-Map. Behandle den Management-Server als sensibel: binde ihn an
ein internes Interface und schütze ihn mit den Optionen ipAllowlist (und/oder
auth) von managementRoutes(...), statt ihn öffentlich zu exponieren.
Health-Probes
Abschnitt betitelt „Health-Probes“GET /health
Abschnitt betitelt „GET /health“Liveness — ist dieser Prozess fundamental gesund?
GET /health→ 200 OK{ "status": "UP", "checks": [ { "name": "actor-system", "status": true } ]}503 mit { "status": "DOWN", ... }, wenn ein Liveness-Check
fehlschlägt. Siehe
Health Checks.
GET /ready
Abschnitt betitelt „GET /ready“Readiness — sollte dieser Pod Traffic bekommen?
GET /ready→ 200 OK{ "status": "UP", "clusterReady": true, "checks": [ { "name": "cluster-membership", "status": true }, { "name": "cluster-transport", "status": true }, { "name": "database", "status": true } ]}Das checks-Array enthält jeden Readiness-Check auf der Registry
des Systems — die Framework-Checks cluster-membership und
cluster-transport plus alles, was du registriert hast.
clusterReady ist das Ergebnis von cluster-membership selbst, aus
diesem Array zurückgelesen statt separat berechnet: es ist also
false, bis der lokale Node Up erreicht, und true, wenn es gar
keinen Cluster gibt. 503, wenn ein Check fehlschlägt.
Cluster-Info
Abschnitt betitelt „Cluster-Info“Verfügbar, wenn ein cluster an managementRoutes übergeben
wurde.
GET /cluster/members
Abschnitt betitelt „GET /cluster/members“GET /cluster/members→ 200 OK{ "members": [ { "address": "actor-ts://my-app@10.0.0.5:2552", "status": "up", "version": 3, "roles": ["compute"] }, ... ], "self": "actor-ts://my-app@10.0.0.5:2552"}Voller Membership-Snapshot. Nützlich für:
- Manuelle Cluster-Zustand-Checks während Incidents.
- Externe Dashboards, die den Cluster visualisieren.
- Tests, die das Cluster-Joining verifizieren.
GET /cluster/leader
Abschnitt betitelt „GET /cluster/leader“GET /cluster/leader→ 200 OK{ "leader": "actor-ts://my-app@10.0.0.5:2552", "isSelf": true }Die Adresse des Leaders plus isSelf (ist der lokale Node der
Leader). leader ist null, bevor ein Leader gewählt wurde.
Nützlich zum Monitoring von Leader-Wechseln.
GET /cluster/shards?type=<typeName>
Abschnitt betitelt „GET /cluster/shards?type=<typeName>“GET /cluster/shards?type=cart→ 200 OK{ "typeName": "cart", "leader": "my-app@10.0.0.5:2552", "version": 7, "takenAt": 1710000000000, "regions": [ { "key": "my-app@10.0.0.5:2552|/system/cluster/sharding/region-cart", "address": "my-app@10.0.0.5:2552", "path": "/system/cluster/sharding/region-cart", "proxy": false, "shards": [0, 1, 2, ..., 33] }, ... ], "shardHome": [ { "shard": 0, "regionKey": "my-app@10.0.0.5:2552|/system/cluster/sharding/region-cart" }, ... ]}Zeigt die Shard-zu-Region-Allokation für einen sharded Typ. Nutze für:
- Verifizieren gleichmäßiger Verteilung nach einem Rebalance.
- Diagnostizieren heißer Regionen (eine Region mit zu vielen Shards).
- Manuelle Rebalance-Trigger in der Entwicklung.
Woher die Antwort kommt. Der Node antwortet aus
ClusterSharding.shardMap(typeName) — der letzten Karte, die der
Coordinator an die Region dieses Nodes gesendet hat. Dafür ist keine
Konfiguration nötig: keine DistributedData-Extension, kein
coordinatorStateStore, kein Round-Trip zum Leader. Die einzige
Voraussetzung ist, dass der antwortende Node an dem Typ teilnimmt —
also sharding.start(...) oder sharding.startProxy(...) dafür
aufgerufen hat —, denn der Coordinator sendet nur an Regionen, die
sich registriert haben. Ein Node, der cart nie erwähnt hat, hat
nichts zu berichten und antwortet mit 404.
Früher las dieser Endpunkt den DistributedData-Snapshot des
Coordinators, wodurch eine 200 in einer Standardkonfiguration
unerreichbar war: nichts im Framework startet diese Extension, und der
Snapshot wird nur geschrieben, wenn man sich per
coordinatorStateStore dafür entscheidet. Der Store existiert
weiterhin und bleibt optional — er verkürzt den Leader-Failover —,
aber dieser Endpunkt hängt nicht mehr davon ab.
Den Body lesen:
versionzählt Broadcasts des Coordinators, keine Shard-Zuweisungen — eine Erhöhung pro Publish, und ein Publish fasst einen Schwung Verschiebungen zusammen. Vergleiche den Wert über zwei Abfragen, um „die Karte hat sich bewegt“ von „die Karte ist unverändert“ zu unterscheiden.takenAtundleaderstempelt der antwortende Node beim Eintreffen der Karte, zwei Nodes können also dieselbeversionmit leicht verschiedenen Zeitstempeln melden.regions[].keyist<Adresse>|<Region-Pfad>, derselbe Key, denshardHome[].regionKeyverwendet. Eine Region, die nichts hostet, steht trotzdem in der Liste, mit leeremshards— „registriert und ohne Shards“ ist die interessante Hälfte einer Rebalance-Frage.- Eine 200 mit leerem
shardHomeist normal und kein Fehler:regionsfüllt sich, sobald sich Regionen registrieren, während ein Shard erst dann ein Zuhause bekommt, wenn eine Entity darin adressiert wird.
Metriken
Abschnitt betitelt „Metriken“GET /metrics
Abschnitt betitelt „GET /metrics“Opt-in via enableMetricsEndpoint: true.
GET /metrics→ 200 OKContent-Type: text/plain; version=0.0.4; charset=utf-8
# HELP actor_messages_processed_total ...# TYPE actor_messages_processed_total counteractor_messages_processed_total{class="Worker",path="..."} 12345...Prometheus-Textformat. Siehe Prometheus-Exporter.
Antwortet stattdessen mit 503, wenn die installierte
MetricsRegistry sich nicht über collect() zurücklesen lässt —
die prom-client-Bridge
ist die eine im Framework, die das nicht kann, weil sie ihre
Schreibvorgänge an prom-client weiterleitet und keinen Snapshot
behält:
GET /metrics→ 503 Service Unavailable
metrics endpoint unavailable: the installed MetricsRegistry does notsupport collect() — ...Ein 200 mit leerem Body wäre schlimmer als der Fehler: eine leere
Exposition ist ein gültiger Scrape, also verbucht Prometheus das
Target als erreichbar, und jede Serie des Frameworks hört still auf
zu existieren. Die Prüfung läuft pro Request, weil die Registry zu
jedem Zeitpunkt im Leben des Systems ausgetauscht werden kann.
Admin (Opt-in)
Abschnitt betitelt „Admin (Opt-in)“POST /cluster/leave
Abschnitt betitelt „POST /cluster/leave“Opt-in via enableLeaveEndpoint: true.
POST /cluster/leave→ 202 AcceptedleavingDer 202-Body ist das Klartext-Wort leaving (kein JSON) — der
Leave ist Fire-and-forget.
Löst einen Graceful Cluster-Leave aus. Der Node läuft durch
leaving → exiting → removed; Shards rebalancen weg; das
Actor-System terminiert (konfigurierbar).
Nutze für:
- Pod-Außerbetriebnahme vor einem Rolling Update.
- Manuellen Node-Out während Incidents.
Hinter Authentifizierung gaten — jeder mit Portzugriff kann deinen Node drainen.
POST /cluster/down
Abschnitt betitelt „POST /cluster/down“Opt-in via enableDownEndpoint: true.
POST /cluster/down{ "address": "actor-ts://my-app@10.0.0.5:2552" }
→ 202 Accepted{ "downed": "actor-ts://my-app@10.0.0.5:2552" }Gibt 202 mit { downed } zurück, wenn das Mitglied gedownt wurde,
oder 404, wenn die Adresse unbekannt oder bereits terminal ist.
Force-downt ein Remote-Mitglied per Adresse. Destruktiv — die Actor des Targets stoppen, seine Shards reallokieren woanders.
Nutze für:
- Split-Brain-Recovery, wenn keine Downing-Strategie konfiguriert ist.
- Entfernen von hängenden
unreachable-Mitgliedern, die sich weigern, sich zu erholen.
Hochrisiko-Endpunkt — hinter Auth + Audit-Logs gaten.
Eigene Routen
Abschnitt betitelt „Eigene Routen“import { path, get, concat, completeJson } from 'actor-ts/http';import { managementRoutes } from 'actor-ts/management';
const baseRoutes = managementRoutes(system, cluster);
const customRoutes = concat( baseRoutes, path('admin', get(async () => completeJson(200, { appVersion: '1.2.3' })), ),);
await system.http(port, { host }).bind(customRoutes);managementRoutes(system, cluster, options) gibt die Basis-Routen
zurück; kombiniere mit eigenen via concat. Nützlich, um
app-spezifische Admin-Endpunkte neben dem Standard-Satz
hinzuzufügen.
Response-Format
Abschnitt betitelt „Response-Format“Die Read-Endpunkte (/health, /ready, /cluster/members,
/cluster/leader, /cluster/shards) und /cluster/down geben
JSON zurück. /metrics liefert Prometheus-text/plain, und
/cluster/leave gibt den Klartext-Body leaving zurück.
Fehler sind Klartext-Bodies (das complete(status, message) des
Frameworks), kein strukturiertes Objekt — z. B. ergibt ein fehlender
type-Query-Param ein 400, dessen Body missing query param \type“ ist.
Status-Codes folgen HTTP-Konventionen: 200 für Reads, 202 Accepted
für die Admin-Aktionen (/cluster/leave, /cluster/down), 4xx für
Client-Fehler (404, wenn eine /cluster/down-Adresse unbekannt ist)
und 503, wenn Cluster oder ein Health-Check nicht verfügbar ist.
Authentifizierung & IP-Allowlist (#312)
Abschnitt betitelt „Authentifizierung & IP-Allowlist (#312)“Die privilegierten Endpunkte (/cluster/down, /cluster/leave,
/cluster/members, /cluster/shards, /metrics) akzeptieren
standardmäßig anonyme Requests. Das ist nur sicher, wenn der
Management-Port auf einem netzwerkisolierten Bind sitzt
(separater Service, anderer Port, 127.0.0.1-only) oder hinter
einem Reverse-Proxy, der die Auth für dich übernimmt.
Für Produktions-Deployments, die Management-Endpunkte einem
breiteren Netz exponieren, hänge die mitgelieferten Middlewares
über managementRoutes(system, cluster, { auth, ipAllowlist })
an:
import { BearerTokenAuth, IpAllowlist,} from 'actor-ts/http';import { managementRoutes,} from 'actor-ts/management';
const routes = managementRoutes(system, cluster, { enableLeaveEndpoint: true, enableDownEndpoint: true, enableMetricsEndpoint: true, // Shared-Secret-Bearer-Token (Rotation über mehrere Einträge). auth: BearerTokenAuth({ tokens: [process.env.MGMT_TOKEN!], realm: 'my-app-mgmt', }), // Netzwerk-Level-Fence — nur Requests aus diesen CIDRs // erreichen die Anwendung überhaupt. ipAllowlist: IpAllowlist({ allow: ['10.0.0.0/8', '127.0.0.1/32'], // Hinter einem Reverse-Proxy: benenne die Proxys — die // Client-Adresse wird dann vom Socket-Ende der // Forwarded-Kette nach innen aufgelöst. Ohne das liest der // Default den Socket-Peer, und der ist hinter einem Proxy // der Proxy. // trustedProxies: ['10.9.9.0/24'], }),});Policy-Trennung:
authwrappt die privilegierte Teilmenge (/cluster/*,/metrics)./healthund/readybleiben anonym — Kubernetes- Liveness-/Readiness-Probes können nicht so leicht einen Authorization-Header mitschicken. SetzeauthProtectHealth: true, wenn dein Deployment garantiert, dass die Probes Credentials präsentieren können.ipAllowlistwrappt JEDEN Endpunkt inklusive/healthund/ready. Netzwerk-Level-Isolation ist unabhängig davon, wer authentifiziert ist.
Fehlermodi:
- Fehlender / falscher
Authorization-Header →401 UnauthorizedmitWWW-Authenticate: Bearer realm="...". - Client-IP außerhalb der Allowlist (oder unauflösbar) →
403 Forbidden.
Die Middlewares sind general-purpose — du kannst sie auch
außerhalb des Management-Subtrees über den
withMiddleware(mw, route)-Builder einsetzen.
Wo es weitergeht
Abschnitt betitelt „Wo es weitergeht“- Management — Überblick — das größere Bild.
- Health Checks — Custom-Check-Registrierung.
- Cluster — Überblick — die
Membership-Semantik hinter den
/cluster/*-Endpunkten. - Kubernetes-Deployment — das K8s-Rezept, das diese Endpunkte nutzt.
- Prometheus-Exporter — die Details des Metrik-Formats.
