L
Observability & Monitoring • Literally Web ID

Distributed Tracing dan Observability Skala Besar dengan OpenTelemetry dan Prometheus

⏱️ 18 menit baca • 📅 13 Oct 2026 • 👁️ 1 views
Distributed Tracing dan Observability Skala Besar dengan OpenTelemetry dan Prometheus

Distributed Tracing dan Observability Skala Besar dengan OpenTelemetry dan Prometheus

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap arsitektur web modern, pergeseran dari monolit monolitik ke ekosistem layanan mikro (microservices) telah membawa kompleksitas operasional yang masif. Permintaan tunggal dari pengguna (client request) tidak lagi hanya dilayani oleh satu proses aplikasi di balik web server Nginx, melainkan melewati puluhan bahkan ratusan hop jaringan: mulai dari Cloudflare CDN, Edge Workers, WAF, load balancer, reverse proxy, PHP-FPM atau Go backend, antrean pesan (RabbitMQ/Redis), hingga sistem penyimpanan terdistribusi seperti MinIO (S3 compatible) dan database relasional (MySQL/MariaDB).

Ketika terjadi latensi tinggi atau kegagalan transaksi, mencari akar masalah (root cause analysis) menggunakan log konvensional menjadi mimpi buruk. Sysadmin dan Backend Engineer harus melakukan grep manual pada puluhan file log yang tersebar di berbagai server, menyelaraskan stempel waktu (timestamps) yang sering kali tidak sinkron, dan menerka-nerka di titik mana kegagalan terjadi.

Kondisi Sebelum vs Sesudah Penerapan

  • Sebelum (Ad-hoc Logging & Basic Monitoring):

    • Debugging memakan waktu berhari-hari karena hilangnya konteks permintaan lintas layanan.
    • Pemantauan metrik terisolasi (CPU dan RAM tinggi, tetapi tidak diketahui endpoint mana yang menyebabkan lonjakan).
    • Tidak ada visibilitas terhadap ketergantungan eksternal (external API calls, database query lambat).
    • Time to Resolution (TTR) sangat tinggi, berdampak langsung pada SLA dan kepuasan pengguna.
  • Sesudah (OpenTelemetry & Prometheus Observability Stack):

    • Setiap permintaan dilacak secara end-to-end melalui Trace ID unik yang diteruskan melintasi batas jaringan.
    • Metrik performa, tracing, dan log berkorelasi penuh (Unified Observability).
    • Deteksi anomali otomatis berbasis metrik Prometheus dengan alerting cerdas ke sistem notifikasi.
    • Penurunan drastis pada MTTR (Mean Time to Resolution) dari jam/hari menjadi hitungan menit.

2. Konsep Inti & Deep Dive Arsitektur

Observability modern berdiri di atas tiga pilar utama (The Three Pillars of Observability): Metrics, Logs, dan Traces. OpenTelemetry (OTel) menyatukan ketiganya dalam satu standar vendor-agnostik.

Alur Data di Balik Layar (Under the Hood)

  1. Trace dan Span: Sebuah Trace merepresentasikan perjalanan lengkap sebuah transaksi. Di dalamnya terdapat unit kerja terkecil yang disebut Span. Setiap span memiliki nama, timestamp mulai, durasi, atribut key-value, dan status.
  2. Context Propagation: Ketika layanan A memanggil layanan B via HTTP atau gRPC, Header HTTP khusus (seperti traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01) disisipkan secara otomatis oleh SDK OpenTelemetry, memungkinkan layanan penerima melanjutkan Trace ID yang sama.
  3. OpenTelemetry Collector: Agen perantara yang bertugas menerima telemetry data (traces, metrics, logs) dari aplikasi, melakukan pemrosesan (filtering, batching, masking data sensitif), dan mengekspornya ke backend storage seperti Prometheus, Jaeger, atau Tempo.
[Client Request]
       │
       ▼
[Nginx Reverse Proxy]
       │ (Inject Trace Header)
       ▼
[Go Microservice A] ──(gRPC / HTTP)──> [Node.js Microservice B]
       │                                       │
       ├───────────── (Push OTLP) ─────────────┤
       ▼                                       ▼
┌─────────────────────────────────────────────────────────────┐
│                  OpenTelemetry Collector                    │
└──────┬───────────────────────────────────────────────┬──────┘
       │ (Scrape Metrics)                              │ (Export Traces)
       ▼                                               ▼
[Prometheus Server]                            [Jaeger / Tempo]
       │
       ▼
