Kodeloka
  1. Kodeloka
  2. Kelas Grafana
  3. Materi

Modul 11 · Modul 11 dari 15

Unified Alerting: Rule, Expression, dan State Machine

Membangun alert yang benar-benar berbunyi saat perlu dan diam saat tidak, termasuk memahami siklus state yang sering disalahpahami.

Dua Jenis Alert Rule

Grafana-managedData source-managed
Dievaluasi olehGrafanaPrometheus / Mimir / Loki
Sumber dataBanyak data source sekaligusSatu data source
Bisa pakai Expression✅❌ (hanya PromQL/LogQL)
Disimpan diBasis data GrafanaBerkas rule di data source
Bertahan bila Grafana mati❌✅
Butuh manageAlerts: true—✅ untuk mengedit dari UI
🎯 Cara memilih

Alert yang membangunkan orang sebaiknya dievaluasi Prometheus (data source-managed): ia tetap bekerja walaupun Grafana sedang mati atau sedang di-upgrade. Grafana-managed dipakai untuk alert yang perlu menggabungkan beberapa sumber data, memakai Expression, atau berbasis SQL — hal yang tidak mungkin dilakukan Prometheus.

Anatomi Grafana-managed Alert Rule

provisioning/alerting/rules.ymlyaml
apiVersion: 1
groups:
  - orgId: 1
    name: bayarcepat-slo
    folder: Peringatan Produksi
    interval: 1m                     # seberapa sering dievaluasi
    rules:
      - uid: err-rate-ledger
        title: Error rate ledger melewati SLO
        condition: C                  # refId mana yang menentukan
        for: 5m                       # harus benar selama ini dulu
        noDataState: NoData
        execErrState: Alerting
        annotations:
          summary: 'Error rate ledger {{ humanizePercentage $values.C.Value }}'
          description: >
            Error 5xx pada layanan ledger melewati 1% selama 5 menit.
            Periksa pool koneksi database lebih dulu.
          runbook_url: https://wiki.internal/runbook/ledger-error-rate
          dashboardUId: bayarcepat-ikhtisar
          panelId: '3'
        labels:
          severity: critical
          tim: pembayaran
          layanan: ledger
        data:
          - refId: A
            relativeTimeRange: { from: 600, to: 0 }
            datasourceUid: prom-utama
            model:
              expr: sum(rate(http_requests_total{layanan="ledger",status=~"5.."}[5m]))
              instant: true
              refId: A
          - refId: B
            relativeTimeRange: { from: 600, to: 0 }
            datasourceUid: prom-utama
            model:
              expr: sum(rate(http_requests_total{layanan="ledger"}[5m]))
              instant: true
              refId: B
          - refId: C
            datasourceUid: __expr__
            model:
              type: math
              expression: '$A / $B > 0.01'
              refId: C

State Machine — Bagian yang Paling Sering Disalahpahami

siklus statetext
        kondisi benar               bertahan selama "for"
Normal ──────────────────> Pending ───────────────────> Alerting
   ▲                          │                            │
   │      kondisi salah       │                            │
   └──────────────────────────┴────────────────────────────┘
                                          │
   kueri gagal ──> Error   ─┐              │ setelah resolve
   data hilang ──> NoData  ─┴──> mengikuti execErrState / noDataState
                                          ▼
                                     Normal (Resolved)
StateArtiNotifikasi terkirim?
NormalKondisi tidak terpenuhiTidak
PendingKondisi benar, tapi belum selama forTidak
AlertingBenar dan sudah melewati forYa
NoDataKueri tidak menghasilkan seriesSesuai noDataState
ErrorKueri gagal / data source matiSesuai execErrState
⛔ NoData adalah keputusan, bukan detail teknis

Pilihan noDataState menentukan apakah hilangnya monitoring dianggap masalah:

  • NoData — memicu alert khusus NoData. Pilihan yang benar untuk alert kritis: metrik yang hilang biasanya berarti aplikasi mati.
  • Alerting — perlakukan sama seperti kondisi terpenuhi. Agresif, cocok untuk heartbeat.
  • OK — anggap sehat. Berbahaya: aplikasi mati total menjadi "tidak ada masalah" karena tidak ada lagi yang mengekspos metrik.

Untuk metrik yang jarang muncul (misalnya error counter yang biasanya nol), pola yang benar bukan noDataState: OK, melainkan membuat kueri selalu menghasilkan angka — misalnya dengan or vector(0) di PromQL.

