Alerting #
Alert yang baik adalah alert yang ketika muncul di jam 3 pagi, engineer yang bertugas tahu persis apa yang harus dilakukan. Alert yang buruk adalah kebisingan — terlalu banyak, terlalu sensitif, atau tidak actionable — yang membuat on-call engineer kehilangan kepercayaan pada sistem monitoring dan akhirnya mengabaikan alert. Merancang alerting yang tepat sama pentingnya dengan mengumpulkan metrik yang tepat.
Prinsip Alert yang Baik #
Alert yang baik memenuhi kriteria:
1. Actionable
Ada sesuatu yang bisa dilakukan manusia sebagai respons
Alert "CPU tinggi" tanpa threshold yang bermakna tidak actionable
Alert "Error rate >5% selama 5 menit" → investigasi, rollback, atau scale
2. Symptom-based, bukan cause-based
Alert berdasarkan dampak yang dirasakan pengguna, bukan penyebab internal
Buruk: "Pod baru tidak bisa di-schedule" (internal)
Baik: "API error rate >1% selama 5 menit" (dampak pada pengguna)
3. Cukup sensitif, tidak terlalu sensitif
Terlalu sensitif: banyak false positive → alert fatigue
Kurang sensitif: masalah nyata lolos tidak terdeteksi
Gunakan for: clause untuk pastikan kondisi persisten sebelum alert
4. Severity yang tepat
Critical/Page: segera butuh tindakan, bisa bangunkan on-call
Warning/Ticket: perlu perhatian tapi tidak malam ini
PrometheusRule: Mendefinisikan Alert #
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-alerts
namespace: monitoring
labels:
release: prometheus # agar Prometheus Operator pick up
spec:
groups:
- name: api.critical
rules:
# Alert: error rate tinggi
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{
namespace="production",
status_code=~"5.."
}[5m]))
/
sum(rate(http_requests_total{
namespace="production"
}[5m]))
> 0.05 # lebih dari 5% error rate
for: 3m # kondisi harus persisten 3 menit sebelum alert
labels:
severity: critical
team: backend
annotations:
summary: "Error rate tinggi di production API"
description: |
Error rate: {{ $value | humanizePercentage }}
Jauh di atas threshold 5%.
runbook_url: "https://wiki.company.com/runbooks/high-error-rate"
# Alert: latensi p99 tinggi
- alert: HighP99Latency
expr: |
histogram_quantile(0.99,
sum by (le) (
rate(http_request_duration_seconds_bucket{
namespace="production"
}[5m])
)
) > 2 # p99 di atas 2 detik
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "P99 latency tinggi"
description: "P99 saat ini {{ $value }}s, threshold 2s"
- name: kubernetes.critical
rules:
# Alert: Pod crash loop
- alert: PodCrashLooping
expr: |
increase(kube_pod_container_status_restarts_total{
namespace="production"
}[15m]) > 3
for: 0m # segera alert, tidak perlu for
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} crash looping"
description: "Container {{ $labels.container }} restart {{ $value }} kali dalam 15 menit"
# Alert: Deployment tidak mencapai desired replicas
- alert: DeploymentReplicasMismatch
expr: |
kube_deployment_status_replicas_available{namespace="production"}
!=
kube_deployment_spec_replicas{namespace="production"}
for: 10m
labels:
severity: warning
annotations:
summary: "Deployment {{ $labels.deployment }} tidak punya cukup replicas"
description: |
Tersedia: {{ $value }} replicas
Diinginkan: {{ query "kube_deployment_spec_replicas{deployment=\"%s\"}" $labels.deployment | first | value }}
# Alert: Node hampir kehabisan disk
- alert: NodeDiskPressure
expr: |
(
node_filesystem_avail_bytes{mountpoint="/", fstype!="tmpfs"}
/ node_filesystem_size_bytes{mountpoint="/"}
) < 0.10 # kurang dari 10% tersisa
for: 5m
labels:
severity: warning
annotations:
summary: "Disk hampir penuh di node {{ $labels.instance }}"
description: "Hanya tersisa {{ $value | humanizePercentage }} ruang disk"
Konfigurasi Alertmanager #
Alertmanager menerima alert dari Prometheus dan memutuskan ke mana harus dikirim:
# alertmanager-config.yaml
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/...'
route:
receiver: 'default-slack'
group_by: ['alertname', 'namespace']
group_wait: 30s # tunggu 30 detik sebelum kirim grup pertama
group_interval: 5m # interval antara notifikasi untuk alert yang sama
repeat_interval: 4h # kirim ulang jika alert belum resolved setelah 4 jam
routes:
# Critical alert → PagerDuty (membangunkan on-call)
- match:
severity: critical
receiver: 'pagerduty-critical'
repeat_interval: 30m
# Warning → Slack channel berbeda
- match:
severity: warning
receiver: 'slack-warning'
group_wait: 5m
receivers:
- name: 'default-slack'
slack_configs:
- channel: '#alerts-production'
title: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
text: |
{{ range .Alerts }}
*Alert:* {{ .Annotations.summary }}
*Severity:* {{ .Labels.severity }}
*Description:* {{ .Annotations.description }}
*Runbook:* {{ .Annotations.runbook_url }}
{{ end }}
send_resolved: true
- name: 'pagerduty-critical'
pagerduty_configs:
- routing_key: '<pagerduty-integration-key>'
description: '{{ .CommonAnnotations.summary }}'
client: 'AlertManager'
client_url: 'https://grafana.company.com'
- name: 'slack-warning'
slack_configs:
- channel: '#alerts-warning'
text: '{{ .CommonAnnotations.description }}'
send_resolved: true
Silencing dan Inhibition #
# Silence alert selama maintenance window
amtool silence add \
alertname="HighErrorRate" \
--duration=2h \
--comment="Scheduled maintenance: database upgrade"
# Lihat semua silence aktif
amtool silence query
# Hapus silence
amtool silence expire <silence-id>
# Inhibition: suppress warning jika critical sudah aktif
# (jangan kirim warning jika critical yang lebih penting sudah dipagingkan)
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'namespace']
# Jika ada critical alert di namespace X, suppress warning di namespace X
SLO-Based Alerting #
Alih-alih alert berdasarkan threshold arbitrari, alert berdasarkan SLO (Service Level Objective) lebih bermakna:
SLO definition:
"API harus menyelesaikan 99.9% request tanpa error dalam rolling 30 hari"
Error budget = 100% - 99.9% = 0.1% error yang diperbolehkan per 30 hari
= 0.001 × 30 × 24 × 60 = 43.2 menit downtime/error diperbolehkan per bulan
Alert saat error budget terbakar terlalu cepat:
Tier 1 (Critical): burn rate > 14.4x dalam 1 jam
→ Menghabiskan 2% error budget dalam 1 jam
→ Harus segera ditangani
Tier 2 (Warning): burn rate > 6x dalam 6 jam
→ Menghabiskan 5% error budget dalam 6 jam
→ Perlu investigasi hari ini
# Multi-window, multi-burn-rate alert untuk SLO
- alert: SLOErrorBudgetBurnRateCritical
expr: |
(
sum(rate(http_requests_total{status_code=~"5.."}[1h]))
/ sum(rate(http_requests_total[1h]))
) > (14.4 * 0.001) # 14.4x burn rate, SLO 99.9% = 0.1% error budget
and
(
sum(rate(http_requests_total{status_code=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
) > (14.4 * 0.001) # konfirmasi dengan window pendek juga
for: 2m
labels:
severity: critical
annotations:
summary: "SLO error budget terbakar terlalu cepat"
Ringkasan #
- Alert berdasarkan dampak pengguna, bukan internal metric — “error rate >5%” lebih actionable dari “CPU high”; symptom-based alerting mengurangi noise.
for: Nuntuk menghindari false positive dari spike sementara — kondisi harus persisten selama N menit sebelum alert dikirim;for: 5madalah nilai aman untuk sebagian besar alert.- Critical → PagerDuty, Warning → Slack — kriteria severity yang jelas mencegah alert fatigue; hanya bangunkan on-call jika benar-benar diperlukan.
- Runbook URL di setiap alert — sertakan link ke dokumentasi langkah penanganan; engineer baru sekalipun bisa ikuti prosedur saat on-call.
- Silencing selama maintenance — alert yang expected selama maintenance tidak perlu mengganggu on-call; buat silence sebelum window dimulai.
- SLO-based alerting untuk produksi matang — alert berbasis error budget burn rate lebih bermakna dari threshold arbitrari dan langsung terhubung ke komitmen reliability ke pengguna.
← Sebelumnya: Metrics dan Prometheus Berikutnya: Distributed Tracing →