[Grafana Dashboard & Alertmanager]

3. Perbandingan Komparatif / Analisis Pendekatan

Berikut adalah analisis komparatif antara pendekatan monitoring tradisional dengan ekosistem OpenTelemetry + Prometheus untuk arsitektur enterprise.

Parameter Perbandingan Tradisional (APM Komersial / Basic Logging) Modern (OpenTelemetry + Prometheus) Dampak pada Sumber Daya
Vendor Lock-in Tinggi (Terikat pada satu ekosistem SaaS) Nol (Standar open-source CNCF) Kebebasan migrasi infrastruktur
Overhead CPU & RAM Variabel, sering kali membebani aplikasi Sangat rendah (< 2% overhead CPU) Optimal untuk high-concurrency server
Korelasi Data Terpisah (Log di sini, Metrik di sana) Terintegrasi penuh (Trace terhubung ke Log/Metrik) Mempercepat investigasi insiden
Skalabilitas Mahal seiring bertambahnya volume data Fleksibel (Collector dapat di-scaling horizontal) Efisiensi biaya infrastruktur

4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melakukan implementasi teknis, pastikan spesifikasi environment berikut terpenuhi:

  • Sistem Operasi: Ubuntu Server 22.04 LTS / Debian 12 (untuk host collector dan aplikasi).
  • Go Runtime: Versi 1.21 atau lebih tinggi.
  • Node.js Runtime: Versi 18.x / 20.x LTS.
  • Containerization: Docker & Docker Compose versi terbaru.
  • Network Ports:
    • 4317 (OTLP gRPC receiver)
    • 4318 (OTLP HTTP receiver)
    • 9090 (Prometheus)
    • 3000 (Grafana)

Verifikasi instalasi Docker dan Docker Compose pada sistem:

docker --version
docker compose version

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Konfigurasi Docker Compose untuk Stack Observability

Buat file docker-compose.yml untuk menjalankan Prometheus, OpenTelemetry Collector, dan Grafana.

version: '3.8'

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.95.0
    container_name: otel-collector
    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 exporter metrics
    restart: unless-stopped

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

  grafana:
    image: grafana/grafana:10.4.0
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=securepassword
    ports:
      - "3000:3000"
    restart: unless-stopped

volumes:
  prometheus_data:

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

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

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "enterprise"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus] # Di produksi gunakan Jaeger/Tempo untuk traces

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']

Langkah 4: Instrumentasi Backend Golang

Inisialisasi modul Go dan instal dependensi OpenTelemetry.

mkdir go-service && cd go-service
go mod init web-service-go
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get google.golang.org/grpc

Buat file main.go dengan implementasi lengkap:

package main

(
	"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"
)

func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceNameKey.String("golang-backend-service"),
		),
	)
	if err != nil {
		return nil, err
	}

	conn, err := grpc.DialContext(ctx, "localhost:4317", 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()
	tp, err := initTracer(ctx)
	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("golang-backend-service")
		cx, span := tr.Start(r.Context(), "HandleProcessRequest")
		defer span.End()

		time.Sleep(50 * time.Millisecond) // Simulasi proses bisnis / query database
		
		_, subSpan := tr.Start(cx, "DatabaseQuerySimulation")
		time.Sleep(20 * time.Millisecond)
		subSpan.End()

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

	server := &http.Server{Addr: ":8080"}
	go func() {
		log.Println("Golang backend berjalan pada port 8080...")
		if err := server.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
	server.Shutdown(ctx)
	log.Println("Server berhasil dimatikan secara elegan.")
}

Langkah 5: Instrumentasi Backend Node.js / Express

Inisialisasi proyek Node.js dan pasang paket OpenTelemetry.

mkdir node-service && cd node-service
npm init -y
npm install express @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-grpc

Buat file instrumentation.js untuk bootstrapping:

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'grpc://localhost:4317',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
  serviceName: 'nodejs-gateway-service',
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('Tracing terminated'))
    .catch((error) => console.log('Error terminating tracing', error))
    .finally(() => process.exit(0));
});

Buat file server.js:

require('./instrumentation');

const express = require('express');
const app = express();
const PORT = 3001;

app.get('/api/v1/gateway', async (req, res) => {
  // Contoh integrasi memanggil layanan Go
  res.json({
    service: 'nodejs-gateway',
    status: 'routed',
    timestamp: new Date().toISOString()
  });
});

app.listen(PORT, () => {
  console.log(`Node.js gateway berjalan pada port ${PORT}`);
});

6. Bedah Kode & Penjelasan Detail Baris Kunci

  • resource.New(...) (Golang): Mendefinisikan metadata global layanan seperti service.name. Atribut ini wajib agar Prometheus dan Grafana dapat membedakan metrik dari berbagai layanan mikro secara akurat.
  • otlptracegrpc.New(...): Menghubungkan aplikasi ke OpenTelemetry Collector menggunakan protokol gRPC pada port 4317. gRPC dipilih di tingkat enterprise karena efisiensi serialisasi Protocol Buffers dibanding HTTP/JSON.
  • NodeSDK & getNodeAutoInstrumentations (Node.js): Pendekatan zero-code instrumentation otomatis mendeteksi framework Express, HTTP server, database client (pg, mysql2), dan Redis tanpa harus mengubah baris kode bisnis aplikasi secara masif.

[!IMPORTANT] Pastikan TracerProvider di-shut down dengan benar saat aplikasi menerima sinyal terminasi (SIGTERM / SIGINT) agar span terakhir yang masih berada di dalam buffer memory tidak hilang (data loss).


7. Skenario Produksi & Studi Kasus Dunia Nyata

Dalam skenario high concurrency (misalnya 10.000 request per second pada e-commerce flash sale), pengumpulan 100% traces akan membebani jaringan dan penyimpanan backend.

Strategi Tail-based Sampling

Alih-alih melakukan head-based sampling (keputusan sampling diambil di awal aplikasi), gunakan Tail-based Sampling pada OpenTelemetry Collector. Strategi ini menganalisis seluruh siklus trace terlebih dahulu; jika sebuah trace mengalami error (HTTP status 5xx) atau latensi melebihi ambang batas (misal > 500ms), maka trace tersebut 100% disimpan. Sebaliknya, trace normal yang cepat hanya diambil sebagian kecil (misal 1%).


8. Common Pitfalls, Anti-Patterns & Solusi

  1. High Cardinality pada Atribut Span:
    • Anti-Pattern: Memasukkan User ID, Email, atau Dynamic URL Parameters secara langsung ke dalam metric labels atau span names di Prometheus.
    • Dampak: Meledaknya jumlah time-series di Prometheus, berujung pada Out of Memory (OOM).
    • Solusi: Gunakan Attributes di dalam Span untuk data spesifik pengguna, bukan sebagai metric label Prometheus.
  2. Blokir Main Thread oleh Ekspor Telemetry:
    • Anti-Pattern: Menggunakan sinkronisasi ekspor secara langsung pada request handler tanpa batching.
    • Solusi: Selalu gunakan sdktrace.WithBatcher() di Go atau batch processor di Collector.

9. Benchmark, Metrik Kinerja & Tuning Performa

Lakukan pengujian beban menggunakan utilitas k6 untuk menguji ketahanan aplikasi dan collector.

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 was 200': (r) => r.status === 200 });
  sleep(0.1);
}

Jalankan benchmark:

k6 run load-test.js

[!TIP] Pantau metrik penggunaan memori kontainer otel-collector melalui Prometheus. Jika terjadi penumpukan queue, tingkatkan alokasi memori atau sesuaikan konfigurasi send_batch_size pada file konfigurasi collector.


10. Checklist Penerapan Produksi & Kesimpulan

Sebelum merilis arsitektur observability ini ke lingkungan production, pastikan hal-hal berikut telah terpenuhi:

  • Semua layanan mikro telah mengonfigurasi service.name yang unik.
  • Penggunaan Context Propagation terverifikasi aktif pada komunikasi HTTP/gRPC antar layanan.
  • OpenTelemetry Collector dikonfigurasi dengan batch processor untuk menghindari overhead jaringan.
  • Pembatasan cardinality diterapkan pada metrik Prometheus untuk mencegah OOM.
  • Dashboard Grafana terhubung dan menampilkan metrik RED (Rate, Errors, Duration).
  • Mekanisme Alertmanager terintegrasi dengan kanal eskalasi darurat (PagerDuty/Slack).

Dengan menerapkan OpenTelemetry dan Prometheus secara terstruktur, organisasi Anda tidak hanya mendapatkan visibilitas mendalam atas performa sistem, tetapi juga membangun fondasi infrastruktur yang tahan banting dan siap menghadapi skala enterprise modern.

🏷️ Tags: #Golang #Node.js #Opentelemetry #Prometheus #Distributed Tracing