Zum Inhalt springen
Deutsch

Health Checks

Die Management-Routen exponieren zwei Health-Endpunkte:

  • GET /healthLiveness. Liefert 200, wenn der Prozess betriebsbereit ist.
  • GET /readyReadiness. Liefert 200, wenn der Pod bereit ist, Traffic zu empfangen (Cluster up + jeder Readiness-Check besteht).

managementRoutes(...) reicht dir eine HealthCheckRegistry (das Feld health), auf der du eigene Checks für app-spezifische Gesundheit registrierst:

import { managementRoutes } from 'actor-ts';
const { routes, health } = managementRoutes(system, cluster);
health.addReadiness(async () => {
const ok = await db.ping();
return { name: 'database', status: ok, detail: ok ? undefined : 'db unreachable' };
});
health.addReadiness(async () => {
try {
await redis.ping();
return { name: 'cache', status: true };
} catch (e) {
return { name: 'cache', status: false, detail: (e as Error).message };
}
});
await system.http(8558).bind(routes);

Wenn irgendein Check status: false zurückgibt, liefert der entsprechende Endpunkt 503 mit einem JSON-Body, der das Ergebnis jedes Checks auflistet.

type HealthCheckFn = () => Promise<HealthCheckResult> | HealthCheckResult;
type HealthCheckResult = {
name: string; // identifies the check in the response
status: boolean; // true = healthy
detail?: string; // human-readable note, usually on failure
};

Ein Check kann sync oder async sein. addLiveness / addReadiness geben jeweils eine Unsubscribe-Funktion zurück — rufe sie auf, um den Check wieder zu entfernen:

const remove = health.addReadiness(() => ({ name: 'warmup', status: warmedUp }));
// ... once warm-up is permanently done:
remove();

Lang laufende Checks blockieren die Response, also halte sie schnell (sub-sekündlich, idealerweise < 100 ms).

ProbeWas sie beantwortetWas K8s bei Fehler tut
Liveness (/health)„Ist dieser Prozess grundlegend kaputt?”Pod neu starten.
Readiness (/ready)„Soll dieser Pod gerade Traffic bekommen?”Kein Routing mehr hierher (Pod läuft weiter).

Ein Check ist Liveness oder Readiness, je nachdem, welche Methode du aufrufstaddLiveness oder addReadiness. Es sind getrennte Listen; um denselben Check für beide laufen zu lassen, registriere ihn mit beiden.

Unterschiedliche Semantik treibt unterschiedliche Checks:

  • Liveness sollte nur bei nicht behebbaren Problemen fehlschlagen — Actor-System abgestürzt, Deadlock erkannt, fundamentale Invarianten gebrochen. Neustart ist der einzige Fix. Registriere diese mit addLiveness.
  • Readiness darf bei vorübergehenden Problemen fehlschlagen — DB kurz nicht erreichbar, Cache wärmt auf, Cluster tritt neu bei. Kein Neustart nötig; route nur noch nicht hierher. Registriere diese mit addReadiness.

Registriere DB- / Downstream-Checks nicht als Liveness — einen Pod neu zu starten, weil die externe DB kurz zuckte, ist falsch; das Zucken geht vorbei.

/ready meldet zusätzlich ein clusterReady-Flag, berechnet aus der Cluster-Mitgliedschaft — unabhängig von deinen registrierten Checks. Es ist false, bis der lokale Node den Zustand Up erreicht (und immer true, wenn die Routen ohne Cluster gebaut wurden):

GET /ready
→ 503
{ "status": "DOWN", "clusterReady": false, "checks": [] }

Liefert clusterReady: true (und 200, falls auch deine Readiness-Checks bestehen), sobald der Node Up ist — das kanonische „auf den Cluster warten”-Gate.

health.addReadiness(dbCheck);
health.addReadiness(cacheCheck);
health.addReadiness(downstreamApiCheck);

Alle Readiness-Checks laufen parallel, wenn /ready getroffen wird. Die Response listet das Ergebnis jedes Checks:

{
"status": "DOWN",
"clusterReady": true,
"checks": [
{ "name": "database", "status": false, "detail": "connection refused" },
{ "name": "cache", "status": true },
{ "name": "downstream-api", "status": true }
]
}

Das Aggregat ist UP, wenn clusterReady und der status jedes Checks true ist.

import { HealthCheckRegistry } from 'actor-ts';
it('readiness fails when the DB is down', async () => {
const health = new HealthCheckRegistry();
health.addReadiness(async () => ({ name: 'db', status: false, detail: 'mock' }));
const results = await health.checkReadiness();
expect(results).toEqual([{ name: 'db', status: false, detail: 'mock' }]);
});

checkLiveness() / checkReadiness() führen die registrierten Checks aus und geben das HealthCheckResult[] zurück, das die Endpunkte aggregieren — praktisch, um Checks isoliert zu testen. Ein Check, der wirft, wird abgefangen und als { name: 'unknown', status: false, detail } gemeldet. Der isHealthy(results)-Helfer ist dasselbe „alle bestehen”-Prädikat, das die Endpunkte nutzen.

Es gibt kein eingebautes Per-Check-Timeout — ein hängender Check blockiert die gesamte /health- (oder /ready-)Response. Für einen Check, der stocken kann, race ihn gegen deine eigene Deadline:

health.addReadiness(async () => {
const status = await Promise.race([
slowProbe().then(() => true),
new Promise<boolean>((r) => setTimeout(() => r(false), 2_000)),
]);
return { name: 'downstream', status };
});

Ohne Guard trifft ein steckengebliebener Check irgendwann K8s’ eigenes Probe-Timeout (10 s Default) und löst einen Neustart aus. Halte Checks schnell.