L
Observability & Monitoring • Literally Web ID

Observabilitas End-to-End: Distributed Tracing dengan OpenTelemetry dan Jaeger

⏱️ 22 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Observabilitas End-to-End: Distributed Tracing dengan OpenTelemetry dan Jaeger

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap arsitektur web modern yang bertransisi dari monolitik menuju microservices terdistribusi, tantangan operasional terbesar bukanlah lagi pada penskalaan kapasitas komputasi semata, melainkan pada kemampuan untuk mendiagnosis kegagalan secara real-time. Sebuah request HTTP sederhana dari klien (browser atau aplikasi mobile) kini tidak lagi ditangani oleh satu siklus eksekusi PHP atau Node.js tunggal yang membaca dari satu database MySQL. Sebaliknya, request tersebut melompati lapisan Edge CDN (Cloudflare), melewati Reverse Proxy (Nginx), memicu validasi token di API Gateway, terdistribusi ke berbagai layanan backend (Go dan Node.js gRPC/REST), berinteraksi dengan cluster Redis untuk caching, mengantrekan tugas asinkron ke RabbitMQ atau Redis Queue yang dieksekusi oleh Worker (dipantau Supervisor), hingga akhirnya melakukan persistensi ke database relasional dan Object Storage (MinIO/S3).

Ketika terjadi lonjakan latensi atau error rate 5xx pada sistem berskala besar seperti ini, pendekatan tradisional menggunakan centralized logging semata (seperti ELK Stack atau Grafana Loki) sering kali gagal memberikan jawaban yang cepat. Log berbasis teks bersifat terisolasi; log dari backend Go mencatat kegagalan koneksi database, sementara log dari Node.js mencatat timeout, namun menghubungkan kedua kejadian tersebut dalam linimasa linier yang sama menjadi sangat sulit tanpa adanya correlation ID yang konsisten. Di sinilah Observabilitas End-to-End melalui Distributed Tracing menjadi fondasi krusial bagi SRE (Site Reliability Engineer) dan Platform Engineers.

Perbandingan Kondisi Sebelum vs Sesudah Penerapan

Parameter Sebelum Penerapan (Logging & Metric Tradisional) Sesudah Penerapan (OpenTelemetry & Jaeger)
Waktu Deteksi Root Cause Jam hingga hari; menebak-nebak berdasarkan log error. Menit; langsung menunjuk layanan dan baris kode spesifik yang lambat.
Korelasi Request Lintas Layanan Manual, mengandalkan pencarian X-Request-ID di berbagai file log. Otomatis melalui Context Propagation (W3C Trace Context).
Analisis Bottleneck Tidak terlihat; metrik hanya menunjukkan CPU/RAM tinggi secara agregat. Visual, melihat durasi persis (ms) dari setiap span database, HTTP call, dan internal function.
Troubleshooting Beban Tinggi Menimbulkan downtime lebih lama saat debugging di production. Aman dilakukan secara asynchronous tanpa membebani thread utama aplikasi.

2. Konteks Inti & Deep Dive Arsitektur

Distributed tracing bekerja berdasarkan konsep inti berupa Trace dan Span. Sebuah Trace merepresentasikan perjalanan penuh dari sebuah request sejak masuk ke sistem hingga keluar menghasilkan respons. Di dalam sebuah Trace, terdapat sekumpulan Span yang tersusun secara hierarkis (Parent-Child). Setiap Span merepresentasikan unit kerja tunggal yang memiliki durasi waktu, atribut key-value, dan status eksekusi.

Alur Data di Bawah Hood (Under the Hood)

Arsitektur OpenTelemetry (OTel) memisahkan proses instrumentasi kode dari pengiriman data metrik/trace melalui OpenTelemetry Collector. Berikut adalah diagram alur data end-to-end:

+-------------------------------------------------------------------------+
|                           Client Request                                |
+-------------------------------------------------------------------------+
                                     |
                                     v
+-------------------------------------------------------------------------+
|                     Cloudflare CDN / Edge WAF                           | 
+-------------------------------------------------------------------------+
                                     |
                                     v
+-------------------------------------------------------------------------+
|             Nginx Reverse Proxy (SSL Termination + W3C Header)          |
+-------------------------------------------------------------------------+
         |                                                 |
         v (gRPC / HTTP)                                   v (gRPC / HTTP)
