Zum Inhalt springen

Kubernetes-Probes: Liveness, Readiness und Startup

Konfiguriere Liveness-, Readiness- und Startup-Probes korrekt, um Kaskadenausfälle und unnötige Neustarts zu verhindern und Traffic richtig zu lenken.

4 Min. Lesezeit
Diagramm des Kubernetes-Pod-Lebenszyklus, das zeigt, wie Liveness-, Readiness- und Startup-Probes das Traffic-Routing und Neustarts steuern

Drei Probes, drei Zwecke

Kubernetes verwendet drei Arten von Health Checks, um den Lebenszyklus von Pods zu steuern. Werden sie verwechselt, drohen kaskadierende Neustarts, Traffic-Verluste und rätselhafte Ausfälle. Jede Probe beantwortet eine andere Frage: Lebt der Prozess? Kann er Traffic annehmen? Ist der Start abgeschlossen?

Startup-Probes: langsame Initialisierung

Startup-Probes schützen Container mit langsamem Start. Bis die Startup-Probe erfolgreich ist, deaktiviert Kubernetes die Liveness- und Readiness-Prüfungen. Ohne Startup-Probe wird ein Container, der 60 Sekunden zum Initialisieren braucht, von einer Liveness-Probe abgeschossen, die eine Antwort innerhalb von 10 Sekunden erwartet.

ymlyaml
# ❌ No startup probe — slow apps get killed during init
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  template:
    spec:
      containers:
        - name: api
          image: api-server:latest
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10  # Not enough for heavy init
            periodSeconds: 5
            failureThreshold: 3
          # App needs 45 seconds to warm up — gets killed at 25s
ymlyaml
# ✅ Startup probe gives the app time to initialize
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  template:
    spec:
      containers:
        - name: api
          image: api-server:latest
          startupProbe:
            httpGet:
              path: /healthz/started
              port: 8080
            periodSeconds: 5
            failureThreshold: 30     # 5s × 30 = 150s max startup time
          livenessProbe:
            httpGet:
              path: /healthz/live
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /healthz/ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 2
            successThreshold: 1

Liveness-Probes: Hängt der Prozess?

Liveness-Probes erkennen Deadlocks, Endlosschleifen und korrupten Zustand. Schlägt eine Liveness-Probe fehl, startet Kubernetes den Container neu. Setzen Sie Liveness-Probes zurückhaltend ein: Ein falsch positives Ergebnis startet einen gesunden Container neu und kann sich zu einem clusterweiten Ausfall auswachsen.

tstypescript
// Liveness endpoint: check if the process is fundamentally alive
// Do NOT check dependencies here — that's for readiness
app.get("/healthz/live", (req, res) => {
  // Check for deadlock conditions
  const eventLoopLag = measureEventLoopLag();
  const memoryUsage = process.memoryUsage();
 
  if (eventLoopLag > 5000) {
    // Event loop blocked for 5+ seconds — likely deadlocked
    res.status(503).json({
      status: "unhealthy",
      reason: "event_loop_blocked",
      lagMs: eventLoopLag,
    });
    return;
  }
 
  // Process is alive and responding
  res.status(200).json({ status: "ok" });
});
 
function measureEventLoopLag(): number {
  // Simplified — use a library like 'event-loop-lag' in production
  const start = performance.now();
  // If this takes significantly longer than expected,
  // the event loop is congested
  return performance.now() - start;
}
tstypescript
// ❌ Liveness probe that checks database — causes cascading restarts
app.get("/healthz/live", async (req, res) => {
  try {
    await db.query("SELECT 1"); // DB down → pod restart → more load on DB
    res.status(200).send("ok");
  } catch {
    res.status(503).send("unhealthy");
  }
});
 
// ✅ Liveness only checks if the process itself is healthy
app.get("/healthz/live", (req, res) => {
  // No external dependency checks — just process health
  res.status(200).json({ status: "alive", uptime: process.uptime() });
});

Readiness-Probes: Kann er Traffic annehmen?

Readiness-Probes steuern, ob ein Pod Traffic erhält. Schlägt eine Readiness-Probe fehl, wird der Pod aus den Endpoints des Service entfernt: Er bekommt keine Anfragen mehr, läuft aber weiter. Genau hier werden Abhängigkeiten geprüft.

tstypescript
interface DependencyCheck {
  name: string;
  check: () => Promise<boolean>;
  critical: boolean;
}
 
const dependencies: DependencyCheck[] = [
  {
    name: "database",
    critical: true,
    check: async () => {
      try {
        await db.query("SELECT 1");
        return true;
      } catch {
        return false;
      }
    },
  },
  {
    name: "redis",
    critical: true,
    check: async () => {
      try {
        await redis.ping();
        return true;
      } catch {
        return false;
      }
    },
  },
  {
    name: "external-api",
    critical: false, // Degraded but functional without it
    check: async () => {
      try {
        const response = await fetch("https://api.example.com/health", {
          signal: AbortSignal.timeout(2000),
        });
        return response.ok;
      } catch {
        return false;
      }
    },
  },
];
 
