OBSERVABILITY MICROSERVICES DISTRIBUTED-SYSTEMS DEBUGGING LOCAL-DEVELOPMENT DEVELOPER-EXPERIENCE OPENTELEMETRY TRACING DEVOPS BACKEND TROUBLESHOOTING TOOLS DEVELOPMENT-ENVIRONMENT

Observabilitas Lokal untuk Microservices: Debugging Sistem Terdistribusi di Mesin Dev Anda

⏱️ 12 menit baca
👨‍💻

Observabilitas Lokal untuk Microservices: Debugging Sistem Terdistribusi di Mesin Dev Anda

1. Pendahuluan

Pernahkah Anda merasa frustrasi saat mencoba mencari tahu kenapa aplikasi microservices Anda bermasalah di lingkungan pengembangan lokal? Request masuk ke Service A, lalu entah kenapa Service C gagal, tapi Anda tidak tahu persis apa yang terjadi di antara keduanya. Ini adalah skenario umum yang dialami banyak developer ketika beralih dari aplikasi monolitik ke arsitektur microservices.

Debugging microservices secara lokal bisa menjadi mimpi buruk. Anda tidak lagi berurusan dengan satu proses yang bisa Anda pasangi debugger dengan mudah. Sebaliknya, Anda memiliki banyak service yang berkomunikasi melalui jaringan, seringkali secara asinkron. Kesalahan bisa terjadi di mana saja, dan melacak “perjalanan” sebuah request melalui beberapa service untuk menemukan akar masalahnya terasa seperti mencari jarum dalam tumpukan jerami.

Di sinilah observabilitas lokal berperan penting. Observabilitas bukan hanya untuk produksi; ia adalah alat yang sangat ampuh untuk meningkatkan pengalaman developer (DX) Anda. Artikel ini akan membahas bagaimana kita bisa menerapkan konsep Distributed Tracing menggunakan OpenTelemetry dan Jaeger di lingkungan pengembangan lokal untuk memecahkan misteri debugging microservices. Tujuannya adalah agar Anda bisa melihat dengan jelas apa yang terjadi pada setiap request, di setiap service, langsung dari mesin dev Anda.

2. Kenapa Debugging Lokal Microservices Itu Sulit?

Mari kita pahami dulu mengapa debugging microservices secara lokal jauh lebih kompleks dibanding monolit.

Monolit:

⚠️ Microservices:

Intinya, kita kehilangan kemampuan untuk melihat “gambaran besar” dari satu request di seluruh sistem. Kita butuh sebuah peta, dan peta itu adalah Distributed Tracing.

3. Memperkenalkan Distributed Tracing untuk Lingkungan Lokal

Distributed Tracing adalah teknik observabilitas yang memungkinkan kita melacak perjalanan sebuah request atau transaksi saat melewati berbagai service dalam sistem terdistribusi. Bayangkan seperti kartu identitas yang melekat pada setiap request, mengikuti ke mana pun ia pergi.

📌 Konsep Utama Distributed Tracing:

OpenTelemetry adalah proyek open-source yang menyediakan standar dan tooling untuk menginstrumentasi, menghasilkan, mengumpulkan, dan mengekspor data telemetri (logs, metrics, dan traces) dari aplikasi Anda. Ini adalah vendor-agnostic, artinya Anda bisa menggunakan OpenTelemetry untuk mengirim data ke berbagai backend observabilitas (Jaeger, Prometheus, Grafana Tempo, dsb.).

Untuk observabilitas lokal, kita akan menggunakan:

4. Setup Lingkungan Lokal dengan OpenTelemetry Collector dan Jaeger

Cara termudah untuk menjalankan Jaeger dan OpenTelemetry Collector secara lokal adalah dengan Docker Compose.

Pertama, buat file docker-compose.yaml di root direktori proyek Anda:

# docker-compose.yaml
version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686" # Jaeger UI
      - "4317:4317"   # OTLP gRPC receiver (untuk OpenTelemetry Collector)
      - "4318:4318"   # OTLP HTTP receiver (untuk OpenTelemetry Collector)
      - "14268:14268" # Jaeger HTTP receiver (untuk langsung dari aplikasi jika tidak pakai collector)
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    networks:
      - microservice-net

  otel-collector:
    image: otel/opentelemetry-collector:latest
    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
    depends_on:
      - jaeger
    networks:
      - microservice-net

networks:
  microservice-net:
    driver: bridge

Kemudian, buat file konfigurasi untuk OpenTelemetry Collector (otel-collector-config.yaml):

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  jaeger:
    endpoint: jaeger:14250 # Kirim data ke Jaeger Agent/Collector
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger]

Penjelasan:

Untuk menjalankannya, cukup buka terminal di direktori yang sama dan ketik:

docker compose up -d

Sekarang, Anda bisa mengakses Jaeger UI di http://localhost:16686.

5. Instrumentasi Aplikasi Anda

Sekarang, mari kita instrumentasi dua service sederhana: service-a (Node.js) yang memanggil service-b (Node.js).

5.1. Service B (Node.js - Express)

