Spring Boot Actuator: Health Checks und Metrics
1. Übersicht
Ein Service in der Produktion muss zwei Fragen beantworten können: »Läuft er noch?« und »Wie wird er ausgeführt?«. Spring Boot Actuator beantwortet beide über vordefinierte Endpoints, hauptsächlich /actuator/health und /actuator/metrics.
In diesem Artikel werden Sie Actuator aktivieren, einen benutzerdefinierten HealthIndicator schreiben und Liveness/Readiness Probes für Kubernetes konfigurieren. Anschließend werden Sie Business-Metrics mit Micrometer erfassen, diese zu Prometheus exportieren und Tests für beide Teile schreiben.
2. Installation
Sie müssen den Actuator-Starter hinzufügen. Das Beispiel in diesem Artikel fügt auch spring-boot-starter-web hinzu, um die Endpoints über HTTP bereitzustellen:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Starten Sie die Anwendung und testen Sie:
curl http://localhost:8080/actuator
{"_links":{"self":{...},"health":{...},"health-path":{...}}}
Standardmäßig exponiert Spring Boot 3.x nur den health-Endpoint über HTTP. Andere Endpoints existieren zwar, sind aber von außen nicht erreichbar. Dies ist eine bewusste Sicherheitsmaßnahme.
2.1. Weitere Endpoints freigeben
Sie listen die Endpoints, die Sie freigeben möchten, in application.properties auf:
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
Die erste Zeile gibt die drei Endpoints health, info und metrics frei. Die zweite Zeile ermöglicht /actuator/health, Details für jede Komponente zurückzugeben, anstatt nur einen Gesamtstatus.
Hinweis: Vermeiden Sie
management.endpoints.web.exposure.include=*in der Produktion. Endpoints wieenv,heapdumpoderconfigpropskönnen Passwörter, interne Konfigurationen und Speicherdaten offenlegen.
3. Health Checks
3.1. Standard-Endpoint
Mit der obigen Konfiguration gibt /actuator/health ein Ergebnis wie folgt zurück:
{
"status": "UP",
"components": {
"diskSpace": {
"status": "UP",
"details": { "total": 499963174912, "free": 213456789504, "threshold": 10485760 }
},
"ping": { "status": "UP" }
}
}
Spring Boot registriert automatisch HealthIndicator basierend auf dem Classpath. Wenn ein DataSource vorhanden ist, sehen Sie zusätzlich db. Wenn Redis vorhanden ist, dann auch redis, und ähnlich für andere Komponenten. Der Gesamtstatus ist ein kombiniertes Ergebnis: Wenn nur eine Komponente DOWN ist, ist das Gesamtergebnis DOWN, und der HTTP-Status ist 503.
3.2. Einen benutzerdefinierten HealthIndicator schreiben
Angenommen, Ihr Order-Service hängt von einem externen Payment Gateway ab. Zuerst erstellen wir einen einfachen Client:
public interface PaymentGatewayClient {
boolean ping();
}
Anschließend schreiben Sie einen Indicator, um diesen Client zu überprüfen:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final PaymentGatewayClient client;
public PaymentGatewayHealthIndicator(PaymentGatewayClient client) {
this.client = client;
}
@Override
public Health health() {
try {
if (client.ping()) {
return Health.up().withDetail("provider", "VNPay").build();
}
return Health.down().withDetail("reason", "Ping gab false zurück").build();
} catch (Exception e) {
return Health.down(e).build();
}
}
}
Einige Punkte zu beachten:
- Der Bean-Name ist
paymentGatewayHealthIndicator. Spring Boot entfernt das SuffixHealthIndicator, daher ist der Schlüssel im JSONpaymentGateway. withDetail(...)fügt zusätzliche Informationen hinzu, die nur angezeigt werden, wennshow-detailsdas zulässt.Health.down(e)fügt die Fehlermeldung automatisch in den Abschnittdetails.errorein.
Das Ergebnis, wenn das Payment Gateway fehlschlägt:
{
"status": "DOWN",
"components": {
"diskSpace": { "status": "UP", ... },
"paymentGateway": {
"status": "DOWN",
"details": { "error": "java.net.ConnectException: Connection refused" }
},
"ping": { "status": "UP" }
}
}
Hinweis: Die Methode
health()wird jedes Mal ausgeführt, wenn der Endpoint aufgerufen wird. Wenn sie netzwerkabhängig ist und kein Timeout hat, wird ein langsames Dependency/actuator/healthblockieren. Sie sollten immer ein kurzes Timeout (z.B. 1–2 Sekunden) für Clients in Health Checks setzen.
3.3. Liveness und Readiness Probes
Kubernetes unterscheidet zwei Arten von Probes:
- Liveness: Hängt die Anwendung? Wenn sie fehlschlägt, wird der Pod neu gestartet.
- Readiness: Ist die Anwendung bereit, Anfragen zu verarbeiten? Wenn sie fehlschlägt, wird der Pod aus dem Load Balancer entfernt, aber nicht neu gestartet.
Spring Boot aktiviert diese beiden Gruppen automatisch, wenn es erkennt, dass es auf Kubernetes läuft. Bei lokaler Ausführung müssen Sie sie manuell aktivieren:
management.endpoint.health.probes.enabled=true
management.endpoint.health.group.readiness.include=readinessState,paymentGateway
Die erste Zeile erstellt zwei Endpoints /actuator/health/liveness und /actuator/health/readiness. Die zweite Zeile fügt paymentGateway zur Readiness-Gruppe hinzu. Wenn das Payment Gateway ausfällt, empfängt der Pod keinen Traffic mehr, wird aber nicht unnötigerweise neu gestartet.
curl http://localhost:8080/actuator/health/liveness
{"status":"UP"}
Die entsprechende Konfiguration im Kubernetes-Manifest:
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
Hinweis: Packen Sie externe Abhängigkeiten (Datenbank, Payment-API) nicht in die Liveness-Gruppe. Wenn die Datenbank ausfällt, werden alle Pods bei Liveness fehlschlagen und gleichzeitig neu gestartet. Der Neustart behebt die Datenbankprobleme nicht und belastet die Systemressourcen zusätzlich beim Start.
4. Metrics mit Micrometer
Actuator verwendet Micrometer als Abstraktionsschicht für Metrics. Ohne zusätzliche Konfiguration haben Sie bereits Metrics für JVM, CPU, HTTP-Anfragen, Connection Pools und vieles mehr.
curl http://localhost:8080/actuator/metrics
{"names":["application.ready.time","http.server.requests","jvm.gc.pause","jvm.memory.used","process.cpu.usage", ...]}
Um Details eines Metrics anzuzeigen, fügen Sie seinen Namen zur URL hinzu. Sie können nach Tag mit dem Parameter tag filtern:
curl "http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/orders"
4.1. Counter für Bestellzahlen
System-Metrics allein reichen nicht aus; Sie benötigen normalerweise auch Business-Metrics. Das folgende Beispiel zählt die Anzahl der erstellten Bestellungen und klassifiziert sie nach Vertriebskanal:
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final MeterRegistry registry;
public OrderService(MeterRegistry registry) {
this.registry = registry;
}
public Order createOrder(Customer customer, String channel) {
Order order = new Order(customer.id(), channel);
// ... Bestellung speichern
Counter.builder("orders.created")
.description("Anzahl erstellter Bestellungen")
.tag("channel", channel)
.register(registry)
.increment();
return order;
}
}
public record Customer(long id, String name) {}
public record Order(long customerId, String channel) {}
MeterRegistry wird von Spring Boot automatisch konfiguriert und injiziert. Das mehrfache Aufrufen von register(registry) mit demselben Namen und Tag ist sicher, da Micrometer den bereits vorhandenen Counter zurückgibt. Nach dem Erstellen von drei Bestellungen über den web-Kanal:
curl http://localhost:8080/actuator/metrics/orders.created
{
"name": "orders.created",
"description": "Anzahl erstellter Bestellungen",
"measurements": [ { "statistic": "COUNT", "value": 3.0 } ],
"availableTags": [ { "tag": "channel", "values": ["web"] } ]
}
4.2. Timer für Payment-Verarbeitungszeit
Timer zählt nicht nur die Anzahl der Aufrufe, sondern misst auch die Gesamtzeit und die maximale Zeit:
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final Timer paymentTimer;
public PaymentService(MeterRegistry registry) {
this.paymentTimer = Timer.builder("payment.process")
.description("Zeit zur Verarbeitung von Zahlungen")
.register(registry);
}
public boolean pay(Order order) {
return paymentTimer.record(() -> {
// ... Payment Gateway aufrufen
return true;
});
}
}
Der Timer wird einmal im Constructor erstellt und dann wiederverwendet, da es keine Tags gibt, die sich bei jedem Aufruf ändern. record(Supplier) misst die Ausführungszeit des Lambda und gibt sein Ergebnis zurück. Der Endpoint /actuator/metrics/payment.process zeigt drei Statistiken: COUNT, TOTAL_TIME und MAX.
4.3. Gemeinsame Tags für alle Metrics
Wenn mehrere Services Metrics an einem Ort zusammenfassen, sollten Sie den Anwendungsnamen an alle Metrics anhängen:
management.metrics.tags.application=order-service
Hinweis: Verwenden Sie keine Tags mit großen oder unbegrenzten Wertemengen wie
orderId,customerIdoder E-Mail. Jede Tag-Kombination erzeugt eine separate Zeit-Serie. Millionen von Bestellungen würden Millionen von Serien erzeugen und das Speicher der Anwendung und des Überwachungssystems aufblähen. Tags sollten nur kleine, feste Wertemengen enthalten wiechannel,statusoderregion.
5. Export zu Prometheus
Der Endpoint /actuator/metrics eignet sich zum schnellen Durchschauen. Für langfristige Speicherung und Diagramme benötigen Sie ein System wie Prometheus. Fügen Sie die entsprechende Registry hinzu:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Geben Sie dann den prometheus-Endpoint frei:
management.endpoints.web.exposure.include=health,info,metrics,prometheus
curl http://localhost:8080/actuator/prometheus | grep -E "orders|payment"
# HELP orders_created_total Anzahl erstellter Bestellungen
# TYPE orders_created_total counter
orders_created_total{application="order-service",channel="web"} 3.0
payment_process_seconds_count{application="order-service"} 5.0
payment_process_seconds_sum{application="order-service"} 0.412
payment_process_seconds_max{application="order-service"} 0.153
Micrometer benennt automatisch nach den Prometheus-Konventionen um: Punkte werden zu Unterstrichen, Counter haben das Suffix _total, Timer sind in Sekunden. Der Rest ist die Konfiguration von Prometheus zum Scrapen des Pfads /actuator/prometheus.
6. Tests
6.1. Test HealthIndicator
HealthIndicator ist eine normale Klasse, daher können Sie sie testen, ohne den Spring-Kontext zu starten:
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
class PaymentGatewayHealthIndicatorTest {
@Test
void whenPingSucceeds_thenStatusIsUp() {
var indicator = new PaymentGatewayHealthIndicator(() -> true);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsEntry("provider", "VNPay");
}
@Test
void whenPingThrows_thenStatusIsDownWithError() {
var indicator = new PaymentGatewayHealthIndicator(() -> {
throw new IllegalStateException("timeout");
});
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails().get("error").toString()).contains("timeout");
}
}
Da PaymentGatewayClient nur eine Methode hat, können Sie ein Lambda anstelle eines Mock übergeben.
6.2. Test Metrics
Micrometer stellt SimpleMeterRegistry bereit, die Metrics im Speicher speichert und sich hervorragend für Unit Tests eignet:
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class OrderServiceTest {
@Test
void whenOrdersCreated_thenCounterIncrementsPerChannel() {
var registry = new SimpleMeterRegistry();
var service = new OrderService(registry);
var customer = new Customer(1L, "An");
service.createOrder(customer, "web");
service.createOrder(customer, "web");
service.createOrder(customer, "mobile");
assertThat(registry.get("orders.created").tag("channel", "web").counter().count())
.isEqualTo(2.0);
assertThat(registry.get("orders.created").tag("channel", "mobile").counter().count())
.isEqualTo(1.0);
}
}
Wenn eine Metrik nicht vorhanden ist, wirft registry.get(...) eine MeterNotFoundException. Dadurch können Tests auch Tippfehler in Metriknamen abfangen.
7. Sicherheit und häufige Fehler
Separaten Admin-Port. Sie können Actuator auf einem eigenen Port ausführen, der nur im internen Netzwerk zugänglich ist:
management.server.port=8081
Dann wird die Anwendung auf 8080 bedient, während /actuator/** nur auf 8081 verfügbar ist. Sie können diesen Port auf der Firewall oder im Ingress blockieren. In Kubernetes denken Sie daran, den Port in der Probe-Konfiguration auf 8081 zu ändern.
Health-Details begrenzen. Verwenden Sie in der Produktion show-details=when-authorized zusammen mit Spring Security anstelle von always. Anonyme Benutzer sehen dann nur {"status":"UP"}.
Häufige Fehler:
- Aufruf von
/actuator/metricsgibt404: Sie habenmetricsnicht inexposure.includehinzugefügt. - Health Check schlägt zufällig fehl: Ein Indicator ruft das Netzwerk ohne Timeout auf, oder die Antwortzeit ist länger als das Probe-Timeout.
- Pod startet ständig neu, wenn die Datenbank fehleranfällig ist: Die Datenbank ist in der Liveness-Gruppe.
- Speicher wächst mit der Zeit: Eine Metrik verwendet Tags mit unbegrenzten Wertemengen.
- Endpoint
/actuator/prometheusist nicht sichtbar:micrometer-registry-prometheusfehlt oder der Endpoint wurde nicht freigegeben.
8. Fazit
Spring Boot Actuator stellt Health Checks und Metrics praktisch kostenlos bereit. Ihre Aufgabe ist die korrekte Konfiguration: Exponieren Sie nur notwendige Endpoints, schreiben Sie HealthIndicator mit Timeouts für wichtige Abhängigkeiten, und trennen Sie Liveness von Readiness, damit Kubernetes angemessen reagiert.
Bei Metrics hilft Ihnen Micrometer, Counter und Timer für das Geschäft in nur wenigen Zeilen zu erfassen und dann mit einer einzigen Abhängigkeit zu Prometheus zu exportieren. Halten Sie die Anzahl der Tag-Werte klein und schreiben Sie Tests mit SimpleMeterRegistry, um sicherzustellen, dass Metrics nach dem Refactoring nicht stillschweigend kaputt gehen.