Studi Kasus: Stack Observabilitas

Studi Kasus: Stack Observabilitas

Bitnesia Sep 13, 2026 11 EN

Aplikasi yang sudah berjalan stabil di production tetap butuh jawaban cepat atas tiga pertanyaan sehari-hari: seberapa sehat resource yang dipakai container saat ini, kenapa request tertentu tiba-tiba lambat, dan di baris log mana persisnya sebuah error terjadi. Mengandalkan docker stats atau docker logs satu per satu masih bisa dipakai untuk debugging cepat, tapi jelas tidak praktis begitu jumlah container bertambah jadi puluhan atau saat insiden butuh korelasi antara metrik dan log dalam rentang waktu yang sama. Bab ini membangun satu stack observabilitas lengkap berbasis Docker Compose, yaitu kombinasi tooling untuk mengamati kondisi sistem secara menyeluruh, memakai Prometheus untuk mengumpulkan metrik, Grafana untuk visualisasi dan dashboard, serta Loki untuk log terpusat, lalu menghubungkan ketiganya dengan aplikasi container yang sudah dibahas pada studi kasus-studi kasus sebelumnya.

34.1 Setup Prometheus untuk Metrics Collection

Prometheus adalah monitoring system dan time series database open-source yang mengumpulkan metrik dengan cara menarik data secara berkala dari target yang sudah didaftarkan, pola yang disebut pull-based scraping. Setiap target cukup menyediakan satu endpoint HTTP berisi metrik dalam format teks yang sudah distandarkan, dan Prometheus yang mengunjungi endpoint tersebut sesuai interval yang dikonfigurasi, kebalikan dari pola push-based yang mengharuskan aplikasi aktif mengirim data ke server monitoring.

34.1.1 Arsitektur Stack Observabilitas

Stack pada bab ini terdiri dari enam service yang saling melengkapi. Service prometheus bertugas menyimpan dan menyediakan metrik lewat query. Service cadvisor (singkatan dari Container Advisor) mengekspos metrik penggunaan CPU, memori, network, dan disk per container. Service node-exporter mengekspos metrik level host, seperti penggunaan CPU dan memori mesin secara keseluruhan. Service grafana menampilkan seluruh metrik tersebut dalam bentuk dashboard visual. Service loki menyimpan log terpusat dari seluruh container, sementara alloy bertugas mengumpulkan log dari tiap container dan mengirimkannya ke Loki. Sysadmin/DevOps Engineer biasanya menempatkan seluruh service ini dalam satu compose.yaml terpisah dari aplikasi utama, supaya stack observabilitas bisa dikelola dan di-upgrade independen dari siklus deployment aplikasi yang diawasinya.

Susun kerangka compose.yaml dengan service prometheus, cadvisor, dan node-exporter terlebih dahulu sebagai fondasi pengumpulan metrik.

services:
  prometheus:
    image: prom/prometheus:v3.13.3
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - observability-net

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.56.2
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    networks:
      - observability-net

  node-exporter:
    image: prom/node-exporter:v1.12.1
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"
      - "--path.rootfs=/rootfs"
    networks:
      - observability-net

networks:
  observability-net:

volumes:
  prometheus-data:

Konfigurasi cadvisor dan node-exporter sama-sama meminjam sejumlah path dari filesystem host lewat bind mount mode ro (read-only), karena keduanya memang perlu membaca informasi dari kernel dan runtime Docker di level host untuk bisa mengukur pemakaian resource secara akurat. Mode read-only ini penting sebagai lapisan keamanan tambahan, supaya kedua container ini tidak bisa menulis apapun ke filesystem host meski punya akses baca yang luas.

34.1.2 Konfigurasi prometheus.yml dan Scrape Target

File prometheus.yml mendefinisikan target mana saja yang harus di-scrape Prometheus beserta intervalnya, sesuai format konfigurasi resmi Prometheus.

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "cadvisor"
    static_configs:
      - targets: ["cadvisor:8080"]

  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "api"
    metrics_path: /metrics
    static_configs:
      - targets: ["api:3000"]

job_name di sini bebas dipakai sebagai label untuk membedakan asal metrik saat query, sementara targets memakai nama service Compose karena Prometheus berjalan dalam network internal yang sama dan bisa me-resolve nama service lewat DNS bawaan Docker, pola yang sama seperti komunikasi antar-container pada studi kasus sebelumnya. Job api pada contoh ini mengasumsikan aplikasi backend sudah mengekspos endpoint /metrics sendiri lewat instrumentasi library client Prometheus di sisi kode aplikasi.

Restart service prometheus setelah mengubah prometheus.yml, lalu buka antarmuka web Prometheus untuk memverifikasi status tiap target.