+-------------------------------+               +-------------------------+
| Go Backend Service            | ------------> | Node.js Backend Service |
| (Instrumented via OTel Go SDK)|  gRPC/HTTP    | (Instrumented via JS SDK|
+-------------------------------+               +-------------------------+
         \                                                 /
          \                                               /
           +------------------- OTLP (gRPC) -------------+
                                     |
                                     v
+-------------------------------------------------------------------------+
|                        OpenTelemetry Collector                          | 
|  (Receivers -> Processors: Batch/Memory Limiter -> Exporters: OTLP/Jaeger)|
+-------------------------------------------------------------------------+
                                     |
                                     v
+-------------------------------------------------------------------------+
|                 Jaeger Backend (Collector & Storage)                    |
+-------------------------------------------------------------------------+
                                     |
                                     v
+-------------------------------------------------------------------------+
|                    Jaeger UI (Analisis Latensi)                         |
+-------------------------------------------------------------------------+
  1. Instrumentasi Aplikasi: Aplikasi backend (Go dan Node.js) menggunakan OpenTelemetry SDK untuk membuat Span secara otomatis (lewat auto-instrumentation HTTP/gRPC/Database) maupun manual (custom span).
  2. Context Propagation: Saat layanan Go memanggil layanan Node.js, header HTTP khusus (traceparent standar W3C) disuntikkan ke dalam request outbound, memastikan layanan penerima melanjutkan trace ID yang sama.
  3. Pengumpulan (Collection): Data span dikirimkan ke OpenTelemetry Collector menggunakan protokol OTLP (OpenTelemetry Protocol) baik via gRPC (port 4317) maupun HTTP (port 4318).
  4. Pemrosesan (Processing): Collector melakukan batching, penyaringan, dan pengayaan data sebelum diteruskan ke penyimpanan.
  5. Visualisasi: Jaeger Backend menerima data dari Collector, menyimpannya di memori/storage, dan menyajikannya secara interaktif melalui Jaeger UI.

3. Perbandingan Komparatif / Analisis Pendekatan

Dalam membangun sistem observabilitas, pemilihan arsitektur pengumpulan data menentukan reliabilitas sistem itu sendiri. Berikut adalah perbandingan tiga pendekatan utama pengiriman trace dari aplikasi ke backend penyimpanan:

Pendekatan Arsitektur Kelebihan Kekurangan Konsumsi Sumber Daya (CPU/RAM/Latency)
Direct to Backend (Aplikasi langsung ke Jaeger) Implementasi awal sangat cepat dan minim komponen infrastruktur. Aplikasi terikat langsung ke backend Jaeger; rentan dropped spans jika Jaeger down. CPU: Sedang
RAM: Rendah
Latency: Minimal
OpenTelemetry Collector (Daemonset / Sidecar) Sangat scalable, mendukung batching, retry, filtering, dan transformasi data di edge. Membutuhkan manajemen infrastruktur tambahan (deployment OTel Collector). CPU: Rendah
RAM: Sedang (Tergantung queue size)
Latency: Sangat kecil (Buffer asynchronous)
Managed SaaS (Datadog / New Relic / Honeycomb) Fitur lengkap, zero-maintenance infrastruktur, analisis AI bawaan. Biaya lisensi sangat tinggi untuk traffic enterprise skala besar. CPU: Sangat Rendah
RAM: Rendah
Latency: Bergantung jaringan eksternal

[!IMPORTANT] Untuk lingkungan produksi skala enterprise, selalu gunakan OpenTelemetry Collector sebagai proxy lokal (baik sebagai Sidecar di Kubernetes maupun Daemonset/Service lokal). Ini mencegah lonjakan trafik telemetry melumpuhkan aplikasi utama jika sistem penyimpanan analitik mengalami gangguan.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum melakukan implementasi teknis, pastikan environment Anda memenuhi spesifikasi minimum berikut:

  • Sistem Operasi: Linux Ubuntu 22.04 LTS / Debian 12 / RHEL 9 (atau Kubernetes v1.28+).
  • Runtime Backend:
    • Go v1.22+
    • Node.js v20.x LTS+
  • Infrastruktur Pendukung:
    • Docker & Docker Compose v2.20+
    • Nginx v1.24+ (Reverse Proxy)
    • Redis v7.0+ (Caching & Session)
    • MySQL v8.0+ / PostgreSQL v15+ (Database)
  • Port Jaringan yang Harus Dibuka:
    • 4317: OTLP gRPC Receiver (OpenTelemetry Collector)
    • 4318: OTLP HTTP Receiver (OpenTelemetry Collector)
    • 16686: Jaeger UI Web Dashboard
    • 14250: Jaeger Collector gRPC

Verifikasi kesiapan port dengan perintah berikut pada terminal Linux:

sudo ss -tulpn | grep -E '4317|4318|16686|14250'

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Konfigurasi Docker Compose untuk Jaeger & OTel Collector

Buat file docker-compose.yml untuk menjalankan infrastruktur observabilitas secara lokal atau di server staging production:

version: '3.8'

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.96.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:
      - observability-net

  jaeger:
    image: jaegertracing/all-in-one:1.54.0
    container_name: jaeger
    restart: always
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "16686:16686" # Jaeger UI
      - "14250:14250" # Jaeger gRPC collector
    networks:
      - observability-net

networks:
  observability-net:
    driver: bridge

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

Buat file konfigurasi collector untuk menerima data dari aplikasi dan meneruskannya ke Jaeger:

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:
  otlp:
    endpoint: "jaeger:4317"
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp]
  telemetry:
    logs:
      level: "info"

