L
Observability & Monitoring • Literally Web ID

Advanced Observability: Distributed Tracing Menggunakan OpenTelemetry & Prometheus

⏱️ 22 menit baca • 📅 13 Oct 2026 • 👁️ 1 views
Advanced Observability: Distributed Tracing Menggunakan OpenTelemetry & Prometheus

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap arsitektur web modern yang berbasis microservices, serverless, dan distributed systems, tantangan operasional terbesar bukanlah lagi sekadar memastikan server tetap hidup (uptime), melainkan memahami secara mendalam apa yang terjadi di dalam sistem ketika sebuah request HTTP melintasi puluhan batas jaringan, kontainer, dan layanan backend.

Secara historis, ketika arsitektur masih menggunakan monolith berbasis PHP (PHP-FPM) atau Node.js tunggal, proses debugging performa relatif sederhana. Cukup dengan melihat log akses Nginx, memeriksa slow query log di MySQL, atau melacak eksekusi fungsi lewat profiler lokal. Namun, modern web stack kini melibatkan rantai infrastruktur yang kompleks:

  • DNS & Edge Caching: Cloudflare/CloudFront menangani SSL/TLS termination, WAF, dan edge caching.
  • Web Server & Reverse Proxy: Nginx atau Caddy meneruskan request ke upstream backend.
  • Backend Services: Kombinasi layanan berbasis Go, Node.js, dan PHP yang berkomunikasi secara asinkron lewat message broker (RabbitMQ, Redis Streams) atau gRPC.
  • Storage & Persistence: Cluster MySQL/MariaDB dengan replikasi master-slave, Redis untuk caching, dan S3/MinIO untuk object storage.

Ketika pengguna mengeluhkan latensi tinggi pada sebuah endpoint API, pertanyaan klasik muncul: Di titik mana bottleneck tersebut terjadi? Apakah di fungsi Go yang melakukan serialisasi JSON terlalu lambat, koneksi ke Redis yang mengalami latency spike, atau query MySQL yang tidak menggunakan indeks yang tepat?

Tanpa sistem Advanced Observability yang mumpuni, SRE (Site Reliability Engineer) dan backend engineer terjebak dalam mode reaktif: menebak-nebak akar masalah, membaca ribuan baris log mentah yang tidak terstruktur, atau melakukan restart layanan tanpa solusi permanen. Di sinilah Distributed Tracing menggunakan OpenTelemetry dan Prometheus menjadi fondasi mutlak untuk mengubah reaktifitas menjadi prediktabilitas berbasis data.

Perbandingan Kondisi: Sebelum vs Sesudah Implementasi

Aspek Sebelum Observabilitas Terdistribusi (Legacy) Sesudah Implementasi OpenTelemetry & Prometheus
Mean Time to Detect (MTTD) Jam/Hari (Menunggu komplain pengguna menumpuk) Menit (Alert otomatis dari Prometheus/Grafana)
Mean Time to Resolution (MTTR) Berjam-jam melakukan analisis log manual per server Menit (Trace ID melacak exact path request yang gagal)
Korelasi Metrik & Log Terisolasi; log aplikasi terpisah dari metrik server Terintegrasi penuh (Metrics, Logs, Traces dalam satu dashboard)
Analisis Bottleneck Spekulatif; menebak layanan mana yang lambat Presisi; menunjukkan durasi milidetik per span pada fungsi spesifik

2. Konteks Inti & Deep Dive Arsitektur

OpenTelemetry (OTel) adalah standar industri universal yang diinkubasi oleh Cloud Native Computing Foundation (CNCF) untuk menghasilkan, mengumpulkan, dan mengekspor data telemetri (metrics, logs, and traces). Sementara itu, Prometheus bertindak sebagai time-series database dan sistem scraping untuk metrik numerik.

Untuk memahami cara kerjanya di balik layar (under the hood), mari bedah alur siklus hidup sebuah request dari klien hingga penyimpanan data.

[Client / Browser] 
       │
       ▼ HTTP Request