Buat folder service-b.

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

Buat file tracer.js untuk konfigurasi OpenTelemetry:

// service-b/tracer.js
const process = require('process');
const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const exporterOptions = {
  url: 'http://otel-collector:4317', // Kirim ke OpenTelemetry Collector
};

const traceExporter = new OTLPTraceExporter(exporterOptions);
const sdk = new opentelemetry.NodeSDK({
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

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 index.js untuk aplikasi Express:

// service-b/index.js
require('./tracer'); // Pastikan tracer di-load pertama
const express = require('express');
const app = express();
const port = 3001;

app.get('/data', (req, res) => {
  console.log('Service B received request');
  res.json({ message: 'Data from Service B' });
});

app.listen(port, () => {
  console.log(`Service B listening on port ${port}`);
});

5.2. Service A (Node.js - Express)

Buat folder service-a.

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

Buat file tracer.js (sama seperti service-b, hanya URL collector yang penting):

// service-a/tracer.js
// ... (isi sama persis dengan service-b/tracer.js)
const process = require('process');
const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const exporterOptions = {
  url: 'http://otel-collector:4317', // Kirim ke OpenTelemetry Collector
};

const traceExporter = new OTLPTraceExporter(exporterOptions);
const sdk = new opentelemetry.NodeSDK({
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

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 index.js untuk aplikasi Express:

// service-a/index.js
require('./tracer'); // Pastikan tracer di-load pertama
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;

app.get('/call-b', async (req, res) => {
  console.log('Service A received request, calling Service B');
  try {
    const response = await axios.get('http://service-b:3001/data'); // Panggil service-b
    res.json({ message: 'Call to Service B successful', data: response.data });
  } catch (error) {
    console.error('Error calling Service B:', error.message);
    res.status(500).json({ message: 'Error calling Service B', error: error.message });
  }
});

app.listen(port, () => {
  console.log(`Service A listening on port ${port}`);
});

5.3. Menjalankan Aplikasi dengan Docker Compose

Untuk menjalankan service-a dan service-b bersama Jaeger dan OTel Collector, tambahkan service-service ini ke docker-compose.yaml yang sudah ada:

# docker-compose.yaml (lanjutan)
version: '3.8'
services:
  # ... (jaeger dan otel-collector services seperti di atas)

  service-a:
    build: ./service-a
    ports:
      - "3000:3000"
    networks:
      - microservice-net
    environment:
      - OTEL_SERVICE_NAME=service-a # Nama service untuk tracing
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # Penting: Gunakan nama service otel-collector
    depends_on:
      - otel-collector
      - service-b

  service-b:
    build: ./service-b
    ports:
      - "3001:3001"
    networks:
      - microservice-net
    environment:
      - OTEL_SERVICE_NAME=service-b # Nama service untuk tracing
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # Penting: Gunakan nama service otel-collector
    depends_on:
      - otel-collector

networks:
  microservice-net:
    driver: bridge

Buat Dockerfile sederhana di masing-masing folder service-a dan service-b:

# service-a/Dockerfile & service-b/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "index.js"]

Sekarang, bangun dan jalankan semua service:

docker compose up --build -d

Pastikan semua kontainer berjalan dengan docker compose ps.

6. Membaca Trace di Jaeger UI

Sekarang, buka browser Anda dan akses http://localhost:3000/call-b. Ini akan memicu service-a untuk memanggil service-b.

Setelah itu, buka Jaeger UI di http://localhost:16686.

🎯 Langkah-langkah di Jaeger UI:

  1. Pilih Service: Di dropdown “Service”, pilih service-a.
  2. Cari Trace: Klik tombol “Find Traces”. Anda akan melihat daftar trace yang baru saja dibuat.
  3. Analisis Trace: Klik salah satu trace untuk melihat detailnya.

Anda akan melihat visualisasi hierarkis dari request Anda:

💡 Apa yang bisa Anda lihat:

Dengan ini, jika service-b mengalami masalah, Anda akan langsung melihat span yang gagal di Jaeger UI, lengkap dengan durasi dan potensi pesan error, tanpa harus menebak-nebak atau memeriksa log di banyak tempat.

7. Tips dan Best Practices untuk Observabilitas Lokal

Kesimpulan

Debugging microservices secara lokal tidak harus menjadi pengalaman yang menyakitkan. Dengan Distributed Tracing yang didukung oleh OpenTelemetry dan divisualisasikan oleh Jaeger, Anda mendapatkan visibilitas yang belum pernah ada sebelumnya ke dalam sistem terdistribusi Anda. Ini mengubah “black box” menjadi “glass box”, memungkinkan Anda untuk dengan cepat mengidentifikasi bottleneck, melacak error, dan memahami aliran data antar service.

Mulai sekarang, jadikan observabilitas lokal sebagai bagian integral dari alur kerja pengembangan Anda. Investasi awal dalam setup ini akan sangat menghemat waktu dan mengurangi frustrasi Anda dalam jangka panjang, meningkatkan produktivitas dan kepuasan Anda sebagai developer.

🔗 Baca Juga