πŸ“Š Observability Stack: Prometheus, Grafana & Logging Pipeline

Vault punya site-reability-engineering yang bahas SRE secara konseptual (SLO, error budget, toil) dan cicd-guide untuk pipeline deployment. Tapi gak ada catatan yang bahas toolkit observability secara konkret: Prometheus buat metrics, Grafana buat dashboard, Loki/Vector buat logs, Tempo/Jaeger buat tracing. Catatan ini jembatin teori SRE ke implementasi β€” dari setup exporter, query PromQL, dashboard design, sampe logging pipeline yang scalable. Tanpa observability, lo operasi sistem dalam kegelapan.

Posisi di Vault

Ini adalah implementasi konkret dari konsep SRE di site-reability-engineering. Juga terhubung dengan cicd-shiftleft-shiftright (shift-right = observability di production), devops (prinsip DevOps), container-kubernetes-security-deepdive (monitoring container), dan waf-reverse-proxy-deepdive (WAF metrics & alerting).


Daftar Isi


1. Observability vs Monitoring β€” Bedanya?

Monitoring = "Is the system working?"
  β†’ Tanya: uptime, CPU, memory, disk
  β†’ Output: dashboard hijau/merah, alert on/off

Observability = "Why is the system not working?"
  β†’ Tanya: high cardinality, distributed tracing, structured logging
  β†’ Output: root cause dari interaksi complex systems

Tiga pilar observability:

  1. Metrics β€” angka yang bisa di-aggregate (Prometheus)
  2. Logs β€” event diskrit, immutable, timestamped (Vector β†’ Loki/ES)
  3. Traces β€” end-to-end request flow antar service (Jaeger, Tempo)
AspekMonitoringObservability
ScopeKnown unknownsUnknown unknowns
DataCPU, memory, diskHigh-cardinality, request-level
Debug”Apa yang mati?""Kenapa lambat?”
ApproachReactive (alert β†’ fix)Proactive (explore β†’ understand)
ToolsNagios, ZabbixPrometheus, Grafana, Loki

2. Prometheus β€” Metrics & Alerting

2.1 Arsitektur

[Node Exporter] ─── scrape /metrics ──→ [Prometheus Server]
[Postgres Exp.] β”€β”€β”€β”€β”˜                        β”‚
[Blackbox Exp.] β”€β”€β”€β”€β”€β”˜                       β”‚
[Custom Exporter] β”€β”€β”€β”˜                       β”‚
                                             β”œβ”€β”€β†’ [Grafana] (visualization)
                                             β”œβ”€β”€β†’ [Alertmanager] (alerts)
                                             β”‚       └──→ [Telegram/PagerDuty]
                                             └──→ [PromQL API] (ad-hoc query)

Komponen:

  • Prometheus Server β€” pull model: scrape /metrics endpoint tiap interval
  • Exporters β€” expose metrics dari berbagai service (node, postgres, nginx)
  • Pushgateway β€” untuk job batch yang lifespan pendek (push model)
  • Alertmanager β€” alert routing, silencing, inhibition
  • Service Discovery β€” file_sd, consul_sd, kubernetes_sd

2.2 Metric Types

Counter (hanya naik):

http_requests_total{method="GET", status="200"} 1024
http_requests_total{method="POST", status="500"} 42
# Hanya naik, reset ketika restart
# Query rate: rate(http_requests_total[5m])

Gauge (naik-turun):

node_memory_MemAvailable_bytes 8589934592
node_cpu_usage_percent 45.2
# Snapshot immediate value

Histogram (distribusi bucket):

http_request_duration_seconds_bucket{le="0.1"} 500    # 500 request < 100ms
http_request_duration_seconds_bucket{le="0.5"} 800    # 800 request < 500ms
http_request_duration_seconds_bucket{le="1.0"} 950    # 950 request < 1s
http_request_duration_seconds_bucket{le="+Inf"} 1000  # 1000 total
http_request_duration_seconds_sum 450.0               # total duration
http_request_duration_seconds_count 1000               # total count
# Query p99: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

Summary (sama tapi pre-computed quantile):

rpc_duration_seconds{quantile="0.5"} 0.05   # median
rpc_duration_seconds{quantile="0.99"} 0.5   # p99
rpc_duration_seconds_sum 450.0
rpc_duration_seconds_count 1000
# Summary pre-compute di application β€” lebih mahal CPU
# Histogram compute di query β€” lebih fleksibel

2.3 Instrumentasi Aplikasi

