Monitoring #
Log memberitahu apa yang sudah terjadi. Monitoring memberitahu apa yang sedang terjadi — dan memperingatkan sebelum masalah menjadi insiden. Mengotomasi setup monitoring dengan Ansible memastikan setiap server langsung termonitor sejak pertama kali di-provision, dengan konfigurasi yang konsisten. Artikel ini membahas cara men-deploy stack monitoring Prometheus + Grafana menggunakan Ansible, dari instalasi agen sampai alert rules yang bisa di-review di pull request.
Perbedaan penting dengan /observability/logging/: logging adalah catatan event-level yang immutable, monitoring adalah stream metrik numerik yang di-aggregate. Keduanya saling melengkapi — log menjawab “kenapa CPU naik?”, monitoring menjawab “CPU naik di host mana?”. Artikel ini fokus ke monitoring; untuk log lihat artikel logging, dan untuk cara mengumpulkannya dari banyak service lihat /observability/metric-collection/.
Anatomi Stack Prometheus #
Prometheus bukan sekadar database time-series — dia adalah ekosistem lengkap dengan komponen yang punya peran spesifik. Memahami arsitektur ini membantu kita melakukan debug masalah dan merancang high availability:
flowchart LR
A["Node Exporter<br/>:9100"] -->|"scrape HTTP"| P["Prometheus<br/>:9090"]
B["App Exporter<br/>:9090+"] -->|"scrape HTTP"| P
C["Pushgateway<br/>:9091"] -->|"push"| P
P -->|"query PromQL"| AM["Alertmanager<br/>:9093"]
P -->|"remote_write"| R["Remote Storage<br/>Thanos / Cortex"]
P -->|"datasource"| G["Grafana<br/>:3000"]
AM -->|"route"| SL["Slack / PagerDuty / Email"]
G -->|"dashboard"| U["SRE / Developer"]
style A stroke:#b45309,stroke-width:2px
style B stroke:#b45309,stroke-width:2px
style C stroke:#b45309,stroke-width:2px
style P stroke:#1d4ed8,stroke-width:2px
style AM stroke:#be185d,stroke-width:2px
style G stroke:#15803d,stroke-width:2px
style U stroke:#7e22ce,stroke-width:2px
Arah aliran data penting: Prometheus menarik (pull/scrape) metrik dari target, bukan target mengirim (push) ke Prometheus. Pengecualian: Pushgateway dipakai untuk batch job yang berjalan pendek dan tidak bisa di-scrape.
Model pull Prometheus memiliki konsekuensi operasional yang perlu kita pahami: jika target di belakang firewall tidak reachable dari Prometheus, metric-nya tidak akan masuk. Untuk target di VPC lain atau di belakang NAT, gunakan federation atau relabelexternal_labelssaat scrape. Untuk microservice di Kubernetes, service discovery lewat annotationprometheus.io/scrapelebih scalable dari static config.
Empat Tipe Metrik Prometheus #
Sebelum menulis alert rules atau dashboard, pahami dulu empat tipe metrik yang Prometheus bisa simpan. Pemahaman ini menentukan fungsi dan storage:
| Tipe | Karakteristik | Contoh Use Case | Fungsi di PromQL |
|---|---|---|---|
| Counter | Hanya naik, reset ke 0 saat restart | HTTP request total, bytes sent | rate(), increase() |
| Gauge | Nilai arbitrary, bisa naik/turun | CPU usage, memory used, queue depth | langsung dipakai, atau avg_over_time() |
| Histogram | Distribusi nilai dalam bucket | Request latency, response size | histogram_quantile() |
| Summary | Mirip histogram, quantile dihitung client | SLA latency tracking | langsung quantile |
Untuk 90% use case, Counter dan Gauge cukup. Gunakan histogram saat kita membutuhkan p99 latency atau distribusi. Summary jarang digunakan karena quantile dihitung di client — kita kehilangan fleksibilitas agregasi cross-instance.
Instalasi Node Exporter #
Node Exporter adalah agen yang mengekspos metrik sistem (CPU, memory, disk, network) dalam format Prometheus. Wajib di-install di semua server production, idealnya sebagai bagian dari common role sehingga setiap server baru otomatis termonitor:
# roles/node-exporter/tasks/main.yml
---
- name: Buat user node_exporter
user:
name: node_exporter
system: true
shell: /usr/sbin/nologin
home: /var/lib/node_exporter
create_home: false
- name: Download Node Exporter
get_url:
url: >
https://github.com/prometheus/node_exporter/releases/download/
v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz
dest: /tmp/node_exporter.tar.gz
checksum: "sha256:{{ node_exporter_checksum }}"
- name: Extract Node Exporter
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp/
remote_src: true
- name: Instal binary Node Exporter
copy:
src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
dest: /usr/local/bin/node_exporter
owner: root
group: root
mode: '0755'
remote_src: true
- name: Deploy systemd unit file
template:
src: node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
notify:
- Reload systemd
- Restart node_exporter
- name: Jalankan Node Exporter
systemd:
name: node_exporter
state: started
enabled: true
{# roles/node-exporter/templates/node_exporter.service.j2 #}
[Unit]
Description=Prometheus Node Exporter
Documentation=https://prometheus.io/docs/guides/node-exporter/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--web.listen-address=:9100 \
--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc|var/lib/docker/.+)($$|/) \
--collector.netclass.ignored-devices=^(veth.*|docker.*|br-.*) \
--collector.netdev.device-exclude=^(veth.*|docker.*|br-.*) \
--no-collector.wifi
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Perhatikan flag --collector.netclass.ignored-devices=^(veth.*|docker.*|br-.*) — tanpa flag ini, metric node_network_* di server Docker akan di-bombardir metrik dari interface virtual container, menyulitkan monitoring interface fisik. Pilih collector yang kita butuhkan; default Node Exporter mengaktifkan semua collector, beberapa di antaranya noisy atau tidak relevan.
Konfigurasi Prometheus dengan Inventory-Driven Targets #
Prometheus scrape target harus bisa berubah seiring waktu — server baru ditambah, server lama di-decommission, service di-scale. Template Jinja2 yang membaca inventory otomatis menyesuaikan diri — tidak perlu diedit secara manual saat deployment:
# roles/prometheus/tasks/main.yml
---
- name: Deploy konfigurasi Prometheus
template:
src: prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
owner: prometheus
group: prometheus
mode: '0644'
validate: promtool check config %s
notify: Reload Prometheus
{# roles/prometheus/templates/prometheus.yml.j2 #}
global:
scrape_interval: {{ prometheus_scrape_interval | default('15s') }}
evaluation_interval: {{ prometheus_evaluation_interval | default('15s') }}
external_labels:
cluster: {{ cluster_name }}
environment: {{ env }}
query_log_file: /var/log/prometheus/queries.log
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
# Prometheus scraping itself
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
# Node Exporter dari semua server di inventory
- job_name: node_exporter
static_configs:
{% for host in groups['all'] %}
- targets: ['{{ hostvars[host]['ansible_default_ipv4']['address'] }}:9100']
labels:
hostname: {{ host }}
environment: {{ env }}
os: {{ hostvars[host].get('ansible_os_family', 'unknown') | lower }}
{% endfor %}
relabel_configs:
- source_labels: [__address__]
target_label: __address__
replacement: '${1}:9100'
# Application metrics jika tersedia
- job_name: {{ app_name }}
static_configs:
{% for host in groups['appservers'] %}
- targets: ['{{ hostvars[host]['ansible_default_ipv4']['address'] }}:{{ app_metrics_port | default(9090) }}']
labels:
hostname: {{ host }}
tier: application
{% endfor %}
metrics_path: /metrics
Parameter validate: promtool check config %s wajib — ini menjalankan promtool di remote host untuk validasi syntax dan struktur config sebelum file di-overwrite. Tanpa validasi, konfigurasi salah akan membuat Prometheus gagal reload dan kita akan kehilangan monitoring tanpa mengetahui penyebabnya.
Pair Anti-Pattern: Hardcoded Target vs Inventory-Driven #
{# ANTI-PATTERN: target di-hardcode di file konfigurasi, harus diedit secara manual saat server ditambahkan #}
{# roles/prometheus/templates/prometheus.yml.j2 #}
scrape_configs:
- job_name: node_exporter
static_configs:
- targets:
- '10.0.1.10:9100'
- '10.0.1.11:9100'
- '10.0.1.12:9100'
labels:
environment: production
# Masalah: setiap server baru harus dimasukkan ke file ini secara manual, lalu di-commit,
# lalu jalankan playbook. Saat auto-scaling (5 server sekaligus),
# target akan tertinggal dan server baru tidak termonitor.
{# BENAR: baca dari inventory Ansible secara dinamis #}
{# roles/prometheus/templates/prometheus.yml.j2 #}
scrape_configs:
- job_name: node_exporter
static_configs:
{% for host in groups['all'] %}
- targets: ['{{ hostvars[host]['ansible_default_ipv4']['address'] }}:9100']
labels:
hostname: {{ host }}
environment: {{ env }}
{% endfor %}
# Hasil: jalankan playbook di server baru → otomatis masuk ke target list.
# Saat auto-scaling, jalankan ulang playbook setelah instance ready.
# Tidak perlu edit file atau commit.
Alert Rules sebagai Kode #
Alert rules mendefinisikan kondisi yang memicu notifikasi. Menyimpannya sebagai file yang di-manage Ansible memastikan alert yang sama berlaku di semua environment dan bisa di-review lewat pull request sebelum di-deploy ke production:
# roles/prometheus/tasks/alert-rules.yml
---
- name: Buat direktori rules
file:
path: /etc/prometheus/rules
state: directory
owner: prometheus
mode: '0755'
- name: Deploy alert rules
template:
src: "{{ item }}"
dest: "/etc/prometheus/rules/{{ item | basename | replace('.j2', '') }}"
owner: prometheus
mode: '0644'
with_fileglob:
- "templates/rules/*.j2"
notify: Reload Prometheus
- name: Validasi syntax rules
command: promtool check rules /etc/prometheus/rules/*.yml
register: rules_check
changed_when: false
failed_when: rules_check.rc != 0
# roles/prometheus/templates/rules/system.yml.j2
---
groups:
- name: system
rules:
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > {{ alert_cpu_threshold | default(85) }}
for: 5m
labels:
severity: warning
annotations:
summary: "CPU usage tinggi di {{ '{{' }} $labels.instance {{ '}}' }}"
description: "CPU usage {{ '{{' }} $value | humanizePercentage {{ '}}' }} selama 5 menit terakhir"
runbook_url: "https://runbooks.example.com/cpu-high"
- alert: LowDiskSpace
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < {{ alert_disk_threshold | default(0.15) }}
for: 5m
labels:
severity: critical
annotations:
summary: "Disk hampir penuh di {{ '{{' }} $labels.instance {{ '}}' }}"
description: "Disk tersisa {{ '{{' }} $value | humanizePercentage {{ '}}' }} (mountpoint /)"
- alert: HostDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Host tidak responsif: {{ '{{' }} $labels.instance {{ '}}' }}"
description: "Prometheus tidak bisa scrape {{ '{{' }} $labels.instance {{ '}}' }} selama 2 menit"
- alert: HighMemoryUsage
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > {{ alert_memory_threshold | default(0.90) }}
for: 10m
labels:
severity: warning
annotations:
summary: "Memory usage tinggi di {{ '{{' }} $labels.instance {{ '}}' }}"
description: "Memory usage {{ '{{' }} $value | humanizePercentage {{ '}}' }} (threshold 90%)"
Empat alert di atas adalah baseline yang harus ada di semua environment. Tambahkan alert spesifik aplikasi sesuai kebutuhan. Penting: setiap alert yang kita buat harus memiliki runbook URL di annotation — saat alert bunyi jam 3 pagi, tim on-call harus bisa langsung mendapatkan instruksi mitigasi tanpa harus scroll Slack history.
Severity dan Routing #
Tidak semua alert sama pentingnya. Pakai label severity untuk routing di Alertmanager — critical ke PagerDuty, warning ke channel Slack, info ke email mingguan:
# roles/alertmanager/tasks/main.yml — routing config
- name: Deploy konfigurasi Alertmanager
template:
src: alertmanager.yml.j2
dest: /etc/alertmanager/alertmanager.yml
validate: amtool check-config %s
notify: Restart Alertmanager
# roles/alertmanager/templates/alertmanager.yml.j2
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'pagerduty'
group_wait: 10s
repeat_interval: 1h
- match:
severity: warning
receiver: 'slack-warn'
group_wait: 1m
repeat_interval: 4h
- match_re:
severity: ^(info|debug)$
receiver: 'null'
receivers:
- name: 'default'
slack_configs:
- channel: '#alerts-general'
send_resolved: true
- name: 'pagerduty'
pagerduty_configs:
- service_key: "{{ vault_pagerduty_service_key }}"
send_resolved: true
description: '{{ .CommonAnnotations.summary }}'
- name: 'slack-warn'
slack_configs:
- channel: '#alerts-warning'
send_resolved: true
title_link: 'https://grafana.example.com/alerting/grafana/{{ .GroupLabels.alertname }}'
- name: 'null'
Hindari alert spam. Alert yang firing setiap 5 menit membuat orang mengabaikan SEMUA alert, termasuk yang penting. Aturan praktis: setiap alert harus actionable (ada yang bisa dilakukan), rare (tidak setiap hari firing), dan specific (jelas apa yang salah). Jika alert kita mengalami firing setiap jam dan tidak ada tindakan yang diambil, threshold-nya salah atau alert-nya tidak relevan — disable atau naikkan threshold.
Setup Grafana dengan Provisioning Otomatis #
Grafana yang di-setup manual lewat UI click-click akan mengalami drift dalam hitungan minggu: orang A menambah datasource, orang B mengubah dashboard, dan tidak ada yang mengingat konfigurasi yang sebenarnya. Provisioning via file YAML memastikan datasource dan dashboard selalu sama di semua environment:
# roles/grafana/tasks/main.yml
---
- name: Install Grafana
apt:
name: "grafana={{ grafana_version }}"
state: present
- name: Deploy konfigurasi Grafana
template:
src: grafana.ini.j2
dest: /etc/grafana/grafana.ini
owner: root
group: grafana
mode: '0640'
notify: Restart Grafana
- name: Provisioning datasource Prometheus
template:
src: datasource-prometheus.yml.j2
dest: /etc/grafana/provisioning/datasources/prometheus.yml
owner: root
group: grafana
mode: '0640'
notify: Restart Grafana
- name: Provisioning dashboard otomatis
copy:
src: "files/dashboards/{{ item }}"
dest: "/etc/grafana/provisioning/dashboards/{{ item }}"
owner: root
group: grafana
mode: '0640'
with_fileglob:
- "*.json"
notify: Restart Grafana
{# templates/datasource-prometheus.yml.j2 #}
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://{{ prometheus_host }}:9090
isDefault: true
editable: false
jsonData:
timeInterval: 15s
httpMethod: POST
manageAlerts: true
prometheusType: Prometheus
# templates/dashboards/dashboards.yml — provider config
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: 'Production'
type: file
disableDeletion: true
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: true
disableDeletion: true dan allowUiUpdates: false mencegah perubahan lewat UI — semua perubahan harus lewat Ansible. Untuk dev/staging environment, set allowUiUpdates: true supaya developer bisa bereksperimen.
State Monitoring Setup yang Sehat #
Monitoring yang sudah deployed harus terus dipantau kondisinya sendiri. Jika monitoring mengalami down saat insiden terjadi, kita menjadi buta. Pakai meta-monitoring untuk memastikan monitoring-nya sendiri hidup:
stateDiagram-v2
[*] --> Setup: "Ansible deploy"
Setup --> Scrape: "Prometheus scrape targets"
Scrape --> Healthy: "semua target up"
Scrape --> Degraded: "1-2 target down"
Scrape --> Broken: ">50% target down"
Healthy --> Scrape: "scrape interval"
Degraded --> Alerting: "Alertmanager notify"
Alerting --> Scrape: "target recovered"
Broken --> [*]: "monitoring blind"
Healthy --> [*]: "planned maintenance"
Alert untuk monitoring-down itu sendiri:
# roles/prometheus/templates/rules/monitoring-self.yml.j2
---
groups:
- name: monitoring-health
rules:
- alert: PrometheusTargetDown
expr: up == 0
for: 5m
labels:
severity: warning
annotations:
summary: "Prometheus tidak bisa scrape {{ '{{' }} $labels.instance {{ '}}' }}"
description: "Job {{ '{{' }} $labels.job {{ '}}' }} target {{ '{{' }} $labels.instance {{ '}}' }} down 5 menit"
- alert: PrometheusDown
expr: absent(up{job="prometheus"})
for: 2m
labels:
severity: critical
annotations:
summary: "Prometheus sendiri tidak termonitor"
description: "Metric `up` Prometheus tidak ada — kemungkinan Prometheus crash"
- alert: AlertmanagerDown
expr: absent(alertmanager_alerts)
for: 5m
labels:
severity: critical
annotations:
summary: "Alertmanager tidak bisa dihubungi Prometheus"
- alert: TooManyTargetsDown
expr: 100 * (sum by(job) (up == 0) / count by(job) (up)) > 50
for: 5m
labels:
severity: critical
annotations:
summary: ">50% target {{ '{{' }} $labels.job {{ '}}' }} down"
description: "Monitoring blind untuk job ini"
AlertPrometheusDownadalah critical tanpa route ke receiver tertentu — kita bisa melakukannya denganreceiver: pagerdutydi Alertmanager. Namun hati-hati: jika Prometheus mati, Prometheus tidak bisa mengirim alert. Solusinya: jalankan Alertmanager secara terpisah di host yang berbeda, dan monitor Alertmanager dengan health check dari sistem eksternal (bisa juga ping dari host yang berbeda, atau pakai uptime monitoring SaaS).
Relabel Config: Pembersihan Label Otomatis #
Metric Prometheus datang dengan banyak label default yang tidak selalu kita butuhkan. relabel_configs membersihkan dan menstandarkan label sebelum disimpan ke TSDB. Contoh paling berguna: drop label yang tidak perlu dan tambahkan environment:
# Tambahkan di scrape_configs job tertentu
scrape_configs:
- job_name: node_exporter
static_configs:
- targets: ['host1:9100', 'host2:9100']
relabel_configs:
# Drop instance label asli, ganti dengan hostname
- source_labels: [__address__]
regex: '([^:]+)(:\d+)?'
target_label: hostname
replacement: '${1}'
# Drop label kosong
- action: labeldrop
regex: '__meta_kubernetes_pod_label_(?!app|name).*'
# Hanya scrape target yang punya label env
- source_labels: [__meta_ec2_tag_Environment]
regex: 'production'
action: keep
action: keep sangat berguna saat scrape target campuran dan kita hanya menginginkan sebagian. Contoh: di shared cluster dengan staging dan production, keep hanya yang punya tag Environment=production.
Decision Tree: Prometheus atau Alternatifnya? #
Pemilihan stack monitoring menentukan banyak hal downstream. Decision tree ini untuk orientasi awal:
flowchart TD
A["Butuh monitoring<br/>untuk?"] -->|"Kubernetes-native"| K["Prometheus +<br/>Grafana + Loki"]
A -->|"VM / bare metal klasik"| B{"Volume metrik<br/>per detik?"}
B -->|"< 1M"| E["Prometheus<br/>self-hosted"]
B -->|"1-10M"| F["Prometheus +<br/>Thanos / Mimir"]
B -->|"> 10M"| G["Hosted solution<br/>Datadog / Grafana Cloud"]
A -->|"Cloud-native AWS"| H["CloudWatch +<br/>managed Prometheus"]
A -->|"Compliance ketat"| I["Vendor dengan<br/>SLA & support"]
K --> J["scrape config<br/>via annotations"]
E --> L["static atau file_sd"]
F --> M["object storage<br/>S3 / GCS"]
style A stroke:#b45309,stroke-width:2px
style E stroke:#15803d,stroke-width:2px
style F stroke:#15803d,stroke-width:2px
style G stroke:#be185d,stroke-width:2px
| Kriteria | Prometheus Self-Hosted | Hosted (Datadog/Cloud) |
|---|---|---|
| Biaya per host/bulan | Infrastruktur saja (~$5-20) | $15-30 per host |
| Custom metric | Gratis, unlimited | Berbayar per metric |
| Konfigurasi | YAML di Git (as code) | UI / API, drift risk |
| Operasi | Tim perlu expertise Prometheus | Vendor handle scaling |
| Lock-in | Rendah (format terbuka) | Tinggi |
| Best for | Tim DevOps/SRE yang sudah pakai K8s | Tim kecil tanpa dedicated ops |
Pair Anti-Pattern: Reload Tanpa Validasi vs Dengan Validasi #
# ANTI-PATTERN: copy file config langsung tanpa validasi
- name: Deploy konfigurasi Prometheus
copy:
src: prometheus.yml
dest: /etc/prometheus/prometheus.yml
notify: Reload Prometheus
# Masalah: jika terdapat typo atau kesalahan struktur, Prometheus akan gagal melakukan reload.
# Monitoring down tanpa bunyi alert — karena monitoring-nya yang down.
# Kita baru menyadarinya saat ada yang melaporkan "dashboard Grafana kosong" beberapa waktu kemudian.
# BENAR: validasi syntax sebelum file di-overwrite
- name: Deploy konfigurasi Prometheus
template:
src: prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
validate: promtool check config %s # ✓ promtool jalankan di remote, return 0 jika OK
notify: Reload Prometheus
# Hasil: kalau config salah, task fail sebelum file ditulis. Prometheus
# tetap pakai config lama yang valid. Kita mendapatkan pesan error yang jelas
# di Ansible output: baris mana, field mana yang salah.
promtool adalah CLI resmi Prometheus untuk validasi config. Install sebagai dependency role:
# roles/prometheus/tasks/dependencies.yml
- name: Download promtool
get_url:
url: "https://github.com/prometheus/prometheus/releases/download/v{{ prometheus_version }}/prometheus-{{ prometheus_version }}.linux-amd64.tar.gz"
dest: /tmp/prometheus.tar.gz
- name: Install promtool binary
unarchive:
src: /tmp/prometheus.tar.gz
dest: /usr/local/bin/
include: ['promtool']
extra_opts: ['--strip-components=1']
remote_src: true
creates: /usr/local/bin/promtool
Pair Anti-Pattern: Threshold Sama untuk Semua Host vs Threshold Berbasis Role #
# ANTI-PATTERN: threshold tunggal untuk semua server
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
# Masalah: database server yang memang CPU-intensive untuk query legitimate
# akan memicu alert setiap 5 menit. Alert menjadi noise dan cenderung diabaikan.
# BENAR: threshold berbeda per role
- alert: HighCPUUsageDatabase
expr: |
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 10m
labels:
role: database
annotations:
summary: "DB server CPU tinggi: {{ '{{' }} $labels.instance {{ '}}' }}"
- alert: HighCPUUsageApp
expr: |
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 75
for: 5m
labels:
role: application
annotations:
summary: "App server CPU tinggi: {{ '{{' }} $labels.instance {{ '}}' }}"
# Hasil: alert specific ke role, threshold realistis untuk workload
# masing-masing. Alert untuk DB firing di 90% (sangat tinggi), untuk
# app di 75% (lebih ketat karena app biasanya tidak CPU-bound).
Pendekatan yang lebih scalable: pisahkan alert definition ke file terpisah per role (rules/database.yml, rules/application.yml, rules/worker.yml) dan load via rule_files glob di config utama. Threshold bisa dibedakan, dan saat add service baru cukup tambah file.
Menghubungkan Monitoring dengan Logging #
Monitoring memberitahu “CPU tinggi di host X”. Logging memberitahu “kenapa CPU tinggi di host X”. Keduanya paling powerful saat digabung di dashboard yang sama. /observability/logging/ sudah menjelaskan setup Loki; di sini kita akan melihat cara menyatukan label:
# roles/prometheus/tasks/integration.yml
---
- name: Pastikan label Prometheus konsisten dengan Loki
lineinfile:
path: /etc/prometheus/prometheus.yml
regexp: '^ environment:'
line: " environment: {{ env }}"
insertbefore: '^scrape_configs:'
notify: Reload Prometheus
Grafana bisa menampilkan panel log (Loki) dan panel metric (Prometheus) dalam satu dashboard. Cukup klik titik anomali pada grafik CPU untuk langsung melihat log error di host yang sama dengan filter otomatis.
Ringkasan #
- Node Exporter di setiap managed node mengekspos metrik sistem — instal ini di semua server sebagai bagian dari role
common, jangan sebagai langkah terpisah.- Template konfigurasi Prometheus menggunakan
hostvarsdangroupsuntuk otomatis menambahkan semua server di inventory sebagai scrape target — tidak perlu diedit secara manual saat server baru ditambahkan.validate: promtool check config %suntuk memvalidasi konfigurasi Prometheus sebelum disimpan — konfigurasi yang salah bisa membuat Prometheus gagal reload tanpa bunyi alert.- Alert rules sebagai file template yang di-manage Ansible adalah monitoring as code — review di Git, konsisten di semua environment, dan bisa di-rollback via
git revert.- Grafana provisioning via file YAML memungkinkan datasource dan dashboard dikonfigurasi otomatis saat Grafana di-restart — tidak perlu klik manual di UI yang rentan drift.
- Pin versi Prometheus, Node Exporter, dan Grafana di
defaults/main.yml— update minor yang tidak terkontrol bisa membreak dashboard atau mengubah format metrik.- Alert harus punya
runbook_urldi annotation — saat alert bunyi, orang on-call butuh link langsung ke instruksi mitigasi, bukan harus scroll Slack history.- Monitor monitoring itu sendiri: alert
PrometheusDown,AlertmanagerDown, danTooManyTargetsDownmemastikan kita mengetahui saat terjadi blind spot.