[Nginx / API Gateway] ──(Inject Trace Context: W3C TraceContext)
       │
       ▼ gRPC / HTTP
[Go Microservice] ──(OpenTelemetry Go SDK) ──> [OTel Collector (gRPC: 4317)]
       │                                                      │
       ├── (Export Metrics via Prometheus Exporter) ──────────┼──> [Prometheus (Scrape 9090)]
       │                                                      │          │
       └── (Export Traces via OTLP Exporter) ─────────────────┘          ▼
                                                                   [Grafana Dashboard]

Alur Data & Eksekusi (Data Flow):

  1. Trace Creation & Context Propagation: Ketika request masuk ke gerbang terluar (misal: Nginx atau API Gateway berbasis Go), sebuah Trace ID unik dan Span ID dihasilkan. Standar W3C Trace Context (traceparent header) disuntikkan ke dalam HTTP header agar diteruskan ke layanan hilir (downstream services).
  2. Instrumentation: Layanan backend (Node.js atau Go) yang dilengkapi dengan OpenTelemetry SDK membaca header tersebut, melanjutkan rantai trace, dan membungkus setiap operasi (seperti pemanggilan database atau fungsi internal) dalam bentuk Span.
  3. Collection (OTel Collector): Alih-alih mengirim data langsung ke penyedia penyimpanan backend, aplikasi mengirimkan data telemetri ke OpenTelemetry Collector lokal atau terpusat melalui protokol OTLP (OpenTelemetry Protocol).
  4. Processing & Routing: Otel Collector menerima data, melakukan batching, filtering, atau transformation, lalu meneruskan metrik ke Prometheus dan traces ke backend storage seperti Jaeger, Tempo, atau ClickHouse.
  5. Scraping & Visualization: Prometheus secara berkala melakukan scraping terhadap metrics endpoint dari OTel Collector atau aplikasi secara langsung, sementara Grafana memvisualisasikan korelasi antara metrik performa dan distributed traces.

3. Perbandingan Komparatif / Analisis Pendekatan

Dalam merancang arsitektur telemetri, terdapat beberapa pendekatan dalam pengumpulan dan pengiriman data. Pemilihan pendekatan sangat memengaruhi konsumsi sumber daya CPU, RAM, dan latensi jaringan pada infrastruktur Anda.

Pendekatan / Komponen Kelebihan Kekurangan Dampak Sumber Daya (CPU/RAM/Network)
Direct Exporter (Aplikasi -> Backend) Konfigurasi awal sederhana, minim komponen perantara. Beban tambahan pada aplikasi untuk retry, buffering, dan serialisasi data. CPU/RAM Tinggi di aplikasi; berisiko kehilangan data jika backend down.
Agent Collector (Sidecar / DaemonSet) Isolasi beban kerja, aplikasi cukup kirim ke localhost, buffering lokal yang andal. Membutuhkan manajemen infrastruktur tambahan (Docker container/K8s pod). RAM Sedang pada server lokal (100MB - 500MB per collector).
Gateway Collector (Centralized Pool) Pengaturan terpusat, memudahkan rotasi kunci, sampling global, dan keamanan. Titik kegagalan tunggal (Single Point of Failure / SPOF) jika tidak di-cluster; latensi jaringan tambahan. Network Bandwidth Tinggi menuju klaster pusat; konsumsi resource besar di node pusat.

[!TIP] Untuk lingkungan produksi berskala menengah hingga besar, arsitektur Agent Collector (DaemonSet atau Sidecar lokal di setiap VM/Node) adalah standar emas terbaik. Ini mencegah aplikasi mengalami kegagalan pengiriman data telemetri saat jaringan pusat mengalami gangguan sementara.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melangkah ke implementasi teknis, pastikan infrastruktur dan dependensi berikut telah tersedia pada sistem operasi berbasis Linux (Ubuntu 22.04 LTS / Debian 12 direkomendasikan):