# Python Prometheus client
from prometheus_client import Counter, Histogram, generate_latest, start_http_server
import time
 
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP requests',
                        ['method', 'endpoint', 'status'])
REQUEST_DURATION = Histogram('http_request_duration_seconds', 'Request latency',
                             ['method', 'endpoint'],
                             buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0])
 
@REQUEST_DURATION.labels(method='GET', endpoint='/api/users').time()
def handle_request():
    time.sleep(0.1)  # simulate work
    REQUEST_COUNT.labels(method='GET', endpoint='/api/users', status='200').inc()
 
# Expose /metrics endpoint
start_http_server(8000)  # Prometheus scrape di :8000/metrics

2.4 Storage & Retention

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
storage:
  tsdb:
    # Default: 15d di local disk
    retention.time: 30d
    # Max block size: 512MB
    # Total space β‰ˆ rate(ingested_samples) Γ— 2 bytes Γ— retention
 
scrape_configs:
  - job_name: "node"
    static_configs:
      - targets: ["localhost:9100"]

Keterbatasan Prometheus:

  • Single node β€” tidak HA secara native (Thanos/Cortex/Mimir untuk HA)
  • Not for logs β€” Prometheus bukan log storage
  • Not for events β€” gak cocok untuk event dengan cardinality sangat tinggi
  • Local storage β€” perlu remote write untuk long-term retention

3. PromQL β€” Query Language untuk Engineer

3.1 Instant Vector vs Range Vector

# Instant vector β€” nilai saat ini
node_memory_MemAvailable_bytes
 
# Range vector β€” nilai dalam rentang waktu (5 menit)
node_memory_MemAvailable_bytes[5m]

3.2 Rate, Increase, dan Derivative

# Rate per detik (rata-rata over 5m)
rate(http_requests_total[5m])
 
# Total increase dalam 1 jam
increase(http_requests_total[1h])
 
# Perubahan per detik (gauge)
deriv(node_memory_MemAvailable_bytes[30m])

3.3 Aggregation Operators

# By label
sum by(instance) (rate(http_requests_total[5m]))
 
# Without label
sum without(job) (rate(http_requests_total[5m]))
 
# Quantile (untuk histogram)
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
 
# Top/Bottom
topk(3, rate(http_requests_total[5m]))
bottomk(3, rate(http_requests_total[5m]))

3.4 Useful Queries

# Node CPU usage %
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
 
# Node memory usage %
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
 
# Disk space remaining %
node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} * 100
 
# Request error rate (5xx / total)
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
 
# Service Health SLO compliance β€” 99.9% uptime over 30d
(1 - (sum(increase(http_requests_total{status=~"5.."}[30d])) / sum(increase(http_requests_total[30d])))) * 100
 
# Kubernetes pod CPU
sum by(pod) (rate(container_cpu_usage_seconds_total{pod!=""}[5m]))

4. Exporters β€” Cara Kerja & Setup

4.1 Node Exporter

# docker-compose.yml
services:
  node-exporter:
    image: prom/node-exporter:latest
    command:
      - "--path.rootfs=/host"
      - "--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)"
    ports:
      - "9100:9100"
    volumes:
      - "/:/host:ro,rslave"

Metrics yang disediakan:

  • node_cpu_seconds_total β€” CPU time by mode
  • node_memory_MemAvailable_bytes β€” available memory
  • node_disk_io_time_seconds_total β€” disk I/O
  • node_network_receive_bytes_total β€” network throughput
  • node_filesystem_avail_bytes β€” disk space
  • node_load1, node_load5, node_load15 β€” system load

4.2 Blackbox Exporter

# Untuk probing endpoint eksternal (HTTP/HTTPS/TCP/ICMP)
services:
  blackbox-exporter:
    image: prom/blackbox-exporter:latest
    command: "--config.file=/config/blackbox.yml"
    ports:
      - "9115:9115"
    volumes:
      - "./blackbox.yml:/config/blackbox.yml"
 
# prometheus.yml tambahan:
scrape_configs:
  - job_name: "blackbox-http"
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://example.com
          - https://app.internal.company.com
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox:9115

4.3 Postgres Exporter

services:
  postgres-exporter:
    image: prometheuscommunity/postgres-exporter:latest
    environment:
      DATA_SOURCE_NAME: "postgresql://monitor:password@localhost:5432/postgres?sslmode=disable"
    ports:
      - "9187:9187"

Metrics penting:

  • pg_stat_database_xact_commit / pg_stat_database_xact_rollback β€” transaction rate
  • pg_stat_database_blks_hit / pg_stat_database_blks_read β€” cache hit ratio
  • pg_stat_replication β€” replication lag
  • pg_stat_user_tables β€” live/dead tuples, seq scan vs idx scan

5. Grafana β€” Dashboard yang Efektif

5.1 Dashboard Design Principles

1. Satu dashboard = satu pertanyaan
   ❌ "System Overview" (CPU + DB + Network + App + Error rate)
   βœ… "Application Latency" (p50/p95/p99, error rate, request rate)

2. Top-down: Overview β†’ Detail β†’ Raw
   Row 1: Service health (4 singlestat: availability, latency, error rate, throughput)
   Row 2: Latency breakdown (graph: p50, p95, p99)
   Row 3: Error details (table: top 5 error endpoints)
   Row 4: Raw logs (Loki: last 50 error logs)

3. Gunakan template variables
   - $instance: pilih instance yang mau dilihat
   - $job: pilih service type
   - $env: production / staging

4. Annotations β†’ overlay event
   - Deploy events (dari CI/CD pipeline)
   - Config changes
   - Alert firings

5.2 Variables & Templating

# Variable: instance
Name: instance
Type: Query
Query: label_values(node_cpu_seconds_total, instance)
 
# Variable: job
Name: job
Type: Query
Query: label_values(node_cpu_seconds_total, job)
 
# Variable: env (manual)
Name: env
Type: Custom
Values: production, staging, development
 
# Query menggunakan variable
100 - (avg by(instance) (
    rate(node_cpu_seconds_total{mode="idle", instance=~"$instance", job=~"$job"}[5m])
) * 100)

5.3 Panel Types

PanelUse CaseData Format
Time seriesTrendlines (CPU, latency, rate)Time series
StatSingle value (uptime, error rate)Single query result
GaugeThreshold-based (disk > 80%)Single value + thresholds
TableDetail data (slowest endpoints)Table (multi-column)
Bar chartComparison (CPU by instance)Categorical
HeatmapDistribution (latency over time)Histogram
LogsRaw log viewer (Loki)Log lines
TracesTrace viewer (Tempo)Trace spans

5.4 Alert Rules di Grafana

# alerting/rule.yaml
groups:
  - name: node_alerts
    rules:
      - alert: HighCPUUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Instance {{ $labels.instance }} CPU > 80%"
          description: "CPU usage at {{ $value }}% for 5 minutes"
          runbook_url: "https://runbooks.internal/cpu-saturation"
 
      - alert: CriticalCPUUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Instance {{ $labels.instance }} CPU > 95%"

6. Logging Pipeline β€” Vector, Loki, Elasticsearch

6.1 Arsitektur Logging

[Journald/Syslog] β†’ [Vector Agent] β†’ [Vector Aggregate] β†’ [Loki/Elasticsearch]
                        β”‚                     β”‚                  β”‚
                    File tail               Transform          Storage
                    Kubernetes logs         Buffer (disk)      Index
                    Docker logs             Routing by label   Grafana/Kibana

6.2 Vector β€” Modern Log Collector

Kenapa Vector bukan Logstash/Fluentd?

  • Rust-based β†’ memory safe, performa tinggi
  • Resource usage 5-10x lebih rendah dari Logstash
  • Satu binary, satu config language (TOML/VCL)
  • Buffer di disk untuk durability
# vector.toml
[sources.journald]
type = "journald"
current_boot_only = true
 
[sources.nginx_logs]
type = "file"
include = ["/var/log/nginx/access.log"]
 
[transforms.parse_nginx]
type = "remap"
inputs = ["nginx_logs"]
source = '''
. = parse_regex!(.message, r'^(?P<client_ip>\S+) - - \[(?P<timestamp>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) \S+" (?P<status>\d+) (?P<size>\d+)')
.timestamp = parse_timestamp!(.timestamp, format: "%d/%b/%Y:%H:%M:%S %z")
'''
 
[sinks.loki]
type = "loki"
inputs = ["parse_nginx", "journald"]
endpoint = "http://loki:3100"
encoding.codec = "json"
labels.job = "vector"

6.3 Loki β€” Log Aggregation ala Prometheus

Loki berbeda dari Elasticsearch: label-based, bukan full-text index.

# docker-compose.yml
services:
  loki:
    image: grafana/loki:latest
    command: -config.file=/etc/loki/loki-config.yaml
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/loki-config.yaml
      - ./data/loki:/loki
 
  promtail: # alternative to Vector (lighter, Grafana-native)
    image: grafana/promtail:latest
    command: -config.file=/etc/promtail/promtail-config.yaml
    volumes:
      - /var/log:/var/log
      - ./promtail-config.yaml:/etc/promtail/promtail-config.yaml

