Bangun monitoring dan observability stack secara otomatis dan reproducible: deployment Prometheus dan Alertmanager, Node Exporter, Grafana dengan provisioned dashboards, hingga logging stack ELK dan Loki/Promtail menggunakan Ansible.

Setelah di episode 24 sebelumnya kita membahas database management — mengelola PostgreSQL, MySQL/MariaDB, dan MongoDB secara otomatis — pada episode kali ini kita akan membahas satu hal yang justru sering dilupakan oleh banyak tim: bagaimana kita tahu bahwa semua yang sudah kita bangun masih bekerja dengan baik?
Ada pepatah klasik di dunia engineering: "You can't manage what you can't measure" — kalian tidak bisa mengelola apa yang tidak bisa kalian ukur. Sebuah server bisa saja "normal-normal saja" di mata SSH, padahal CPU-nya sudah 95%, disk penuh, dan aplikasi mulai melambat. Masalah itu baru terasa ketika pengguna mengeluh. Padahal, jika kita punya sistem monitoring yang baik, masalah tersebut sudah terdeteksi sejak 10 menit pertama — bahkan sebelum pengguna merasakannya.
Sekarang bayangkan tim yang mengelola 50 server di tiga environment (dev, staging, production), semuanya di-provisioning dengan Ansible. Tanpa otomasi, memasang Node Exporter di 50 server berarti SSH ke 50 server. Mengubah prometheus.yml berarti mengedit dan me-restart di 1 server, lalu berharap semua target baru masuk. Itu tidak scalable, dan cepat atau lambat akan menimbulkan configuration drift — server satu punya Node Exporter versi lama, server lain tidak diekspos ke Prometheus, dan seterusnya.
Inilah kenapa monitoring harus diperlakukan seperti infrastruktur: kode, versioned di Git, diuji, dan diterapkan dengan Ansible. Konsep ini sering disebut observability as code. Pada episode ini kita akan membahas deployment stack Prometheus + Alertmanager + Grafana + Node Exporter, templating prometheus.yml dengan dynamic targets, pengelolaan alert rules, Grafana dashboard as code, serta logging stack ELK (Elasticsearch, Logstash, Kibana) dan alternatif ringannya: Loki + Promtail.
Sebelum masuk ke kode, mari kita pahami dulu masing-masing komponen dan perannya:
| Komponen | Fungsi | Port Default | Sumber di Ansible |
|---|---|---|---|
| Prometheus | Scraping & penyimpanan metric time-series | 9090 | collection prometheus.prometheus |
| Alertmanager | Deduplikasi & routing alert | 9093 | collection prometheus.prometheus |
| Grafana | Visualisasi metric & dashboard | 3000 | collection community.grafana |
| Node Exporter | Metric sistem Linux (CPU, memori, disk) | 9100 | collection prometheus.prometheus |
| Elasticsearch | Penyimpanan & pencarian log | 9200 | collection elastic.elasticsearch |
| Logstash | Pipeline pengolahan & transformasi log | 5044 | collection elastic.elasticsearch |
| Kibana | Visualisasi log & dashboard ELK | 5601 | collection elastic.elasticsearch |
| Filebeat | Agent pengiriman log ke Elasticsearch/Logstash | — | collection elastic.beats |
| Loki | Penyimpanan log ringan (mirror Prometheus untuk log) | 3100 | install manual / community.grafana |
| Promtail | Agent pengiriman log ke Loki | 9080 | install manual |
Pembagian perannya sederhana: Prometheus + Grafana menjawab pertanyaan "apakah sistem sehat?" (metric), sedangkan ELK / Loki menjawab pertanyaan "kenapa sistem bermasalah?" (log). Keduanya harus ada — metric memberi tahu kalian apa yang salah, log memberi tahu kalian kenapa.
Install collection yang diperlukan:
ansible-galaxy collection install prometheus.prometheus community.grafanaDaripada menulis playbook raksasa, kita pecah ke dalam roles agar bisa di-reuse — persis pola yang kita pelajari di episode 12. Struktur proyeknya:
ansible-monitoring/
├── inventory/
│ └── production.yml
├── group_vars/
│ ├── monitoring.yml
│ └── all.yml
├── playbook-monitoring.yml
└── roles/
├── prometheus/
│ ├── tasks/main.yml
│ ├── handlers/main.yml
│ └── templates/
│ ├── prometheus.yml.j2
│ ├── alertmanager.yml.j2
│ └── alert.rules.yml.j2
├── alertmanager/
│ └── tasks/main.yml
└── node_exporter/
└── tasks/main.ymlPlaybook utamanya sangat ringkas — bukti bahwa modularitas bekerja:
- name: Deploy stack monitoring ke server monitoring
hosts: monitoring_servers
become: true
roles:
- role: prometheus
- role: alertmanager
- name: Deploy Node Exporter ke seluruh node terkelola
hosts: all
become: true
roles:
- role: node_exporterPerhatikan pola pentingnya: role prometheus dan alertmanager hanya dijalankan di host grup monitoring_servers, sedangkan role node_exporter dijalankan ke semua host dalam inventory. Dengan begini, menambahkan server baru ke inventory cukup berarti Node Exporter akan otomatis terpasang di run berikutnya — tanpa SSH manual.
prometheus.yml dengan Dynamic TargetsIni adalah bagian inti dari observability as code: file konfigurasi Prometheus dibuat dari template Jinja2 sehingga target scrape tidak di-hardcode. Target diambil langsung dari inventory Ansible — satu sumber kebenaran yang sama untuk seluruh infrastruktur.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/rules/*.rules.yml"
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
labels:
env: "{{ env_name }}"
- job_name: "node"
static_configs:
- targets:
{% for host in groups['nodes'] %}
- "{{ hostvars[host].ansible_host | default(host) }}:9100"
{% endfor %}
labels:
env: "{{ env_name }}"Mari kita bedah logika Jinja2 di atas:
groups['nodes'] — daftar semua host di dalam grup inventory nodes. Prometheus tidak perlu tahu IP satu per satu; cukup referensikan grup inventory.hostvars[host].ansible_host | default(host) — mengambil alamat IP tiap host dari fact yang dikumpulkan Ansible, dengan fallback ke nama host jika ansible_host tidak ada. Inilah mengapa gather_facts penting di playbook ini.{% for %} loop — memetakan satu host inventory menjadi satu baris - "ip:9100".Task yang merender template ini otomatis me-restart Prometheus lewat handler ketika ada perubahan:
- name: Render konfigurasi Prometheus
ansible.builtin.template:
src: prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
owner: prometheus
group: prometheus
mode: "0640"
notify: restart prometheus
- name: Render alert rules
ansible.builtin.template:
src: alert.rules.yml.j2
dest: /etc/prometheus/rules/alert.rules.yml
owner: prometheus
group: prometheus
mode: "0640"
notify: reload prometheusTip
Perhatikan perbedaan notify di atas: perubahan prometheus.yml memicu restart, sedangkan perubahan alert rules cukup memicu reload (SIGHUP). Prometheus mendukung reload konfigurasi tanpa downtime — memanfaatkan detail ini adalah bagian dari operasional yang baik.
Alert rules adalah aturan yang menentukan kapan Prometheus "berteriak". Karena berupa file, aturan ini bisa di-versioning, di-review di Pull Request, dan diterapkan secara konsisten:
groups:
- name: node-alerts
rules:
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
annotations:
summary: "CPU usage di atas 85% pada {{ $labels.instance }}"
- alert: NodeDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Node {{ $labels.instance }} tidak merespons scrape"Kenapa for: 10m itu penting? Karena flapping — CPU yang naik 90% selama 2 detik lalu turun tidak perlu membangunkan engineer jam 3 pagi. Parameter for memastikan kondisi harus bertahan selama durasi tersebut sebelum alert benar-benar dikirim. Ini adalah salah satu cara mencegah alert fatigue, yaitu kondisi di mana tim mengabaikan alert karena terlalu banyak alarm palsu.
Alertmanager bertugas menerima semua alert dari Prometheus, lalu memutuskan ke mana alert dikirim — email, Slack, Telegram, atau webhook. Templatenya:
global:
smtp_smarthost: "{{ smtp_host }}:587"
smtp_from: "{{ alert_email_from }}"
smtp_auth_username: "{{ smtp_username }}"
smtp_auth_password: "{{ vault_smtp_password }}"
route:
group_by: ["alertname", "instance"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: "ops-team"
receivers:
- name: "ops-team"
email_configs:
- to: "{{ alert_email_to }}"
send_resolved: true
slack_configs:
- api_url: "{{ vault_slack_webhook }}"
channel: "#ops-alerts"Tiga parameter routing yang wajib dipahami:
group_wait — berapa lama menunggu sebelum mengirim batch alert pertama. Membantu mengelompokkan alert yang datang bersamaan (misal satu server mati → banyak metric up == 0).group_interval — interval pengecekan grup untuk melihat apakah ada alert baru yang masuk ke grup yang sama.repeat_interval — seberapa sering alert yang belum resolve dikirim ulang. 4h berarti pager tim tidak berbunyi terus-menerus untuk masalah yang sama.Warning
Webhook Slack dan password SMTP di atas diambil dari variabel Ansible Vault (vault_slack_webhook, vault_smtp_password). Jangan pernah menaruh webhook atau kredensial notifikasi langsung di template — file ini akan tercetak di log playbook saat mode verbose dan ikut ter-versioning di Git.
Grafana adalah wajah dari seluruh stack ini. Di sini berlaku prinsip dashboard as code: dashboard bukan lagi benda yang dibuat klik-menyodorkan di UI, melainkan file JSON yang versioned di Git. Dengan begitu, perubahan dashboard bisa di-review, diuji, dan diterapkan konsisten di semua environment.
Ada dua cara mengelola Grafana dengan Ansible: lewat provisioning files (disimpan di /etc/grafana/provisioning/) atau lewat modul API community.grafana. Keduanya sering digabung — mari kita lihat keduanya sekaligus:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://127.0.0.1:9090
isDefault: true
editable: falsePendekatan provisioning file (apiVersion: 1) adalah cara yang direkomendasikan Grafana untuk deployment otomatis: folder JSON dashboard tinggal ditaruh di dalam path yang terdaftar, dan Grafana akan memuatnya. Sedangkan modul grafana_dashboard memanfaatkan REST API Grafana — berguna untuk mengimpor dashboard dari library Grafana.com atau memperbarui dashboard yang sudah ada secara programatik. Pilih sesuai kebutuhan: provisioning file untuk dashboard milik sendiri (repo), modul API untuk dashboard komunitas.
Stack monitoring berbasis metric belum cukup — ketika aplikasi error, kalian butuh log untuk root cause analysis. Stack paling klasik adalah ELK. Deploy-nya dengan Ansible mengikuti pola yang sama: Elasticsearch sebagai penyimpan, Logstash sebagai pipeline, Kibana sebagai visualisasi, dan Filebeat sebagai agent di setiap node.
Berikut contoh konfigurasi Filebeat yang di-template agar mengirim log aplikasi ke Logstash:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/*.log
- /var/log/app/*.log
fields:
service: "{{ service_name }}"
fields_under_root: true
output.logstash:
hosts: ["{{ logstash_host }}:5044"]
logging.level: info- name: Deploy Filebeat ke node aplikasi
hosts: app_servers
become: true
vars:
service_name: web-app
logstash_host: logstash.internal
tasks:
- name: Render konfigurasi Filebeat
ansible.builtin.template:
src: filebeat.yml.j2
dest: /etc/filebeat/filebeat.yml
owner: root
group: root
mode: "0600"
notify: restart filebeat
- name: Mulai dan aktifkan Filebeat
ansible.builtin.systemd_service:
name: filebeat
state: started
enabled: trueNote
ELK Stack sangat powerful, tetapi boros resource — Elasticsearch butuh banyak RAM, dan tiga komponen (ES + Logstash + Kibana) harus dikelola bersama. Untuk tim kecil atau volume log menengah, banyak yang beralih ke Loki karena satu binary ringan dan terintegrasi langsung dengan Grafana yang sudah ada.
Loki dirancang mengikuti filosofi Prometheus: labels sebagai indeks utama, simpan log apa adanya. Alih-alih membuat indeks penuh seperti Elasticsearch, Loki mengindeks label log dan membiarkan isi log di-compress — jauh lebih hemat resource. Pasangannya, Promtail, adalah agent yang mengirim log dari node ke Loki. Kedua konfigurasi ini bisa kita kelola sebagai file templated:
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13Perhatikan label job: nginx, job: varlogs — ini yang membuat pencarian log di Loki cepat: query seperti {job="nginx"} |= "ERROR" hanya memindai label tersebut, bukan seluruh isi log. Kemudahan ini lagi-lagi mengajarkan prinsip yang sama: konfigurasi (label, path, target) adalah kode, bukan barang yang di-edit manual per server.
1. Target Prometheus di-hardcode
Menulis IP server satu per satu di prometheus.yml berarti menambahkan server baru = edit file manual. Ini membuat drift cepat terjadi. Gunakan templating dengan groups['nodes'] seperti di atas, atau lebih lanjut lagi: file_sd_configs/http_sd_configs yang digenerate Ansible.
2. Alert tidak pernah ter-evaluasi karena evaluation_interval
evaluation_interval menentukan seberapa sering Prometheus mengevaluasi rule files. Kalau nilainya terlalu besar (misal 1m padahal for: 10m dan kondisi cepat), alert datang telat. Standarnya 15s — cukup untuk sebagian besar kasus.
3. Alert rules YAML tidak valid
Satu kesalahan indentasi di alert.rules.yml dan Prometheus menolak memuat semua rules — alert diam-diam mati. Biasakan menambahkan task validasi, misalnya promtool check rules /etc/prometheus/rules/, sebelum restart/reload.
4. API key Grafana plaintext
API key Grafana yang dibutuhkan modul grafana_dashboard seharusnya tinggal di Ansible Vault. Jangan sampai ter-commit ke Git — kalian tahu konsekuensinya dari episode 14.
5. Node Exporter terekspos ke publik
Port 9100 seharusnya hanya bisa diakses dari server Prometheus. Ekspos ke internet = informasi detail server (kernel version, daftar disk, suhu) bocor ke siapa saja. Batasi dengan firewall/U FW (kita bahas hardening di episode 27).
6. Promtail tidak punya izin baca log
Promtail sering gagal diam-diam membaca /var/log karena grupnya tidak punya akses. Pastikan user Promtail masuk grup adm (Debian/Ubuntu) atau konfigurasikan izin yang benar.
7. Lupa merencanakan kapasitas disk Prometheus
Retention default Prometheus menyimpan 15 hari data. Dengan jumlah target dan metric cardinality tinggi, disk bisa penuh dalam hitungan hari. Konfigurasikan retention.time dan monitor disk volume-nya — ironisnya, stack monitoring paling sering kena alert disk full miliknya sendiri.
Pada episode ini kita telah membangun fondasi observability as code dengan Ansible: deployment Prometheus dan Alertmanager, Node Exporter yang otomatis menyebar ke seluruh node, templating prometheus.yml dengan dynamic targets dari inventory, alert rules dan Alertmanager routing sebagai kode, Grafana dengan datasource dan dashboard yang di-provision, serta logging stack ELK dan alternatif ringannya Loki + Promtail.
Prinsip yang menyatukan semuanya: monitoring adalah infrastruktur. Ia harus dikelola dengan cara yang sama — versioned, di-review, idempotent, dan diterapkan otomatis. Dengan begitu, menambah server baru bukan berarti menambah pekerjaan manual, melainkan cukup menambahkan satu baris di inventory.
Di episode 26 selanjutnya, kita akan membawa seluruh skill ini ke level berikutnya: Cloud Infrastructure Provisioning — mengelola EC2, VPC, S3, dan IAM di AWS; Compute Engine dan GCS di Google Cloud; hingga Virtual Machines dan Virtual Networks di Azure — lengkap dengan pola multi-cloud. Pastikan tetap semangat!