Spesifikasi Minimum Environment:

  • OS: Linux x86_64 (Kernel 5.10+)
  • Memory (RAM): Minimal 4 GB (disarankan 8 GB untuk menjalankan stack penuh termasuk Prometheus & Grafana)
  • Go Runtime: Versi 1.22+
  • Node.js Runtime: Versi 20.x LTS
  • Docker & Docker Compose: Versi 24+

Verifikasi Port & Firewall (UFW):

Pastikan port-port berikut diizinkan atau dikonfigurasi dengan benar di balik jaringan internal (VPC):

  • 9090: Prometheus Server
  • 3000: Grafana Dashboard
  • 4317: OTLP Receiver gRPC (OpenTelemetry Collector)
  • 4318: OTLP Receiver HTTP (OpenTelemetry Collector)
  • 8889: Prometheus Exporter dari OTel Collector

Perintah verifikasi port dasar:

sudo ss -tuln | grep -E '(9090|3000|4317|4318|8889)'

5. Panduan Implementasi Langkah demi Langkah

Implementasi ini dibagi menjadi 4 bagian utama: Konfigurasi Docker Compose untuk infrastruktur observabilitas, Konfigurasi OpenTelemetry Collector, Instrumentasi Backend Go, dan Instrumentasi Backend Node.js.

Langkah 1: Menyiapkan Infrastruktur (Docker Compose)

Buat direktori kerja /opt/observability dan buat file docker-compose.yml dengan konfigurasi lengkap berikut:

version: '3.8'

networks:
  otel-net:
    driver: bridge

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.95.0
    container_name: otel-collector
    restart: always
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC receiver
      - "4318:4318"   # OTLP HTTP receiver
      - "8889:8889"   # Prometheus metrics exporter
    networks:
      - otel-net

  prometheus:
    image: prom/prometheus:v2.51.0
    container_name: prometheus
    restart: always
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - otel-net

  grafana:
    image: grafana/grafana:10.4.0
    container_name: grafana
    restart: always
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=securepassword123
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - otel-net

volumes:
  prometheus_data:
  grafana_data:

Langkah 2: Konfigurasi OpenTelemetry Collector (otel-collector-config.yaml)

Buat file konfigurasi OTel Collector di direktori yang sama:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 20

exporters:
  prometheus:
    endpoint: 0.0.0.0:8889
    namespace: "otel"
  logging:
    loglevel: debug

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus, logging]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [logging]

Langkah 3: Konfigurasi Prometheus (prometheus.yml)

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['otel-collector:8889']
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

Langkah 4: Instrumentasi Backend Go (main.go)

Inisialisasi modul Go baru dan tulis kode implementasi server HTTP dengan OpenTelemetry SDK terintegrasi:

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// Inisialisasi Tracer Provider dengan OTLP gRPC Exporter
func initTracer(ctx context.Context, collectorURL string) (*sdktrace.TracerProvider, error) {
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceNameKey.String("go-backend-service"),
			semconv.ServiceVersionKey.String("1.0.0"),
		),
	)
	if err != nil {
		return nil, err
	}

	conn, err := grpc.DialContext(ctx, collectorURL,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithBlock(),
	)
	if err != nil {
		return nil, err
	}

	exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
	if err != nil {
		return nil, err
	}

	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSampler(sdktrace.AlwaysSample()),
		sdktrace.WithResource(res),
		sdktrace.WithBatcher(exporter),
	)

	otel.SetTracerProvider(tp)
	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
		propagation.TraceContext{},
		propagation.Baggage{},
	))

	return tp, nil
}