LogQL β€” query language Loki:

# Cari log dengan label job="nginx"
{job="nginx"}
 
# Filter dengan log content
{job="nginx"} |= "error"
{job="nginx"} |~ "5[0-9][0-9]"  # regex match 5xx
{job="api"} != "healthcheck"
 
# Log rate
rate({job="nginx"} |= "error"[5m])
 
# Aggregate
sum by(instance) (rate({job="nginx"} |~ "5.."[5m]))

Pilih Elasticsearch ketika butuh: full-text search, complex aggregations, atau sudah punya investasi ELK.

# Minimal ES + Kibana
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.x
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - "9200:9200"
 
  kibana:
    image: docker.elastic.co/kibana/kibana:8.x
    ports:
      - "5601:5601"

6.5 Perbandingan Log Backend

AspekLokiElasticsearch
Storage modelLabel + chunksFull-text inverted index
Query languageLogQL (label-first)DSL (JSON query)
Resource usageRingan (20% dari ES)Berat (heap-heavy)
ScalabilitySimple (distributed by label)Complex (shards, replicas)
RetentionEfficient (chunk compression)Space-intensive
IntegrationGrafana nativeKibana
Use caseKubernetes logs, infra logsApplication logs, SIEM

7. Distributed Tracing β€” Jaeger, Tempo, OpenTelemetry

7.1 Kenapa Tracing?

Service A β†’ Service B β†’ Service C β†’ Database
    |            |           |          |
  50ms         200ms       1.2s       5ms      β†’ total response time = 1.45s
                                                  (tapi dari mana yang lambat?)

Tanpa tracing: β€œsemuanya lambat.” Dengan tracing: β€œService C yang query DB-nya slow.”

7.2 OpenTelemetry β€” Standard API

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
import time
 
# Setup tracer
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://tempo:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
 
# Trace a request
with tracer.start_as_current_span("handle_request") as span:
    span.set_attribute("http.method", "GET")
    span.set_attribute("http.url", "/api/users")
 
    with tracer.start_as_current_span("query_database") as db_span:
        time.sleep(0.1)  # simulate DB query
        db_span.set_attribute("db.system", "postgresql")
        db_span.set_attribute("db.statement", "SELECT * FROM users")

7.3 Tempo vs Jaeger

AspekJaegerGrafana Tempo
StorageCassandra/ES/BadgerS3/GCS (object store)
QueryJaeger UIGrafana (Tempo datasource)
SchemaModeled spansTrace by ID + search by tags
CostMahal (full index)Murah (object storage)
IntegrationStandalone UIGrafana native

8. Alerting β€” Alertmanager, On-Call, Runbooks

8.1 Alertmanager Configuration

# alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: "https://hooks.slack.com/services/..."
 
route:
  receiver: "default"
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - match:
        severity: critical
      receiver: "pagerduty-critical"
      repeat_interval: 1h
    - match:
        severity: warning
      receiver: "slack-warning"
 
receivers:
  - name: "default"
    slack_configs:
      - channel: "#alerts"
        title: "{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}"
        text: '{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}'
 
  - name: "pagerduty-critical"
    pagerduty_configs:
      - routing_key: "..."
        severity: critical
 
  - name: "slack-warning"
    slack_configs:
      - channel: "#alerts-warning"
        title: "[WARN] {{ .GroupLabels.alertname }}"

8.2 Alert Design Principles

❌ Bad alert: "CPU > 80%" (lo tahu tapi gak bisa action)
βœ… Good alert: "Nginx connection pool exhausted β€” 503 errors > 1%"

Aturan:
1. Actionable β†’ ada runbook yang jelas
2. Reliable β†’ false positive < 5%
3. Urgent β†’ perlu di-wake-up tengah malam?
4. Firing β†’ for: 5m (jangan alert dari spike 1 menit)
5. Resolved β†’ auto-resolve dalam 5 menit setelah metric normal

8.3 Runbook Template

# Runbook: HighErrorRate
 
## Symptoms
 
- Grafana panel "Error Rate" > 1%
- Users complain "site error"
- PagerDuty alert fired
 
## Checklist
 
1. Cek Grafana dashboard "Service Latency" β€” apakah error rate naik di semua instance?
2. Cek log: `{job="nginx"} |= "5[0-9][0-9]"`
3. Cek recent deploy: apakah ada perubahan dalam 1 jam terakhir?
4. Cek upstream: apakah database/API masih hidup?
5. Rollback jika perlu
 