app.get("/healthz/ready", async (req, res) => {
  const results = await Promise.all(
    dependencies.map(async (dep) => ({
      name: dep.name,
      healthy: await dep.check(),
      critical: dep.critical,
    }))
  );
 
  const criticalFailures = results.filter(
    (r) => r.critical && !r.healthy
  );
 
  if (criticalFailures.length > 0) {
    res.status(503).json({
      status: "not_ready",
      failures: criticalFailures.map((f) => f.name),
      checks: results,
    });
    return;
  }
 
  res.status(200).json({
    status: "ready",
    checks: results,
  });
});

Geordnetes Herunterfahren mit PreStop-Hooks

Wenn Kubernetes einen Pod beendet, sendet es SIGTERM und entfernt den Pod gleichzeitig aus den Endpoints. Dabei entsteht eine Race Condition: Traffic kann noch eintreffen, nachdem der Pod bereits mit dem Herunterfahren begonnen hat. Ein preStop-Hook fügt eine Verzögerung ein, um laufende Anfragen sauber abzuarbeiten.

ymlyaml
spec:
  containers:
    - name: api
      lifecycle:
        preStop:
          exec:
            command: ["sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 30
tstypescript
// Handle graceful shutdown in Node.js
let isShuttingDown = false;
 
process.on("SIGTERM", async () => {
  console.log("SIGTERM received, starting graceful shutdown");
  isShuttingDown = true;
 
  // Stop accepting new connections
  server.close(async () => {
    // Close database connections
    await db.end();
    await redis.quit();
    console.log("Graceful shutdown complete");
    process.exit(0);
  });
 
  // Force exit after timeout
  setTimeout(() => {
    console.error("Forced shutdown after timeout");
    process.exit(1);
  }, 25_000);
});
 
// Readiness probe returns not ready during shutdown
app.get("/healthz/ready", (req, res) => {
  if (isShuttingDown) {
    res.status(503).json({ status: "shutting_down" });
    return;
  }
  // ... normal readiness checks
});

Häufige Fehler und wie man sie behebt

Die gefährlichste Fehlkonfiguration sind zu aggressive Liveness-Probes, die Pods schon bei vorübergehenden Problemen neu starten und so einen Schneeballeffekt auslösen.

tstypescript
// Probe configuration guidelines as code
interface ProbeConfig {
  path: string;
  periodSeconds: number;
  failureThreshold: number;
  timeoutSeconds: number;
  successThreshold: number;
}
 
const recommendedConfig = {
  startup: {
    path: "/healthz/started",
    periodSeconds: 5,
    failureThreshold: 30,      // Allow 150s startup
    timeoutSeconds: 3,
    successThreshold: 1,
  } satisfies ProbeConfig,
 
  liveness: {
    path: "/healthz/live",
    periodSeconds: 15,          // Not too frequent
    failureThreshold: 3,        // 3 failures = 45s before restart
    timeoutSeconds: 5,
    successThreshold: 1,
  } satisfies ProbeConfig,
 
  readiness: {
    path: "/healthz/ready",
    periodSeconds: 5,           // Quick to remove from rotation
    failureThreshold: 2,        // 2 failures = 10s before traffic stops
    timeoutSeconds: 3,
    successThreshold: 1,        // 1 success = back in rotation
  } satisfies ProbeConfig,
};

Das Wichtigste in Kürze

Die drei Kubernetes-Probes erfüllen unterschiedliche Zwecke und dürfen nicht verwechselt werden. Startup-Probes schützen langsam initialisierende Container vor verfrühten Neustarts. Liveness-Probes erkennen blockierte Prozesse – sie prüfen ausschließlich die Prozessgesundheit, niemals externe Abhängigkeiten. Readiness-Probes steuern den Traffic – hier werden Datenbanken, Caches und nachgelagerte Dienste geprüft.

Prüfen Sie Abhängigkeiten niemals in Liveness-Probes. Eine ausfallende Datenbank sorgt dafür, dass alle Pods neu starten, was die Last auf genau dieser bereits angeschlagenen Datenbank weiter erhöht – eine kaskadierende Fehlerschleife. Konfigurieren Sie die Schwellenwerte der Probes konservativ: Die Liveness-Probe sollte träge auslösen (hoher failureThreshold), die Readiness-Probe sollte schnell reagieren. Implementieren Sie ein geordnetes Herunterfahren mit preStop-Hooks und SIGTERM-Handling, um laufende Anfragen während Deployments sauber abzuarbeiten. Health Checks sind einfach zu implementieren, aber bei falscher Konfiguration verheerend.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX