Implementierung robuster tiefer Gesundheitsprüfungen in Backend-Frameworks für die Container-Orchestrierung
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Einführung
In der sich schnell entwickelnden Landschaft der modernen Softwareentwicklung sind Containerisierung und Microservices-Architektur zum De-facto-Standard für die Erstellung skalierbarer, widerstandsfähiger und wartbarer Anwendungen geworden. Tools wie Kubernetes, Docker Swarm und Amazon ECS vereinfachen die Bereitstellung und Verwaltung, aber ihre Effektivität hängt von einer entscheidenden, aber oft unterschätzten Komponente ab: Gesundheitsprüfungen. Während grundlegende Gesundheitsprüfungen uns sagen können, ob ein Dienst läuft, kann eine Anwendung zwar "hoch" sein, aber aufgrund eines Abhängigkeitsfehlers oder einer Ressourcenerschöpfung immer noch nicht in der Lage sein, ihre Kernfunktionen auszuführen. Hier kommen tiefe Gesundheitsprüfungen ins Spiel. Sie gehen über die reine Prozessüberwachung hinaus und prüfen den internen Zustand und die externen Abhängigkeiten einer Anwendung, um ein genaueres Bild ihrer betrieblichen Bereitschaft zu liefern. Dieser Artikel untersucht die Bedeutung tiefer Gesundheitsprüfungen, wie man sie effektiv in Backend-Frameworks implementiert, und ihre unverzichtbare Rolle bei der robusten Container-Orchestrierung.
Kernkonzepte erklärt
Bevor wir uns mit den Implementierungsdetails befassen, klären wir einige Schlüsselbegriffe, die für das Verständnis tiefer Gesundheitsprüfungen und ihrer Interaktion mit Container-Orchestratoren von zentraler Bedeutung sind:
- Gesundheitsprüfung (Allgemein): Ein Endpunkt, der den betrieblichen Status einer Anwendung oder einer Dienstinstanz meldet. Orchestrierungssysteme verwenden dies, um festzustellen, ob ein Container gesund ist und bereit ist, Verkehr zu bedienen.
- Liveness-Probe (Lebensfähigkeitsprüfung): Wird von Orchestratoren verwendet, um festzustellen, ob ein Container läuft. Wenn eine Liveness-Probe fehlschlägt, startet der Orchestrator den Container typischerweise neu. Dies verhindert Deadlocks und stellt sicher, dass Prozesse reaktionsschnell sind.
- Readiness-Probe (Bereitschaftsprüfung): Wird von Orchestratoren verwendet, um festzustellen, ob ein Container bereit ist, Verkehr zu akzeptieren. Wenn eine Readiness-Probe fehlschlägt, entfernt der Orchestrator den Container vorübergehend aus dem Lastverteilungspool des Dienstes. Dies ist entscheidend während des Starts oder wenn ein Dienst vorübergehend keine Anfragen bearbeiten kann (z. B. Datenbankverbindungen herstellen).
- Startup-Probe: (Kubernetes-spezifisch) Wird verwendet, um anzuzeigen, ob eine Anwendung innerhalb eines Containers gestartet wurde. Wenn konfiguriert, deaktiviert sie Liveness- und Readiness-Prüfungen, bis die Startup-Probe erfolgreich bestanden wurde, und verhindert so vorzeitige Neustarts oder Entfernungen aus dem Dienst während einer potenziell langen Initialisierungsphase.
- Tiefe Gesundheitsprüfung: Eine fortschrittliche Form der Gesundheitsprüfung, die nicht nur die grundlegende Funktionalität der Anwendung überprüft, sondern auch die Gesundheit ihrer kritischen internen Komponenten und externen Abhängigkeiten (z. B. Datenbanken, Nachrichtenwarteschlangen, externe APIs, Caches).
- Container-Orchestrierungssystem: Softwareplattformen (z. B. Kubernetes, Docker Swarm), die die Bereitstellung, Skalierung, Verwaltung und Vernetzung von Containern automatisieren. Sie sind stark auf Gesundheitsprüfungen angewiesen, um gewünschte Anwendungszustände aufrechtzuerhalten.
Implementierung tiefer Gesundheitsprüfungen in Backend-Frameworks
Tiefe Gesundheitsprüfungen befähigen Container-Orchestratoren, intelligente Entscheidungen über die Weiterleitung von Verkehr und den Neustart von Diensten zu treffen, was letztendlich die Anwendungsresilienz erhöht. Wir werden untersuchen, wie man diese mit gängigen Backend-Frameworks wie Spring Boot (Java) und Express.js (Node.js) als Beispiele implementiert.
Die Kernidee ist die Erstellung eines dedizierten HTTP-Endpunkts (z. B. /health/deep
oder /actuator/health
in Spring Boot), der bei Aufruf eine Reihe von Prüfungen gegen kritische interne Komponenten und externe Abhängigkeiten durchführt.
Spring Boot Beispiel (Java)
Spring Boot Actuator bietet hervorragende Unterstützung für Gesundheitsprüfungen. Es enthält eine erweiterbare HealthIndicator
-Schnittstelle, mit der Sie benutzerdefinierte Gesundheitsprüfungen definieren können.
Stellen Sie zuerst sicher, dass Sie die Spring Boot Actuator-Abhängigkeit in Ihrer pom.xml
haben:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Standardmäßig bietet Actuator Gesundheitsprüfungen für gängige Komponenten wie Datenbanken, Redis usw., falls entsprechende Abhängigkeiten vorhanden sind. Um eine tiefe Gesundheitsprüfung für eine externe API zu implementieren, würden Sie beispielsweise einen benutzerdefinierten HealthIndicator
erstellen:
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class ExternalApiServiceHealthIndicator implements HealthIndicator { private final RestTemplate restTemplate; private final String externalApiUrl; public ExternalApiServiceHealthIndicator(RestTemplate restTemplate) { this.restTemplate = restTemplate; // In einer realen Anwendung würden Sie diese aus der Konfiguration injizieren, anstatt sie fest zu codieren this.externalApiUrl = "http://external-api.example.com/status"; } @Override public Health health() { try { // Versuchen Sie, einen Aufruf an den eigenen Gesundheitsendpunkt oder einen leichten Endpunkt der externen API zu machen String response = restTemplate.getForObject(externalApiUrl, String.class); if (response != null && response.contains("UP")) { // Oder JSON-Antwort parsen return Health.up().withDetail("externalApiUrl", externalApiUrl).build(); } else { return Health.down().withDetail("externalApiUrl", externalApiUrl) .withDetail("message", "External API reported unhealthy").build(); } } catch (Exception e) { return Health.down(e) .withDetail("externalApiUrl", externalApiUrl) .withDetail("message", "Failed to reach external API").build(); } } }
Nun, wenn Sie den /actuator/health
-Endpunkt aufrufen, aggregiert Spring Boot Actuator alle HealthIndicator
s, einschließlich Ihres benutzerdefinierten, und gibt einen umfassenden Status zurück. Der Orchestrator kann diesen Endpunkt dann abfragen.
Für Kubernetes könnte Ihre Deployment-YAML wie folgt aussehen:
apiVersion: apps/v1 kind: Deployment metadata: name: my-backend-service spec: replicas: 3 selector: matchLabels: app: my-backend-service template: metadata: labels: app: my-backend-service spec: containers: - name: my-backend-service image: myrepo/my-backend-service:1.0.0 ports: - containerPort: 8080 livenessProbe: httpGet: path: /actuator/health/liveness # Spring Boot Actuator spezifisch port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness # Spring Boot Actuator spezifisch port: 8080 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2 startupProbe: # Wenn Ihre Anwendung lange zum Starten braucht httpGet: path: /actuator/health/startup port: 8080 initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 10 # Dies bedeutet, dass es 50 Sekunden lang versucht (10*5)
Hinweis: Spring Boot Actuator 2.x bietet die Endpunkte /actuator/health/liveness
und /actuator/health/readiness
, die für Kubernetes Liveness- und Readiness-Proben optimiert sind und die Trennung zwischen "System läuft" und "System ist bereit, Verkehr zu bedienen" gewährleisten. Der Endpunkt /actuator/health
aggregiert alle Gesundheitsprüfungen und zeigt vollständige Details an.
Express.js Beispiel (Node.js)
Für Node.js mit Express.js würden Sie typischerweise eine dedizierte Route für Ihre tiefe Gesundheitsprüfung erstellen. Sie könnten eine Bibliothek wie express-healthcheck
verwenden oder es manuell implementieren.
const express = require('express'); const axios = require('axios'); // Für HTTP-Anfragen const app = express(); const port = 3000; // Simulate a database connection check const checkDatabaseConnection = async () => { try { // In einer echten App würde dies das Versuchen eines Clients, eine Verbindung herzustellen/eine Abfrage auszuführen, beinhalten const dbStatus = await new Promise(resolve => setTimeout(() => resolve(Math.random() > 0.1), 100)); // 90% Erfolgschance if (dbStatus) { return { status: 'UP', message: 'Database connected successfully' }; } else { return { status: 'DOWN', message: 'Database connection failed' }; } } catch (error) { return { status: 'DOWN', message: `Database check error: ${error.message}` }; } }; // Simulate an external API check const checkExternalApi = async () => { const externalApiUrl = 'http://jsonplaceholder.typicode.com/posts/1'; // Eine öffentliche Test-API try { const response = await axios.get(externalApiUrl, { timeout: 2000 }); // Timeout setzen if (response.status === 200) { return { status: 'UP', message: 'External API responsive' }; } else { return { status: 'DOWN', message: `External API returned status: ${response.status}` }; } } catch (error) { return { status: 'DOWN', message: `External API check error: ${error.message}` }; } }; app.get('/health', async (req, res) => { const dbHealth = await checkDatabaseConnection(); const externalApiHealth = await checkExternalApi(); const overallStatus = (dbHealth.status === 'UP' && externalApiHealth.status === 'UP') ? 'UP' : 'DOWN'; res.status(overallStatus === 'UP' ? 200 : 503).json({ status: overallStatus, details: { database: dbHealth, externalApi: externalApiHealth } }); }); app.listen(port, () => { console.log(`Express deep health check listening on port ${port}`); });
Für Kubernetes würde Ihre Deployment-YAML dann auf /health
verweisen:
apiVersion: apps/v1 kind: Deployment metadata: name: my-nodejs-service spec: replicas: 3 selector: matchLabels: app: my-nodejs-service template: metadata: labels: app: my-nodejs-service spec: containers: - name: my-nodejs-service image: myrepo/my-nodejs-service:1.0.0 ports: - containerPort: 3000 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2
Wichtige Überlegungen für tiefe Gesundheitsprüfungen
- Leistungsbeeinträchtigung: Tiefe Gesundheitsprüfungen sollten leichtgewichtig sein und schnell ausgeführt werden, um die Anwendungsleistung nicht zu beeinträchtigen und eine schnelle Fehlererkennung sicherzustellen. Vermeiden Sie schwere Berechnungen oder lang andauernde Abfragen innerhalb der Gesundheitsprüfung.
- Timeouts: Konfigurieren Sie geeignete Timeouts für externe Abhängigkeitsprüfungen. Eine langsame Abhängigkeit sollte die Prüfung fehlschlagen lassen, anstatt unendlich lange zu hängen. Dies ist entscheidend für die Kubernetes-Proben
timeoutSeconds
. - Granularität: Entscheiden Sie, welche Abhängigkeiten kritisch genug sind, um in eine tiefe Gesundheitsprüfung aufgenommen zu werden. Nicht jede einzelne Mikroabhängigkeit muss geprüft werden, konzentrieren Sie sich auf diejenigen, die den Dienst funktionsunfähig machen würden.
- Unterscheidung zwischen "Liveness" und "Readiness": Obwohl eine tiefe Gesundheitsprüfung für beides verwendet werden kann, sollten Sie überlegen, ob unterschiedliche Tiefen angemessen sind. Eine Liveness-Probe kann etwas weniger streng sein als eine Readiness-Probe, insbesondere wenn ein Dienst sich von vorübergehenden Abhängigkeitsproblemen erholen kann. Die Trennung von
/liveness
und/readiness
in Spring Boot Actuator 2.x ist ein gutes Beispiel dafür. - Sicherheit: Diese Endpunkte legen oft interne Zustände offen. Sichern Sie sie entsprechend ab, vielleicht indem Sie den Zugriff nur aus internen Netzwerksegmenten zulassen oder eine Authentifizierung erfordern, wenn sie für die Überwachung extern verfügbar gemacht werden.
- Fault Injection Testing (Fehlereinspritzungstests): Testen Sie Ihre tiefen Gesundheitsprüfungen regelmäßig, indem Sie künstlich Abhängigkeiten fehlschlagen lassen, um sicherzustellen, dass sie sich wie erwartet verhalten und dass Ihr Orchestrator korrigierende Maßnahmen ergreift.
Fazit
Tiefe Gesundheitsprüfungen sind keine optionale Funktion; sie sind ein grundlegender Baustein für den Aufbau widerstandsfähiger und zuverlässiger Microservices-Architekturen. Indem Sie den internen Zustand Ihrer Anwendung und ihre externen Abhängigkeiten gründlich prüfen, statten Sie Ihr Container-Orchestrierungssystem mit der Intelligenz aus, die es benötigt, um fundierte Entscheidungen zu treffen, was eine hohe Verfügbarkeit und ein robustes Systemverhalten gewährleistet. Die Implementierung dieser Endpunkte, wie gezeigt, ist ein unkomplizierter, aber wirkungsvoller Schritt in Richtung operativer Exzellenz in einer containerisierten Umgebung.