Jalankan layanan infrastruktur:

docker compose up -d

Langkah 3: Instrumentasi Backend Go (Producer Service)

Inisialisasi modul Go baru dan instal paket OpenTelemetry:

mkdir go-producer && cd go-producer
go mod init go-producer
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 go.opentelemetry.io/otel/propagation
go get google.golang.org/grpc

Buat file main.go dengan kode lengkap produksi:

package main

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

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semantic/conventions/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) {
	host := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
	if host == "" {
		host = "localhost:4317"
	}

	conn, err := grpc.DialContext(ctx, host,
		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
	}

	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceNameKey.String("go-producer-service"),
			attribute.String("environment", "production"),
		),
	)
	if err != nil {
		return nil, err
	}

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

	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("/process", func(w http.ResponseWriter, r *http.Request) {
		tr := otel.Tracer("go-producer")
		ctx, span := tr.Start(r.Context(), "HTTP GET /process")
		defer span.End()

		span.SetAttributes(attribute.String("http.method", "GET"), attribute.String("http.url", "/process"))

		// Simulasi pekerjaan berat di backend Go
		time.Sleep(150 * time.Millisecond)

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("Proses Go Berhasil, meneruskan ke Node.js..."))
	})

	log.Println("Go Producer berjalan pada port 8080...")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

Langkah 4: Instrumentasi Backend Node.js (Consumer Service)

Inisialisasi proyek Node.js dan pasang paket OpenTelemetry:

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

Buat file tracing.js untuk mengaktifkan auto-instrumentation sebelum aplikasi Express berjalan:

'use strict';

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

const exporter = new OTLPTraceExporter({
  url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
});

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'node-consumer-service',
    [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
  }),
  traceExporter: exporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

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

Buat file server.js untuk menjalankan layanan Express:

'use strict';

require('./tracing');

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/consume', async (req, res) => {
  // Simulasi query database atau proses bisnis internal
  await new Promise(resolve => setTimeout(resolve, 200));
  
  res.status(200).json({
    status: 'success',
    message: 'Data berhasil diproses oleh Node.js consumer',
    timestamp: new Date().toISOString()
  });
});

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

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari kita bedah beberapa komponen kode paling krusial di atas:

  1. sdktrace.WithSampler(sdktrace.AlwaysSample()) (Go):
    • Fungsi: Mengatur kebijakan pengambilan sampel (sampling rate). AlwaysSample berarti 100% trace dikirim ke collector. Di lingkungan High Traffic Production (misal > 50.000 req/s), konfigurasi ini akan membebani jaringan dan storage. Ubah menggunakan sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.05)) untuk mengambil hanya 5% dari total traffic.
  2. propagation.NewCompositeTextMapPropagator(...) (Go & Node.js):
    • Fungsi: Memastikan standar W3C Trace Context digunakan (traceparent header). Jika layanan Anda berinteraksi dengan sistem legacy yang menggunakan B3 Propagation (Zipkin), tambahkan propagation.B3{} ke dalam composite propagator agar tidak terjadi broken trace.
  3. getNodeAutoInstrumentations() (Node.js):
    • Fungsi: Secara otomatis menginstrumentasi HTTP, HTTPS, Express, MySQL, Redis, dan pustaka populer lainnya tanpa harus menulis manual span di setiap fungsi internal. Ini memangkas waktu pengembangan secara drastis.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Kasus: Mendiagnosis Lonjakan Latensi pada E-Commerce Checkout Pipeline

Pada saat Flash Sale, sistem mengalami lonjakan p99 latency dari 120ms menjadi 3.2 detik pada endpoint /api/v1/checkout. Tanpa sistem tracing, SRE hanya melihat peningkatan latensi pada Nginx access log tanpa tahu penyebab pastinya.

Langkah Investigasi di Jaeger UI:

  1. Buka Jaeger UI di http://localhost:16686.
  2. Gunakan fitur Find Traces dengan filter service: node-consumer-service, min-duration: 2s.
  3. Pilih salah satu trace yang lambat. Jaeger akan menampilkan grafik air terjun (waterfall chart).
  4. Ditemukan bahwa dari total 3.2 detik latensi:
    • HTTP Server Span: 15ms
    • Internal Express Middleware: 10ms
    • Database Query Span (SELECT * FROM inventory WHERE id = ?): 3,050ms!
  5. Klik pada span database tersebut untuk melihat tag detail. Terlihat bahwa kueri tersebut tidak menggunakan indeks (Full Table Scan pada tabel berukuran 15 juta baris).
  6. DBA segera mengeksekusi CREATE INDEX idx_inventory_id ON inventory(id); dan latensi langsung pulih ke angka normal di bawah 80ms.

8. Common Pitfalls, Anti-Patterns & Solusi

[!WARNING] Kesalahan Fatal 1: Mengirim Atribut Mengandung PII (Personally Identifiable Information)

  • Anti-Pattern: Menyimpan password, token JWT, email mentah, atau nomor kartu kredit ke dalam Span Attributes atau Baggage.
  • Dampak: Pelanggaran kepatuhan regulasi privasi data (GDPR, ISO 27001) dan kebocoran data sensitif ke dalam penyimpanan log/trace.
  • Solusi: Gunakan Span Processor kustom untuk menyaring (sanitize) atau melakukan masking pada atribut string sebelum dikirim ke exporter.

[!WARNING] Kesalahan Fatal 2: Kardinalitas Tinggi pada Atribut Trace

  • Anti-Pattern: Menjadikan ID pengguna yang unik (user_id) atau UUID transaksi penuh sebagai bagian dari Span Name atau Metric Label.
  • Dampak: Ledakan memori pada OpenTelemetry Collector dan Jaeger Backend karena tingginya kombinasi key yang unik, berujung pada Out of Memory (OOM) Crash.
  • Solusi: Jadikan ID unik tersebut sebagai Span Attribute (bukan bagian dari nama span atau metric dimensions).

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk memastikan infrastruktur observabilitas tidak menjadi beban bagi performa aplikasi, lakukan pengujian beban menggunakan perangkat k6 atau wrk.

Buat 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/process');
  check(res, {
    'status adalah 200': (r) => r.status === 200,
  });
  sleep(0.1);
}

Jalankan benchmark:

k6 run load-test.js

Parameter Tuning Lanjutan pada Collector (otel-collector-config.yaml):

  • Sesuaikan memory_limiter berdasarkan kapasitas RAM server Collector Anda (misal limit 2GB).
  • Atur batch.send_batch_size: 512 dan batch.timeout: 500ms untuk menyeimbangkan penggunaan CPU dan kecepatan pengiriman trace ke Jaeger tanpa mengorbankan throughput aplikasi utama.

10. Checklist Penerapan Produksi & Kesimpulan

Sebelum meluncurkan arsitektur Observabilitas OpenTelemetry dan Jaeger ke lingkungan produksi secara menyeluruh, pastikan checklist berikut telah diverifikasi:

  • Sampler Rate Terkonfigurasi: Tidak menggunakan AlwaysSample pada traffic tinggi; menerapkan Ratio-based atau Tail-based sampling.
  • Keamanan Transport: OTLP Collector dikonfigurasi menggunakan mTLS atau token autentikasi jika melintasi batas jaringan publik/cloud.
  • Sanitasi Data Sensitif: Memastikan tidak ada token, password, atau PII yang tercatat dalam Span Attributes.
  • Resource Limit Kubernetes: Memory limits dan CPU requests/limits telah diatur dengan benar pada deployment OTel Collector dan Jaeger.
  • Retensi Storage Jaeger: Kebijakan penghapusan data lama (TTL storage retention) telah diatur pada backend database Jaeger (Elasticsearch/Cassandra/BadgerDB) untuk mencegah kehabisan disk.
  • Alerting Integrasi: Jaeger atau Prometheus telah diintegrasikan dengan sistem notifikasi (PagerDuty/Slack) untuk mendeteksi lonjakan error rate secara proaktif.

Dengan menerapkan panduan komprehensif ini, tim SRE dan Platform Engineer Anda kini memiliki visibilitas penuh, presisi tinggi, dan kontrol total atas performa arsitektur web modern yang kompleks dan terdistribusi.

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