Tes memastikan aplikasi berfungsi saat dibangun; pertanyaan berbeda muncul setelah aplikasi berjalan berjam-jam: apakah basis data masih terhubung, apakah disk menipis, apakah instance siap menerima trafik? Spring Boot Actuator menjawabnya dengan menyuntikkan endpoint operasional ke dalam aplikasi — /actuator/health adalah yang paling terkenal, dibaca oleh load balancer dan orkestrator sebelum mengirim trafik. Pada Chapter ini kita bedah endpoint yang tersedia, cara mengeksposnya secara selektif, mekanisme HealthIndicator dengan agregasi worst-of, serta pasangan liveness dan readiness — lalu membuktikan seluruh alurnya dengan panel kesehatan Java murni.
Endpoint Bawaan Actuator #
Actuator diaktifkan lewat satu starter — sisanya otomatis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>Endpoint yang paling sering dipakai memetakan langsung ke kebutuhan operasional:
| Endpoint | Isi |
|---|---|
/actuator/health | status kesehatan agregat + komponen |
/actuator/info | info build, git, versi aplikasi |
/actuator/metrics | daftar metrik (heap, thread, HTTP) |
/actuator/env | sumber konfigurasi aktif (rahasia disaring) |
/actuator/beans | seluruh bean di konteks |
/actuator/threaddump | jejak seluruh thread JVM |
Aturan keamanannya tegas: hanya /health yang terbuka secara bawaan. Endpoint lain mengandung informasi internal (jalur kelas, variabel lingkungan) sehingga eksposurnya diatur eksplisit per lingkungan:
management:
endpoints:
web:
exposure:
include: health,info,metrics # jangan pernah "*" di produksi
endpoint:
health:
show-details: when-authorized # detail hanya untuk peran berwenangBila Spring Security aktif, endpoint sensitif otomatis memerlukan autentikasi — kombinasikan dengan requestMatchers("/actuator/**").hasRole("ADMIN") dari Chapter Authentication dan Authorization, atau letakkan Actuator di port manajemen terpisah (management.server.port: 9090) yang hanya dijangkau jaringan internal.
HealthIndicator dan Agregasi Worst-of #
Nilai /actuator/health berasal dari kumpulan HealthIndicator — Actuator menyediakan indikator bawaan untuk database, disk, dan mail; kita menambah indikator bisnis sendiri dengan mendaftarkan bean HealthIndicator:
@Component
public class GatewayPembayaranHealthIndicator implements HealthIndicator {
private final GatewayPembayaranKlien klien;
public GatewayPembayaranHealthIndicator(GatewayPembayaranKlien klien) {
this.klien = klien;
}
@Override
public Health health() {
if (!klien.ping()) {
return Health.down()
.withDetail("galat", "gateway tidak merespons")
.build();
}
return Health.up().withDetail("mode", klien.mode()).build();
}
}Agregasi mengikuti aturan worst-of: status paling parah dari seluruh indikator menjadi status keseluruhan, dengan urutan keparahan DOWN > OUT_OF_SERVICE > UP > UNKNOWN — persis urutan bawaan SimpleStatusAggregator (status UP lebih menentukan daripada UNKNOWN). Satu indikator DOWN menjatuhkan seluruh respons menjadi {"status":"DOWN"} — klien load balancer cukup memeriksa satu kata untuk memutuskan menjauh.
Liveness dan Readiness #
Menjalankan aplikasi di Kubernetes mengubah health check menjadi dua pertanyaan berbeda, dan Actuator menjawabnya dengan health group:
management:
endpoint:
health:
probes:
enabled: true
group:
readiness:
include: readinessState,db,mail
liveness:
include: livenessState| Grup | Pertanyaan | Reaksi gagal | Anggota tepat |
|---|---|---|---|
liveness | apakah proses hidup? | pod direstart | internal state saja |
readiness | siap menerima trafik? | trafik dihentikan, pod hidup | + dependensi eksternal |
Pemisahan ini krusial: gateway pembayaran yang mogok tidak berarti proses aplikasi mati. Bila gateway ikut kelompok liveness, orkestrator me-restart seluruh pod — menghapus sesi pengguna dan heap hangat — padahal yang tepat hanya menahan trafik sejenak hingga gateway pulih. Aturan praktisnya: liveness memuat pemeriksaan internal (state proses), readiness boleh memuat dependensi eksternal.
Panel Kesehatan Mini #
Mekanisme agregasi dan health group layak dilihat dari dalam. Program Java murni berikut membangun empat indikator — db, diskSpace, mail (DOWN), dan ping — lalu mengagregasi worst-of dan mengevaluasi dua group ala probe orkestrator:
import java.util.ArrayList;
import java.util.List;
public class PanelKesehatan {
// Status ala Spring Boot Actuator.
enum Status {
UP, DOWN, OUT_OF_SERVICE, UNKNOWN
}
// Urutan keparahan persis SimpleStatusAggregator bawaan:
// DOWN, OUT_OF_SERVICE, UP, UNKNOWN — makin kecil makin parah.
static int keparahan(Status s) {
return switch (s) {
case DOWN -> 0;
case OUT_OF_SERVICE -> 1;
case UP -> 2;
case UNKNOWN -> 3;
};
}
record Laporan(Status status, String detail) { }
// Padanan HealthIndicator: satu komponen, satu keputusan.
interface Indikator {
String nama();
Laporan periksa();
}
static class IndikatorDb implements Indikator {
@Override
public String nama() {
return "db";
}
@Override
public Laporan periksa() {
// Uji nyata: kumpulan koneksi + SELECT 1.
return new Laporan(Status.UP, "kumpulan=aktif seleksi1=ok");
}
}
static class IndikatorDisk implements Indikator {
@Override
public String nama() {
return "diskSpace";
}
@Override
public Laporan periksa() {
long sisaGb = 42; // nilai tetap agar contoh deterministik
return sisaGb >= 10
? new Laporan(Status.UP, "sisa=" + sisaGb + "GB ambang=10GB")
: new Laporan(Status.DOWN, "sisa=" + sisaGb + "GB");
}
}
static class IndikatorMail implements Indikator {
@Override
public String nama() {
return "mail";
}
@Override
public Laporan periksa() {
// SMTP sedang tidak merespons.
return new Laporan(Status.DOWN, "galat=smtp tidak merespons");
}
}
// Liveness: apakah proses masih hidup? Tidak memeriksa dependensi eksternal.
static class IndikatorPing implements Indikator {
@Override
public String nama() {
return "ping";
}
@Override
public Laporan periksa() {
return new Laporan(Status.UP, "proses=hidup");
}
}
// Padanan health group: sekumpulan indikator dengan nama.
record Grup(String nama, List<Indikator> anggota) {
Status status() {
Status parah = Status.UP;
for (Indikator i : anggota) {
Laporan l = i.periksa();
if (keparahan(l.status()) < keparahan(parah)) {
parah = l.status();
}
}
return parah;
}
}
public static void main(String[] args) {
List<Indikator> semua = List.of(
new IndikatorDb(),
new IndikatorDisk(),
new IndikatorMail());
System.out.println("== Indikator kesehatan ==");
for (Indikator i : semua) {
Laporan l = i.periksa();
System.out.printf("%-10s %-6s %s%n", i.nama(), l.status(), l.detail());
}
// Agregasi worst-of: satu komponen DOWN menjatuhkan seluruh status.
Status keseluruhan = new Grup("keseluruhan", semua).status();
System.out.println();
System.out.println("== Agregasi (worst-of: DOWN > OUT_OF_SERVICE > UP > UNKNOWN) ==");
System.out.println("status /actuator/health: " + keseluruhan);
Grup liveness = new Grup("livenessProbe", List.of(new IndikatorPing()));
Grup readiness = new Grup("readinessProbe",
List.of(new IndikatorDb(), new IndikatorMail()));
System.out.println();
System.out.println("== Health groups (probe orkestrator) ==");
System.out.printf("%-15s %-6s indikator: ping -> pod TIDAK direstart%n",
liveness.nama(), liveness.status());
System.out.printf("%-15s %-6s indikator: db, mail -> trafik dihentikan, pod tetap hidup%n",
readiness.nama(), readiness.status());
System.out.println();
System.out.println("== Keputusan orkestrator ==");
System.out.println("liveness UP : jangan restart, proses sehat");
System.out.println("readiness DOWN: jangan kirim trafik baru ke instance ini");
}
}Output yang Dihasilkan:
== Indikator kesehatan ==
db UP kumpulan=aktif seleksi1=ok
diskSpace UP sisa=42GB ambang=10GB
mail DOWN galat=smtp tidak merespons
== Agregasi (worst-of: DOWN > OUT_OF_SERVICE > UP > UNKNOWN) ==
status /actuator/health: DOWN
== Health groups (probe orkestrator) ==
livenessProbe UP indikator: ping -> pod TIDAK direstart
readinessProbe DOWN indikator: db, mail -> trafik dihentikan, pod tetap hidup
== Keputusan orkestrator ==
liveness UP : jangan restart, proses sehat
readiness DOWN: jangan kirim trafik baru ke instance iniMari kita bedah panel tersebut:
enum Statusdankeparahan(...): empat status Actuator dengan urutan keparahan yang ditulis eksplisit dalam switch —DOWNpaling parah (0),UNKNOWNpaling belakang (3). Angka inilah yang dipakai agregator untuk membandingkan, menggantikan pembandingan string yang rawan salah urutan.IndikatorMail.periksa(): satu-satunya yang mengembalikanDOWN—smtp tidak merespons. IndikatordbdandiskSpacesehat dengan detail tetap (nilai beku, bukan pembacaan disk nyata, agar keluaran dapat diverifikasi ulang).Grup.status(): agregasi worst-of — loop menyimpan status terparah (keparahanterkecil) dari seluruh anggota. Diterapkan pada grupkeseluruhanberisi tiga indikator, hasilnyaDOWNmeski dua dari tiga komponen UP: itulah makna “satu komponen menjatuhkan semuanya”.livenessProbevsreadinessProbe: grup liveness hanya berisiping(state internal) sehingga tetapUP— orkestrator tidak me-restart pod. Grup readiness berisidbdanmail; karena mail DOWN, readiness turun keDOWN— trafik dihentikan sementara proses tetap hidup. Justru perbedaan dua baris terakhir inilah inti pemisahan probe.- Konsekuensi cetak:
pod TIDAK direstartpada liveness dantrafik dihentikan, pod tetap hiduppada readiness merangkum kontrak kedua probe — satu kata status berbeda menghasilkan tindakan orkestrator yang sama sekali berbeda.
Health memberi jawaban biner saat ditanya; pertanyaan berikutnya adalah memahami mengapa dan kapan — tren latensi, laju error, jejak request lintas service. Tiga pilar observability itu — log terstruktur, metrik, dan tracing — menyatu di Spring Boot lewat Observation API dan Micrometer, pokok bahasan Chapter Observability Aplikasi berikutnya.
Referensi #
- Spring Boot Reference: Actuator Endpoints
- Spring Boot Reference: Monitoring and Management
- Spring Boot Reference: Production-ready Features
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
PanelKesehatan.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
public class PanelKesehatan {
// Status ala Spring Boot Actuator.
enum Status {
UP, DOWN, OUT_OF_SERVICE, UNKNOWN
}
// Urutan keparahan persis SimpleStatusAggregator bawaan:
// DOWN, OUT_OF_SERVICE, UP, UNKNOWN — makin kecil makin parah.
static int keparahan(Status s) {
return switch (s) {
case DOWN -> 0;
case OUT_OF_SERVICE -> 1;
case UP -> 2;
case UNKNOWN -> 3;
};
}
record Laporan(Status status, String detail) { }
// Padanan HealthIndicator: satu komponen, satu keputusan.
interface Indikator {
String nama();
Laporan periksa();
}
static class IndikatorDb implements Indikator {
@Override
public String nama() {
return "db";
}
@Override
public Laporan periksa() {
// Uji nyata: kumpulan koneksi + SELECT 1.
return new Laporan(Status.UP, "kumpulan=aktif seleksi1=ok");
}
}
static class IndikatorDisk implements Indikator {
@Override
public String nama() {
return "diskSpace";
}
@Override
public Laporan periksa() {
long sisaGb = 42; // nilai tetap agar contoh deterministik
return sisaGb >= 10
? new Laporan(Status.UP, "sisa=" + sisaGb + "GB ambang=10GB")
: new Laporan(Status.DOWN, "sisa=" + sisaGb + "GB");
}
}
static class IndikatorMail implements Indikator {
@Override
public String nama() {
return "mail";
}
@Override
public Laporan periksa() {
// SMTP sedang tidak merespons.
return new Laporan(Status.DOWN, "galat=smtp tidak merespons");
}
}
// Liveness: apakah proses masih hidup? Tidak memeriksa dependensi eksternal.
static class IndikatorPing implements Indikator {
@Override
public String nama() {
return "ping";
}
@Override
public Laporan periksa() {
return new Laporan(Status.UP, "proses=hidup");
}
}
// Padanan health group: sekumpulan indikator dengan nama.
record Grup(String nama, List<Indikator> anggota) {
Status status() {
Status parah = Status.UP;
for (Indikator i : anggota) {
Laporan l = i.periksa();
if (keparahan(l.status()) < keparahan(parah)) {
parah = l.status();
}
}
return parah;
}
}
public static void main(String[] args) {
List<Indikator> semua = List.of(
new IndikatorDb(),
new IndikatorDisk(),
new IndikatorMail());
System.out.println("== Indikator kesehatan ==");
for (Indikator i : semua) {
Laporan l = i.periksa();
System.out.printf("%-10s %-6s %s%n", i.nama(), l.status(), l.detail());
}
// Agregasi worst-of: satu komponen DOWN menjatuhkan seluruh status.
Status keseluruhan = new Grup("keseluruhan", semua).status();
System.out.println();
System.out.println("== Agregasi (worst-of: DOWN > OUT_OF_SERVICE > UP > UNKNOWN) ==");
System.out.println("status /actuator/health: " + keseluruhan);
Grup liveness = new Grup("livenessProbe", List.of(new IndikatorPing()));
Grup readiness = new Grup("readinessProbe",
List.of(new IndikatorDb(), new IndikatorMail()));
System.out.println();
System.out.println("== Health groups (probe orkestrator) ==");
System.out.printf("%-15s %-6s indikator: ping -> pod TIDAK direstart%n",
liveness.nama(), liveness.status());
System.out.printf("%-15s %-6s indikator: db, mail -> trafik dihentikan, pod tetap hidup%n",
readiness.nama(), readiness.status());
System.out.println();
System.out.println("== Keputusan orkestrator ==");
System.out.println("liveness UP : jangan restart, proses sehat");
System.out.println("readiness DOWN: jangan kirim trafik baru ke instance ini");
}
}