docker compose up -d prometheus
curl -s http://localhost:9090/-/healthy

Akses http://localhost:9090/targets dari browser untuk melihat status UP atau DOWN tiap target yang didefinisikan. Target berstatus DOWN biasanya menandakan Prometheus belum bisa menjangkau endpoint tersebut, entah karena nama service salah ketik, port belum sesuai, atau container target belum sepenuhnya siap menerima koneksi.

34.2 Grafana untuk Visualisasi dan Dashboard

Grafana adalah platform visualisasi open-source yang membaca data dari berbagai data source, termasuk Prometheus dan Loki, lalu menampilkannya sebagai dashboard interaktif berisi grafik, tabel, dan panel lainnya. Tambahkan service grafana ke dalam compose.yaml yang sama.

34.2.1 Setup Grafana dan Koneksi Data Source

services:
  grafana:
    image: grafana/grafana:13.2.1
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
    networks:
      - observability-net

Variabel GF_SECURITY_ADMIN_PASSWORD menimpa password admin default Grafana, sesuai dokumentasi resmi konfigurasi Grafana lewat environment variable, dan sebaiknya diisi lewat file .env yang tidak ikut masuk ke version control, sama seperti pola pengelolaan kredensial pada studi kasus lain. Volume grafana-data menjaga dashboard, user, dan pengaturan lain tetap ada meski container Grafana dihapus dan dibuat ulang.

Daripada menambahkan data source Prometheus secara manual lewat antarmuka web setiap kali stack dibangun ulang, manfaatkan provisioning, yaitu mekanisme Grafana membaca konfigurasi data source dan dashboard dari file YAML saat startup. Buat grafana/provisioning/datasources/prometheus.yaml.

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true

Buat juga grafana/provisioning/datasources/loki.yaml untuk data source Loki yang dibahas pada bagian logging.

apiVersion: 1

datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100

34.2.2 Provisioning Dashboard Otomatis

Grafana juga bisa memuat file dashboard JSON secara otomatis lewat provisioning, sehingga dashboard tidak hilang begitu volume Grafana direset. Buat grafana/provisioning/dashboards/default.yaml untuk mendaftarkan folder tempat file dashboard disimpan.

apiVersion: 1

providers:
  - name: "default"
    folder: ""
    type: file
    options:
      path: /etc/grafana/provisioning/dashboards/json

Taruh file dashboard JSON hasil export dari Grafana, atau dashboard komunitas yang diunduh dari Grafana Dashboards Marketplace (misalnya dashboard resmi cAdvisor atau Node Exporter Full), ke dalam folder grafana/provisioning/dashboards/json. Restart service grafana agar dashboard tersebut otomatis muncul.

docker compose up -d grafana

Login ke Grafana lewat http://localhost:3001 memakai user admin dan password sesuai GRAFANA_ADMIN_PASSWORD, lalu periksa menu Connections > Data sources untuk memastikan Prometheus dan Loki sudah terdaftar dengan status koneksi yang sukses tanpa perlu konfigurasi manual apapun.

34.3 Loki untuk Centralized Logging

Loki adalah sistem log aggregation dari Grafana Labs yang dirancang khusus untuk efisien menyimpan log dalam jumlah besar, dengan pendekatan mengindeks label metadata saja alih-alih mengindeks seluruh isi teks log seperti pada Elasticsearch. Pendekatan ini membuat Loki jauh lebih ringan dari sisi resource, cocok dipakai Sysadmin/DevOps Engineer yang ingin sentralisasi log tanpa harus mengoperasikan cluster pencarian teks yang berat.

34.3.1 Setup Loki dan Grafana Alloy

Pengumpulan log dari container ke Loki dilakukan lewat Grafana Alloy, yaitu collector resmi Grafana Labs yang menggantikan Promtail sebagai agen pengumpul log. Promtail sudah dinyatakan deprecated sejak Loki versi 3.0 dan kodenya sepenuhnya digabung ke Alloy, sesuai catatan rilis resmi Loki, sehingga Alloy adalah pilihan yang tepat untuk stack yang dibangun saat ini. Tambahkan service loki dan alloy ke dalam compose.yaml.

services:
  loki:
    image: grafana/loki:3.7.7
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml:ro
      - loki-data:/loki
    networks:
      - observability-net

  alloy:
    image: grafana/alloy:1.19.2
    ports:
      - "12345:12345"
    volumes:
      - ./config.alloy:/etc/alloy/config.alloy:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - run
      - --server.http.listen-addr=0.0.0.0:12345
      - --storage.path=/var/lib/alloy/data
      - /etc/alloy/config.alloy
    networks:
      - observability-net

Alloy perlu akses ke docker.sock supaya bisa membaca metadata container lewat Docker API, sekaligus menarik stream log tiap container langsung dari Docker daemon, tanpa harus membaca file JSON log mentah dari filesystem host seperti pendekatan lama. Port 12345 yang dipetakan ke host menyediakan antarmuka web debugging bawaan Alloy, tempat memantau status tiap komponen pipeline secara real-time.

Buat config.alloy berisi definisi pipeline pengumpulan log, ditulis dalam sintaks deklaratif berbasis komponen milik Alloy.

discovery.docker "containers" {
    host             = "unix:///var/run/docker.sock"
    refresh_interval = "5s"
}

discovery.relabel "containers" {
    targets = discovery.docker.containers.targets

    rule {
        source_labels = ["__meta_docker_container_name"]
        regex         = "/(.*)"
        target_label  = "container"
    }
}

loki.source.docker "containers" {
    host          = "unix:///var/run/docker.sock"
    targets       = discovery.docker.containers.targets
    relabel_rules = discovery.relabel.containers.rules
    forward_to    = [loki.process.containers.receiver]
}

loki.process "containers" {
    stage.docker {}

    forward_to = [loki.write.default.receiver]
}

loki.write "default" {
    endpoint {
        url = "http://loki:3100/loki/api/v1/push"
    }
}

Komponen discovery.docker mendeteksi seluruh container yang berjalan di host lewat Docker API, otomatis mengenali container baru begitu dijalankan tanpa perlu mendaftarkan target secara manual satu per satu. Komponen discovery.relabel mengambil nama container dari label metadata __meta_docker_container_name dan menjadikannya label container, memudahkan proses filter log berdasarkan nama container saat query di Grafana nanti. Komponen loki.source.docker menarik stream log dari tiap container hasil deteksi tersebut, lalu meneruskannya ke loki.process yang menerapkan stage.docker untuk mengurai format JSON log bawaan Docker menjadi baris teks log yang bersih. Komponen terakhir, loki.write, mengirim hasil akhirnya ke endpoint push Loki.

Buat juga loki-config.yaml dengan konfigurasi dasar penyimpanan lokal, cukup memadai untuk kebutuhan single-node.

auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

34.3.2 Docker Logging Driver ke Loki (Opsional)

Selain lewat Alloy yang menarik log dari Docker daemon secara terpusat, Docker juga menyediakan Loki logging driver resmi berupa plugin yang mengirim log satu container langsung ke Loki tanpa perantara Docker API, cocok dipakai kalau ingin mengurangi satu komponen (Alloy) dari stack. Instal plugin-nya terlebih dahulu di host Docker.

docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions

Tag latest sengaja dipakai di sini, bukan mengikuti kebiasaan menghindari latest pada image biasa, karena rilis plugin ini didistribusikan per-arsitektur (misalnya amd64 dan arm64) alih-alih satu tag angka versi tunggal yang seragam, sesuai daftar tag resminya di Docker Hub. Tag latest tetap jadi cara paling aman untuk memastikan plugin yang terpasang cocok dengan arsitektur host tanpa perlu menebak nama tag spesifik-arsitektur secara manual.

Setelah plugin terpasang, konfigurasikan logging driver pada service aplikasi yang ingin log-nya dikirim langsung ke Loki.

services:
  api:
    build: ./api
    logging:
      driver: loki
      options:
        loki-url: "http://localhost:3100/loki/api/v1/push"
        loki-retries: "3"
        loki-batch-size: "400"

Perlu dicatat, pendekatan logging driver ini cuma menangkap log dari container yang secara eksplisit dikonfigurasi memakainya, berbeda dari Alloy yang otomatis menangkap log seluruh container lewat service discovery. Kedua pendekatan sama-sama valid; pilih Alloy kalau ingin satu titik konfigurasi terpusat untuk seluruh container, atau logging driver kalau cuma sebagian container tertentu yang perlu dikirim ke Loki.

34.4 Integrasi Monitoring dengan Aplikasi Container

Metrik infrastruktur dari cAdvisor dan node-exporter memang berguna, tapi Developer maupun Sysadmin/DevOps Engineer di lapangan biasanya lebih membutuhkan metrik level aplikasi, seperti jumlah request per detik, latency tiap endpoint, atau jumlah error yang terjadi, karena metrik semacam ini jauh lebih cepat menunjukkan akar masalah dibanding sekadar mengetahui CPU container sedang tinggi.

34.4.1 Instrumentasi Aplikasi dengan Metrics Endpoint

Tambahkan library client Prometheus resmi sesuai bahasa yang dipakai backend, misalnya prom-client untuk Node.js, untuk mengekspos endpoint /metrics yang sudah dikonfigurasi pada prometheus.yml di bagian sebelumnya.

const client = require("prom-client");
const express = require("express");

const app = express();
const register = new client.Registry();
client.collectDefaultMetrics({ register });

const httpRequestDuration = new client.Histogram({
  name: "http_request_duration_seconds",
  help: "Durasi request HTTP dalam detik",
  labelNames: ["method", "route", "status_code"],
});
register.registerMetric(httpRequestDuration);

app.get("/metrics", async (req, res) => {
  res.set("Content-Type", register.contentType);
  res.end(await register.metrics());
});

app.listen(3000);

collectDefaultMetrics otomatis mengekspos metrik standar seperti penggunaan memori proses Node.js dan durasi garbage collection, sementara metrik http_request_duration_seconds dibuat khusus untuk mengukur durasi tiap request berdasarkan label method, route, dan status_code, sesuai konvensi penamaan metrik yang direkomendasikan dokumentasi resmi Prometheus. Sambungkan port container api ke network observability-net supaya bisa dijangkau Prometheus, tanpa perlu mengekspos port tersebut ke host.

34.4.2 Alerting dengan Alertmanager

Sekadar melihat dashboard tidak cukup kalau tidak ada seseorang yang mengawasinya setiap saat. Alertmanager menangani notifikasi berdasarkan aturan alert yang dievaluasi Prometheus, mengirimkannya ke saluran seperti email, Slack, atau webhook lain. Tambahkan service alertmanager ke dalam stack.

services:
  alertmanager:
    image: prom/alertmanager:v0.34.0
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
    ports:
      - "9093:9093"
    networks:
      - observability-net

Definisikan aturan alert di file terpisah, misalnya alert-rules.yml, lalu daftarkan lewat opsi rule_files pada prometheus.yml.

groups:
  - name: container-alerts
    rules:
      - alert: ContainerHighCPU
        expr: rate(container_cpu_usage_seconds_total{name!=""}[5m]) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Container {{ $labels.name }} memakai CPU tinggi"
          description: "Rata-rata pemakaian CPU container di atas 80% selama 5 menit terakhir."
rule_files:
  - "alert-rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

Aturan ContainerHighCPU ini memanfaatkan metrik container_cpu_usage_seconds_total yang berasal dari cAdvisor, memicu alert kalau rata-rata pemakaian CPU sebuah container di atas 80% selama minimal 5 menit berturut-turut, bukan cuma sekali lonjakan sesaat yang wajar terjadi. Klausa for: 5m ini penting supaya alert tidak terlalu sensitif terhadap fluktuasi normal, praktik yang lazim diterapkan Sysadmin/DevOps Engineer untuk mengurangi alert fatigue, yaitu kondisi tim jadi kebal atau mengabaikan notifikasi karena terlalu sering menerima alert yang ternyata tidak signifikan.

34.4.3 Verifikasi dan Troubleshooting Stack Observabilitas

Jalankan seluruh stack sekaligus, lalu periksa status tiap container untuk memastikan semuanya berjalan normal.

docker compose up -d
docker compose ps

Buka Grafana di http://localhost:3001 dan buat panel baru dengan query rate(container_cpu_usage_seconds_total[5m]) untuk memverifikasi metrik dari cAdvisor sudah masuk. Untuk memeriksa log, buka menu Explore di Grafana, pilih data source Loki, lalu jalankan query LogQL sederhana seperti {container="api"} untuk melihat log dari container aplikasi secara langsung tanpa perlu docker logs manual.

Kalau target Prometheus berstatus DOWN di halaman /targets, penyebab paling umum di lapangan adalah container target belum tergabung ke network yang sama dengan Prometheus, atau port yang didaftarkan di prometheus.yml tidak sesuai dengan port yang benar-benar di-listen aplikasi di dalam container. Kalau log tidak muncul di Loki lewat Alloy, periksa dulu apakah Alloy benar-benar punya akses ke docker.sock, karena kesalahan permission pada socket ini adalah penyebab paling sering ditemui saat komponen discovery.docker gagal menemukan container yang sedang berjalan. Buka antarmuka debugging Alloy di http://localhost:12345 untuk melihat status tiap komponen pipeline secara real-time, termasuk apakah loki.write berhasil mengirim data ke endpoint Loki atau justru gagal karena alamat url yang salah. Pastikan juga volume prometheus-data, grafana-data, dan loki-data tetap dipertahankan saat melakukan docker compose down tanpa opsi -v, karena riwayat metrik, dashboard, dan log yang sudah terkumpul akan hilang permanen kalau volume tersebut ikut terhapus.