Multi-dimensional Alert

Satu rule bisa menghasilkan banyak instance alert, satu per kombinasi label. Ini kekuatan besar sekaligus sumber banjir notifikasi.

satu rule, banyak instancepromql
sum by (layanan, instance) (
  rate(http_requests_total{status=~"5.."}[5m])
) > 1

# Bila 8 layanan × 5 instance melewati batas:
# → 40 instance alert dari SATU rule
# → tanpa grouping yang benar, 40 pesan di Slack

Solusinya bukan mengurangi label pada rule, melainkan mengatur grouping di notification policy (bab berikutnya membahasnya bersama contact point).

Contact Point dan Notification Policy

provisioning/alerting/notifikasi.ymlyaml
apiVersion: 1

contactPoints:
  - orgId: 1
    name: slack-pembayaran
    receivers:
      - uid: cp-slack-bayar
        type: slack
        settings:
          url: $__env{SLACK_WEBHOOK_PEMBAYARAN}
          title: '{{ template "slack.title" . }}'
          text: '{{ template "slack.text" . }}'
  - orgId: 1
    name: pagerduty-oncall
    receivers:
      - uid: cp-pd
        type: pagerduty
        settings:
          integrationKey: $__env{PAGERDUTY_KEY}
          severity: critical

policies:
  - orgId: 1
    receiver: slack-pembayaran      # default
    group_by: ['alertname', 'layanan']
    group_wait: 30s                 # tunggu, mungkin ada yang menyusul
    group_interval: 5m              # jeda antar update grup yang sama
    repeat_interval: 4h             # ulangi bila masih menyala
    routes:
      - receiver: pagerduty-oncall
        object_matchers:
          - ['severity', '=', 'critical']
        continue: false             # berhenti di sini, jangan lanjut
        mute_time_intervals: []
      - receiver: slack-pembayaran
        object_matchers:
          - ['tim', '=', 'pembayaran']
        group_by: ['alertname', 'layanan', 'instance']

muteTimeIntervals:
  - orgId: 1
    name: luar-jam-kerja-non-kritis
    time_intervals:
      - times:
          - start_time: '18:00'
            end_time: '08:00'
        weekdays: ['monday:friday']
        location: 'Asia/Jakarta'
⚠️ group_by menentukan banyaknya pesan
text
group_by: ['alertname']
  → 40 instance menjadi 1 pesan berisi 40 baris  ✅ enak dibaca

group_by: ['alertname', 'instance']
  → 40 pesan terpisah                            ❌ banjir

group_by: ['...']
  → grup per setiap kombinasi label unik          ❌ paling buruk

Mulailah dari grouping yang kasar. Perhalus hanya bila memang ada yang perlu ditangani terpisah.

Templating Notifikasi

template pesantext
{{ define "slack.title" }}
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }}
{{ end }}

{{ define "slack.text" }}
{{ range .Alerts -}}
*Layanan:* {{ .Labels.layanan }}  ·  *Severity:* {{ .Labels.severity }}
*Nilai:* {{ .ValueString }}
*Sejak:* {{ .StartsAt.Format "15:04:05 MST" }}
{{ if .Annotations.summary }}*Ringkasan:* {{ .Annotations.summary }}{{ end }}
{{ if .Annotations.runbook_url }}*Runbook:* {{ .Annotations.runbook_url }}{{ end }}
{{ if .DashboardURL }}*Dashboard:* {{ .DashboardURL }}{{ end }}
{{ if .SilenceURL }}*Bisukan:* {{ .SilenceURL }}{{ end }}
---
{{ end }}
{{ end }}

Fungsi yang berguna di dalam template: humanize, humanizePercentage, humanizeDuration, toUpper, dan reReplaceAll.

Silence, Mute Timing, dan Pause

MekanismeEfekKapan dipakai
SilenceRule tetap dievaluasi, notifikasi ditahanMaintenance terjadwal, insiden yang sudah diketahui
Mute timingNotifikasi ditahan pada jadwal berulangAlert non-kritis di luar jam kerja
Pause ruleRule berhenti dievaluasi sama sekaliRule yang sedang diperbaiki

Silence selalu punya batas waktu — dan itu bagus. Alert yang dibisukan "selamanya" adalah alert yang seharusnya dihapus atau diperbaiki.