func main() {
	ctx := context.Background()

	// Hubungkan ke OTel Collector container di Docker
	tp, err := initTracer(ctx, "localhost:4317")
	if err != nil {
		log.Fatalf("Gagal menginisialisasi tracer: %v", err)
	}
	defer func() {
		if err := tp.Shutdown(ctx); err != nil {
			log.Printf("Error saat mematikan tracer provider: %v", err)
		}
	}()

	http.HandleFunc("/api/v1/process", func(w http.ResponseWriter, r *http.Request) {
		tr := otel.Tracer("go-http-server")
		ctx, span := tr.Start(r.Context(), "HTTP GET /api/v1/process")
		defer span.End()

		// Simulasi pemrosesan bisnis yang memakan waktu
		time.Sleep(50 * time.Millisecond)

		// Sub-span untuk simulasi query database
		_, dbSpan := tr.Start(ctx, "Database SELECT users")
		time.Sleep(30 * time.Millisecond)
		dbSpan.End()

		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"status":"success","message":"Request processed successfully"}`)) 
	})

	srv := &http.Server{Addr: ":8080"}
	go func() {
		log.Println("Server Go berjalan di port 8080...")
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("HTTP server error: %v", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit

	ctxShutDown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctxShutDown); err != nil {
		log.Fatalf(
			"Server Shutdown Failed: %v", err)
		}
	log.Println("Server berhasil dimatikan dengan aman.")
}

Langkah 5: Jalankan Layanan

Jalankan stack infrastruktur menggunakan Docker Compose:

docker-compose up -d

Jalankan aplikasi backend Go di terminal lokal:

go mod init observability-demo
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/sdk/trace
go run main.go

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari bedah beberapa komponen kode paling krusial pada implementasi backend Go di atas:

  1. resource.New(...) & semconv.ServiceNameKey: Bagian ini mendefinisikan metadata atau atribut global dari layanan Anda (service.name, service.version). Atribut ini sangat vital di Grafana untuk melakukan filtering metrik lintas microservices (misal: membedakan log dari go-backend-service dengan layanan Node.js lainnya).

  2. otlptracegrpc.New(...): Menggunakan protokol gRPC untuk mengirimkan data traces ke OTel Collector. gRPC dipilih ketimbang HTTP/JSON karena efisiensi serialisasi protokol Protocol Buffers (Protobuf) yang menghasilkan penggunaan bandwidth jaringan yang jauh lebih kecil pada beban traffic tinggi.

  3. propagation.NewCompositeTextMapPropagator(...): Baris ini mendaftarkan standar W3C TraceContext dan Baggage. Ini memastikan bahwa ketika layanan Go ini memanggil layanan lain via HTTP atau gRPC, Header traceparent secara otomatis disisipkan ke dalam request keluar, menjaga rantai distributed trace tetap utuh.

  4. tr.Start(ctx, "Database SELECT users"): Membuat child span di dalam konteks request HTTP utama. Jika query database ini mengalami kelambatan, span ini akan terlihat secara terisolasi di visualisasi Grafana Tempo/Jaeger dengan durasi eksekusinya sendiri.


7. Skenario Produksi & Studi Kasus Dunia Nyata

[!IMPORTANT] Studi Kasus: Sebuah platform e-commerce dengan 15.000 requests per second (RPS) mengalami lonjakan latensi acak pada halaman checkout. Pengecekan CPU pada server Nginx dan Redis normal, namun database MySQL mengalami connection pool exhaustion secara berkala.

Solusi Penerapan Observabilitas Lanjutan:

  1. Tail-Based Sampling di OTel Collector: Pada traffic 15.000 RPS, mencatat 100% traces akan menghabiskan gigabytes bandwidth dan membebani storage. Solusinya adalah mengonfigurasi Tail-Based Sampling di OTel Collector, di mana collector hanya menyimpan traces yang memiliki durasi melebihi 500ms atau yang menghasilkan status HTTP 5xx error.
  2. Korelasi Metrik Prometheus & Traces: SRE membuka dashboard Grafana, menggunakan Prometheus untuk melihat metrik http_request_duration_seconds_bucket, lalu melakukan drill-down langsung ke OpenTelemetry trace viewer untuk melihat query SQL spesifik yang menyebabkan table lock pada tabel orders.

8. Common Pitfalls, Anti-Patterns & Solusi

Berikut adalah kesalahan fatal yang sering dilakukan oleh pengembang saat mengimplementasikan OpenTelemetry:

  • Anti-Pattern 1: Mengabaikan Context Propagation pada Pemanggilan Asinkron.
    • Masalah: Ketika request masuk ke antrean (RabbitMQ/Redis Queue) lalu diproses oleh worker background, Trace ID terputus karena context Go tidak diteruskan ke job payload.
    • Solusi: Selalu serialisasi Carrier dari OpenTelemetry ke dalam JSON payload antrean, lalu extract kembali context tersebut saat worker mulai memproses job.
  • Anti-Pattern 2: Cardinality Explosion pada Metrik Prometheus.
    • Masalah: Menjadikan User ID atau Dynamic URL Slug (/users/12345, /users/67890) sebagai label metrik Prometheus. Hal ini menyebabkan ledakan kardinalitas memori pada server Prometheus hingga mengalami Out Of Memory (OOM).
    • Solusi: Gunakan normalized route paths (misal: /users/:id) sebagai label metrik, dan simpan User ID secara eksklusif di dalam Distributed Traces (bukan di metrik).
  • Anti-Pattern 3: Menggunakan In-Memory Exporter di Produksi tanpa Batching.
    • Masalah: Mengirim setiap span secara sinkron (synchronous export) akan memblokir goroutine utama aplikasi dan melipatgandakan latensi HTTP.
    • Solusi: Selalu aktifkan WithBatcher() pada SDK Tracer Provider.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk menguji ketahanan sistem observabilitas yang telah dibangun, lakukan load testing menggunakan tool k6 atau wrk. Di bawah ini adalah skrip pengujian k6 (load-test.js):

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 50 },   
    { duration: '1m', target: 200 },  
    { duration: '30s', target: 0 },   
  ],
};

export default function () {
  const res = http.get('http://localhost:8080/api/v1/process');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 200ms': (r) => r.timings.duration < 200,
  });
  sleep(0.1);
}

Jalankan perintah benchmark:

k6 run load-test.js

Tuning Performa OTel Collector:

Jika Anda mendapati OTel Collector mengonsumsi CPU yang terlalu tinggi di produksi, lakukan optimasi pada berkas otel-collector-config.yaml dengan memperketat parameter batching:

processors:
  batch:
    send_batch_size: 8192
    timeout: 5s
    send_batch_max_size: 10240

Penyesuaian ini mengurangi frekuensi pengiriman jaringan (network syscalls) dan menekan penggunaan CPU secara signifikan.


10. Checklist Penerapan Produksi & Kesimpulan

Sebelum meluncurkan arsitektur observabilitas ini ke lingkungan produksi, pastikan Anda telah memverifikasi daftar periksa (checklist) berikut:

  • Keamanan Transport: Apakah komunikasi antara aplikasi backend dan OTel Collector menggunakan TLS yang aman (atau berada di dalam private VPC mesh)?
  • Manajemen Memori: Apakah memory_limiter processor aktif di konfigurasi OTel Collector untuk mencegah OOM Crash?
  • Strategi Sampling: Apakah sampling rate telah diatur dengan bijak (misal: 10% dari total traffic normal, 100% untuk error) untuk menghemat biaya penyimpanan?
  • Retensi Data Prometheus: Apakah konfigurasi --storage.tsdb.retention.time pada Prometheus sudah disesuaikan dengan kapasitas disk server (misal: 15 hari)?
  • Alerting Rules: Apakah alert penting telah dikonfigurasi di Grafana/Prometheus untuk mendeteksi lonjakan error rate (> 1%) dan latency (p99 > 500ms)?

Kesimpulan

Implementasi Advanced Observability menggunakan OpenTelemetry dan Prometheus bukan lagi sekadar pelengkap, melainkan komponen fundamental dalam rekayasa keandalan sistem modern. Dengan menghubungkan titik-titik data antara metrics, logs, dan traces, SRE dan pengembang dapat memangkas waktu penanganan insiden dari berjam-jam menjadi hitungan menit, memastikan ketersediaan dan performa aplikasi tetap optimal pada skala enterprise.

🏷️ Tags: #Sre #Opentelemetry #Prometheus #Grafana #Distributed Tracing