## Resolution
 
- Jika database: restart connection pool
- Jika deploy: rollback ke versi sebelumnya
- Jika traffic spike: scale up instances

9. Service Level Objectives β€” SLI, SLO, Error Budget

9.1 Definisi

SLI (Service Level Indicator): metric yang diukur
  β†’ "Proportion of requests served under 500ms"
  β†’ dalam PromQL: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

SLO (Service Level Objective): target SLI
  β†’ "95% of requests under 500ms over 30 days"

Error Budget: 100% - SLO
  β†’ Jika SLO = 99.9%, error budget = 0.1% = 43 menit/bulan
  β†’ Error budget habis β†’ stop feature releases β†’ fokus reliability

9.2 Implementasi di Prometheus + Grafana

# SLI: latency p95 per 5m
histogram_quantile(0.95, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))
 
# Error budget consumption (30d)
(1 - (sum(increase(http_requests_total{status=~"5.."}[30d]))
      / sum(increase(http_requests_total[30d])))) * 100

10. Perbandingan Tools

Metrics Backend

AspekPrometheusThanosMimirVictoriaMetrics
HA❌ (single)βœ…βœ…βœ…
Long-term storageLocal diskS3/GCSS3/GCSS3/GCS
Query federationβŒβœ…βœ…βœ…
ComplexityRendahSedangTinggiRendah
DownsamplingβŒβœ…βœ…βœ… (auto)
Use caseStarterMid-scaleLarge-scaleHigh-performance

Observability Stack

StackMetricsLogsTracesComplexity
Prometheus + Loki + TempoPrometheusLokiTempoSedang
Elastic (ELK)❌ (APM)ElasticsearchAPMTinggi
Datadogβœ… SaaSβœ… SaaSβœ… SaaSRendah ($)
Grafana Cloudβœ… SaaSβœ… SaaSβœ… SaaSRendah ($)
Self-hosted LGTMLokiGrafanaTempoMimir

πŸ”— Koneksi ke Catatan Lain


βœ… Checklist

  • Paham perbedaan Counter, Gauge, Histogram, Summary
  • Bisa nulis PromQL query: rate, increase, histogram_quantile, topk
  • Setup Prometheus + Node Exporter + Grafana di docker
  • Bisa design dashboard yang efektif (overview β†’ detail β†’ raw)
  • Setup Vector β†’ Loki pipeline for structured logging
  • Paham LogQL: label filter + content filter + aggregation
  • Bisa setup Alertmanager + alert routing rules
  • Paham SLI/SLO/Error Budget dan implementasinya
  • Paham distributed tracing: trace β†’ span β†’ context propagation

Roadmap Belajar

HARI 1: Metrics Fundamentals
  - Baca dokumen ini
  - Setup Prometheus + Node Exporter + Grafana (docker-compose)
  - Explore Grafana: import Node Exporter Full dashboard

HARI 2: PromQL & Alerting
  - Latihan PromQL: rate, increase, histogram_quantile
  - Setup alert rules + Alertmanager β†’ Telegram/Slack
  - Simulasi: matikan service, lihat alert flow

HARI 3: Logging Pipeline
  - Setup Loki + Vector
  - Kirim nginx logs ke Loki
  - Query LogQL: filter error rate, aggregate by instance

HARI 4: Advanced Observability
  - Setup OpenTelemetry + Tempo untuk tracing
  - Instrumentasi aplikasi Python/Go
  - Trace request dari frontend β†’ backend β†’ database

HARI 5: SLO & On-Call
  - Definisikan SLI untuk service lo
  - Setup SLO dashboard + error budget alert
  - Setup PagerDuty integration
  - Tulis runbook untuk top 5 alerts

Bottom Line

Observability bukan sekadar β€œpasang Prometheus, lihat dashboard hijau.” Observability adalah ability to ask new questions without deploying new code. Kalau lo masih SSH ke server buat cek log, atau masih nanya β€œada yang error ga?” β€” lo belum observability. Investasi di metrics + logs + tracing adalah force multiplier untuk velocity dan reliability. Tanpa ini, lo cuma punya monitoring β€” tahu bahwa sesuatu mati, tapi gak tahu kenapa.

Ponytail

Catatan ini belum mencakup OpenTelemetry Collector pipeline (receivers, processors, exporters), custom instrumentation untuk Go/Rust/Java, profiling (continuous profiling via Pyroscope/Phlare), dan eBPF-based observability (Cilium Hubble, Tetragon). Baca ebpf-beyond-security untuk eBPF observability.