Pular para o conteúdo
Português (BR)

Health checks

Este conteúdo não está disponível em sua língua ainda.

The management routes expose two health endpoints:

  • GET /healthliveness. Returns 200 if the process is operational.
  • GET /readyreadiness. Returns 200 if the pod is ready to receive traffic (cluster up + every readiness check passes).

managementRoutes(...) hands you a HealthCheckRegistry (the health field) where you register custom checks for app-specific health:

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);

When any check returns status: false, the corresponding endpoint returns 503 with a JSON body listing every check’s result.

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
};

A check may be sync or async. addLiveness / addReadiness each return an unsubscribe function — call it to remove the check again:

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

Long-running checks block the response, so keep them fast (sub-second, ideally < 100 ms).

ProbeWhat it answersWhat K8s does on failure
Liveness (/health)“Is this process fundamentally broken?”Restart the pod.
Readiness (/ready)“Should this pod receive traffic right now?”Stop routing to this pod (keep it running).

A check is liveness or readiness depending on which method you calladdLiveness or addReadiness. They are separate lists; to run the same check for both, register it with both.

Different semantics drive different checks:

  • Liveness should only fail for unrecoverable issues — actor system crashed, deadlock detected, fundamental invariants broken. Restart is the only fix. Register these with addLiveness.
  • Readiness can fail for transient issues — DB briefly unreachable, cache warming up, cluster rejoining. No restart needed; just don’t route here yet. Register these with addReadiness.

Don’t register DB / downstream checks as liveness — restarting a pod because the external DB blipped is wrong; the blip will pass.

/ready also reports a clusterReady flag, computed from cluster membership — independent of your registered checks. It’s false until the local node reaches the Up state (and always true when the routes were built without a cluster):

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

Returns clusterReady: true (and 200, if your readiness checks also pass) once the node is Up — the canonical “wait for the cluster” gate.

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

All readiness checks run in parallel when /ready is hit. The response lists each check’s result:

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

The aggregate is UP iff clusterReady and every check’s status is true.

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() run the registered checks and return the HealthCheckResult[] the endpoints aggregate — handy for unit-testing checks in isolation. A check that throws is caught and reported as { name: 'unknown', status: false, detail }. The isHealthy(results) helper is the same all-pass predicate the endpoints use.

There is no built-in per-check timeout — a hung check blocks the whole /health (or /ready) response. For a check that can stall, race it against your own 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 };
});

Without a guard, a stuck check eventually trips K8s’s own probe timeout (10 s default) and triggers a restart. Keep checks fast.