Tampilan
Kenapa penting
Di M14 kamu sudah memasang tracing dan korelasi log lintas service. Tapi data telemetri saja belum cukup: di production seseorang harus tahu kapan sistem sedang tidak sehat, seberapa parah, dan apa yang harus dilakukan. Platform observability menyatukan metrik, log, dan trace di satu tempat, lalu menurunkannya menjadi SLO, alert, dan runbook. Pertanyaan "bagaimana kamu tahu service-mu sehat di production?" hampir pasti muncul di interview backend senior, dan jawaban yang baik bukan "lihat log", melainkan "SLO dengan alert burn rate dan dashboard RED".
Konsep inti
Tiga sinyal, empat komponen
| Sinyal | Disimpan di | Dikirim lewat | Dipakai untuk |
|---|---|---|---|
| Metrik | Prometheus | di-scrape dari /actuator/prometheus | dashboard, SLO, alert |
| Log | Loki | OpenTelemetry Collector (atau agen log) | detail kejadian per request |
| Trace | Tempo | OpenTelemetry Collector (OTLP) | jalur request lintas service |
| Tampilan | Grafana | membaca ketiganya | dashboard, eksplorasi, alert |
Kekuatan platform ini ada di korelasi: dari panel latency di Grafana kamu klik ke trace lambat di Tempo, lalu dari span ke log dengan traceId yang sama di Loki. Itu sebabnya traceId di log (bab tracing M14) begitu penting.
Cara paling umum memasang Prometheus + Grafana + Alertmanager di cluster adalah chart Helm kube-prometheus-stack, yang juga memasang Prometheus Operator:
bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespacePrometheus Operator dan ServiceMonitor
Tanpa Operator, kamu harus mengedit file konfigurasi scrape Prometheus setiap ada service baru. Operator menambahkan custom resource: ServiceMonitor ("scrape Service yang labelnya begini") dan PrometheusRule (aturan alert). Tim service cukup menaruh ServiceMonitor di samping Deployment-nya.
yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service
namespace: shop
labels:
release: monitoring # harus cocok dengan serviceMonitorSelector milik Prometheus
spec:
selector:
matchLabels:
app: order-service # label pada Service, bukan pada Pod
endpoints:
- port: http # NAMA port di Service
path: /actuator/prometheus
interval: 30sDi sisi Spring Boot, aktifkan endpoint Prometheus dan histogram latency agar persentil bisa dihitung di server:
yaml
# application.yaml (potongan) — butuh micrometer-registry-prometheus
management:
endpoints:
web:
exposure:
include: health,prometheus
metrics:
distribution:
percentiles-histogram:
http.server.requests: trueOpenTelemetry Collector sebagai "router" telemetri
Aplikasi cukup mengirim OTLP ke satu alamat — Collector — dan Collector yang memutuskan ke mana data diteruskan. Ganti backend (misalnya Tempo ke vendor lain) tanpa menyentuh kode aplikasi.
yaml
# Potongan konfigurasi OpenTelemetry Collector
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
batch: {}
exporters:
otlp/tempo:
endpoint: tempo.monitoring:4317
tls:
insecure: true
otlphttp/loki:
endpoint: http://loki.monitoring:3100/otlp
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/tempo]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlphttp/loki]memory_limiter dipasang paling depan agar Collector menolak data saat memori hampir penuh, bukan mati karena OOM.
Dashboard RED per service
Untuk service yang melayani request, tiga panel ini menjawab 80% pertanyaan saat insiden:
| Huruf | Arti | PromQL (metrik Micrometer) |
|---|---|---|
| Rate | request per detik | sum by (application) (rate(http_server_requests_seconds_count[5m])) |
| Errors | rasio 5xx | sum by (application) (rate(http_server_requests_seconds_count{status=~"5.."}[5m])) / sum by (application) (rate(http_server_requests_seconds_count[5m])) |
| Duration | latency p95 | histogram_quantile(0.95, sum by (le, application) (rate(http_server_requests_seconds_bucket[5m]))) |
Buat satu dashboard template dengan variabel application, lalu pakai untuk semua service. Konsistensi ini membuat siapa pun yang on-call bisa membaca dashboard service yang bukan miliknya.
SLI, SLO, dan error budget
- SLI (indicator): ukuran yang dirasakan pengguna, misalnya "persentase request checkout yang sukses dan < 800 ms".
- SLO (objective): target SLI dalam jendela waktu, misalnya "99,9% per 30 hari".
- Error budget: sisanya — 0,1% itu jatah gagal yang boleh dipakai untuk rilis, eksperimen, atau insiden.
Error budget mengubah debat "rilis atau stabil" menjadi angka: budget masih banyak → rilis jalan terus; budget habis → fokus ke keandalan. Burn rate mengukur seberapa cepat budget terbakar: 1x berarti budget pas habis di akhir jendela, 14,4x berarti 2% budget 30 hari habis dalam satu jam.
java
public class ErrorBudget {
record Slo(String name, double target, int windowDays) {
double budgetMinutes() {
return (1 - target) * windowDays * 24 * 60;
}
}
// burn rate = rasio error teramati / rasio error yang diizinkan SLO
static double burnRate(long errors, long total, double target) {
double observed = (double) errors / total;
return observed / (1 - target);
}
public static void main(String[] args) {
Slo checkout = new Slo("checkout-availability", 0.999, 30);
System.out.printf("Error budget %s: %.1f menit / %d hari%n",
checkout.name(), checkout.budgetMinutes(), checkout.windowDays());
// 1 jam terakhir: 120.000 request, 1.800 gagal (5xx)
double rate = burnRate(1_800, 120_000, checkout.target());
System.out.printf("Burn rate 1 jam: %.1fx%n", rate);
double hoursToEmpty = checkout.windowDays() * 24 / rate;
System.out.printf("Budget habis dalam: %.1f jam%n", hoursToEmpty);
System.out.println(rate >= 14.4 ? "PAGE on-call" : rate >= 6 ? "Tiket" : "Aman");
}
}Output:
text
Error budget checkout-availability: 43.2 menit / 30 hari
Burn rate 1 jam: 15.0x
Budget habis dalam: 48.0 jam
PAGE on-callAmbang di contoh ini disederhanakan. SRE Workbook merekomendasikan multi-window, multi-burn-rate: misalnya page bila 14,4x pada jendela 1 jam dan 5 menit (jendela pendek memastikan masalahnya masih berlangsung), page bila 6x pada 6 jam dan 30 menit, serta tiket untuk burn rate rendah yang berlangsung berhari-hari.
Alerting yang baik
Aturan praktis:
- Alert pada gejala, bukan penyebab. Pengguna tidak peduli CPU 90%; mereka peduli checkout gagal. CPU, memori, dan restart pod tetap dipantau di dashboard.
- Setiap page harus actionable. Kalau penerima alert tidak bisa melakukan apa pun, itu bukan page.
- Setiap alert punya runbook. Tautkan di anotasi
runbook_url. - Ukur kebisingan. Alert yang sering di-ack lalu diabaikan harus diperbaiki atau dihapus — itulah alert fatigue, dan akibatnya alert penting ikut terabaikan.
yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: checkout-slo
namespace: shop
labels:
release: monitoring
spec:
groups:
- name: checkout-slo
rules:
- alert: CheckoutErrorBudgetFastBurn
expr: |
(
sum(rate(http_server_requests_seconds_count{application="order-service",uri="/orders",status=~"5.."}[1h]))
/ sum(rate(http_server_requests_seconds_count{application="order-service",uri="/orders"}[1h]))
) > (14.4 * 0.001)
and
(
sum(rate(http_server_requests_seconds_count{application="order-service",uri="/orders",status=~"5.."}[5m]))
/ sum(rate(http_server_requests_seconds_count{application="order-service",uri="/orders"}[5m]))
) > (14.4 * 0.001)
for: 2m
labels:
severity: page
annotations:
summary: "Checkout membakar error budget 14,4x lebih cepat dari normal"
runbook_url: https://wiki.internal/runbooks/checkout-error-budgetRunbook, on-call, dan postmortem
Runbook adalah halaman singkat per alert: arti alert, dampak ke pengguna, dashboard yang dibuka, langkah diagnosis, mitigasi cepat (rollback, scale out, matikan feature flag), dan kapan eskalasi. Tulis untuk orang yang baru bangun tidur.
On-call yang sehat punya rotasi jelas, jadwal serah terima, dan prioritas: mitigasi dulu (pulihkan layanan), cari akar masalah belakangan. Jumlah page per shift adalah metrik kesehatan tim.
Postmortem blameless ditulis setelah insiden signifikan: timeline, dampak (berapa budget terpakai), akar masalah dan faktor pendukung, apa yang berjalan baik, serta action item dengan pemilik dan tenggat. Fokusnya "kenapa sistem mengizinkan ini terjadi", bukan "siapa yang salah" — orang yang takut disalahkan akan menyembunyikan informasi.
Contoh di dunia backend
Tim toko online menetapkan SLO untuk tiga perjalanan utama pengguna, bukan untuk setiap service:
| Perjalanan | SLI | SLO (30 hari) |
|---|---|---|
| Lihat katalog | request GET /products sukses dan < 300 ms | 99,5% |
| Checkout | request POST /orders sukses dan < 800 ms | 99,9% |
| Konfirmasi pembayaran | event PaymentCompleted diproses < 60 detik | 99,9% |
Suatu malam alert CheckoutErrorBudgetFastBurn berbunyi. On-call membuka runbook, lalu dashboard RED order-service: error rate naik ke 1,5% tepat setelah rilis 21.40. Dari panel error ia klik ke contoh trace di Tempo — span panggilan ke payment-service timeout. Log payment-service di Loki (difilter traceId) menunjukkan pool koneksi database habis. Mitigasi sesuai runbook: rollback payment-service dengan kubectl rollout undo. Error rate turun dalam 4 menit. Postmortem keesokan harinya menghasilkan action item: tambah alert saturasi pool sebagai ticket (bukan page), canary untuk rilis payment-service, dan test beban untuk perubahan konfigurasi pool.
Pertanyaan interview
1. Apa beda SLI, SLO, dan SLA?
SLI adalah ukuran (misalnya rasio request sukses), SLO adalah target internal untuk ukuran itu (99,9% per 30 hari), dan SLA adalah janji kontraktual ke pelanggan beserta konsekuensinya (misalnya kompensasi). SLO biasanya dibuat lebih ketat dari SLA agar tim punya ruang bereaksi sebelum kontrak terlanggar.
2. Kenapa alert sebaiknya berbasis burn rate SLO, bukan threshold error rate tetap?
Threshold tetap seperti "error > 1% selama 5 menit" tidak tahu konteks: bisa terlalu sensitif untuk lonjakan singkat atau terlalu lambat untuk kebocoran kecil yang terus-menerus. Burn rate mengaitkan alert langsung ke dampak terhadap budget, dan kombinasi jendela panjang + pendek membuat alert cepat muncul saat parah serta cepat reda setelah pulih.
3. Bagaimana mencegah high cardinality di Prometheus?
Jangan pakai nilai tak terbatas (user ID, order ID, URL mentah dengan path variable) sebagai label. Micrometer di Spring memakai template URI (/orders/{id}), jadi hindari membuat tag kustom dari input pengguna. Detail per request disimpan di log dan trace, lalu dihubungkan lewat traceId.
Jebakan umum
ServiceMonitortidak terbaca karena labelreleasetidak cocok atauportdiisi nomor, bukan nama port Service.- Alert pada penyebab (CPU, memori, satu pod restart) yang membangunkan orang tanpa ada dampak ke pengguna.
- Alert tanpa runbook — on-call menebak-nebak di tengah malam.
- Label berkardinalitas tinggi (
orderId,email) yang membuat Prometheus kehabisan memori. - Menghitung p95 dari rata-rata persentil per pod. Persentil tidak bisa dirata-rata; hitung dari histogram bucket yang dijumlahkan dengan
histogram_quantile. - SLO 100%. Tidak ada sistem yang mencapainya; target itu menghapus ruang untuk rilis dan membuat tim takut berubah.
Ringkasan
- Prometheus (metrik), Loki (log), Tempo (trace), dan Grafana (tampilan) dikorelasikan lewat
traceId; OpenTelemetry Collector merutekan telemetri dari aplikasi ke backend. - Prometheus Operator memakai
ServiceMonitordanPrometheusRulesehingga tiap tim mendaftarkan scrape dan alert sendiri. - Dashboard RED (rate, errors, duration) yang seragam memudahkan siapa pun membaca service mana pun.
- SLO menetapkan target, error budget memberi jatah gagal, burn rate mengukur kecepatan budget terbakar.
- Alert berbasis gejala, actionable, punya runbook; on-call memulihkan dulu, postmortem blameless memperbaiki sistemnya.