Jaeger เป็นหนึ่งใน distributed tracing platform ที่ได้รับความนิยมสูงสุดในกลุ่ม cloud-native ด้วยสถาปัตยกรรมที่ยืดหยุ่น รองรับ storage หลายรูปแบบ และมี UI ที่ช่วย visualize trace ได้ดี บทความนี้จะพาไปติดตั้ง Jaeger แบบ all-in-one สำหรับ dev, setup production-grade ด้วย collector แยก, เลือก storage backend ที่เหมาะกับ scale, และเชื่อมต่อ application ผ่าน OpenTelemetry
เนื้อหาครอบคลุมตั้งแต่การรัน docker-compose สำหรับทดสอบ, การ deploy บน Kubernetes ด้วย Operator, ไปจนถึงการตั้ง sampling strategy และ tips สำหรับ operate ระบบใน production จริง
สถาปัตยกรรมของ Jaeger
Jaeger ประกอบด้วย 4 component หลัก: SDK/Instrumentation (ฝังใน application, สร้าง span), Collector (รับ span, validate, index, เขียนเข้า storage), Storage (Cassandra, Elasticsearch, Badger, Kafka buffer), และ Query + UI (ดึงข้อมูลมาแสดง) สำหรับ deployment ขนาดเล็กสามารถรวมทุกอย่างใน binary เดียวแบบ all-in-one ได้
ในรุ่นใหม่ (เริ่มจาก v2) Jaeger ใช้ OpenTelemetry Collector เป็น ingestion pipeline รับ span format ทั้ง OTLP, Jaeger native, และ Zipkin ได้ในตัวเดียว — ลดความซับซ้อนในการ integrate กับ stack ที่หลากหลาย
All-in-One Setup ด้วย Docker
วิธีที่เร็วที่สุดในการเริ่มต้นคือ run image all-in-one ที่รวม collector, storage (in-memory หรือ Badger), query, UI ไว้ใน container เดียว เหมาะสำหรับ dev environment, workshop, หรือการทดสอบ instrumentation:
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-e COLLECTOR_OTLP_ENABLED=true \
jaegertracing/all-in-one:latest
# เปิด UI ที่ http://localhost:16686
Port ที่ควรรู้: 16686 คือ UI, 4317/4318 คือ OTLP gRPC และ HTTP, 14250 คือ gRPC สำหรับ Jaeger native, 14268 คือ HTTP สำหรับ legacy client การเปิด COLLECTOR_OTLP_ENABLED=true ทำให้รับ trace จาก OpenTelemetry SDK ได้โดยตรง
Production Setup ด้วย docker-compose
สำหรับ production ควรแยก storage ออกจาก Jaeger binary ตัวอย่างการ deploy Jaeger + Elasticsearch ด้วย docker-compose:
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
volumes:
- es_data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
jaeger-collector:
image: jaegertracing/jaeger-collector:latest
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
- COLLECTOR_OTLP_ENABLED=true
ports:
- "4317:4317"
- "4318:4318"
depends_on:
- elasticsearch
jaeger-query:
image: jaegertracing/jaeger-query:latest
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
ports:
- "16686:16686"
depends_on:
- elasticsearch
volumes:
es_data:
Collector และ Query ทำงานแยกกัน ทำให้สามารถ scale collector ตาม ingestion throughput ได้โดยไม่กระทบ UI หาก traffic เยอะควรเพิ่ม Kafka เป็น buffer หน้า collector เพื่อกัน spike ที่อาจทำให้ ES ตาม index ไม่ทัน
Deploy บน Kubernetes ด้วย Operator
Jaeger Operator ช่วยให้การ deploy บน Kubernetes ง่ายขึ้นมาก โดยใช้ Custom Resource กำหนด instance ที่ต้องการ:
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: prod-tracing
namespace: observability
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://es-master:9200
collector:
replicas: 3
resources:
limits:
cpu: 1
memory: 2Gi
query:
replicas: 2
sampling:
options:
default_strategy:
type: probabilistic
param: 0.1
Strategy production จะสร้าง collector และ query เป็น deployment แยก, allInOne ใช้สำหรับ dev, และ streaming จะเพิ่ม Kafka เข้ามาเป็น buffer กรณี workload หนัก
เชื่อมต่อ Application ผ่าน OpenTelemetry
วิธีแนะนำคือใช้ OpenTelemetry SDK ที่รองรับการส่ง span ผ่าน OTLP ไปยัง collector endpoint ของระบบ tracing ตัวอย่าง Node.js application:
// telemetry.js
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 sdk = new NodeSDK({
resource: new Resource({
'service.name': 'order-api',
'service.version': '1.2.0',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://jaeger-collector:4317',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
auto-instrumentations-node จะติด instrumentation ให้ HTTP, Express, PostgreSQL, Redis, และอื่น ๆ อัตโนมัติ โดยไม่ต้องแก้ code ทุกจุด หลัง run application ไม่กี่วินาทีก็จะเห็น trace บน UI ทันที
Sampling Strategy
Jaeger รองรับ sampling ทั้งฝั่ง client (SDK) และ server (collector) แบบ adaptive — กำหนด rate ต่อ service ได้แยกกัน เช่น service ที่มี traffic สูงเก็บ 1%, service สำคัญที่ traffic ต่ำเก็บ 100% ตัวอย่าง config:
{
"default_strategy": {
"type": "probabilistic",
"param": 0.05
},
"per_service_strategies": [
{
"service": "payment-service",
"type": "probabilistic",
"param": 1.0
},
{
"service": "search-api",
"type": "ratelimiting",
"param": 100
}
]
}
Best Practices
- แยก collector ออกจาก query ในการ deploy เพื่อ scale แยกตาม workload
- ใช้ Kafka เป็น buffer หน้า storage หาก ingestion rate สูงหรือ storage อาจล่มชั่วคราว
- ตั้ง retention policy ของ storage ให้เหมาะสม — trace เก่าเกินสัปดาห์มักใช้ประโยชน์น้อย
- Monitor collector ด้วย Prometheus — metric สำคัญคือ
jaeger_collector_spans_receivedและjaeger_collector_queue_length - ใช้
service.nameและdeployment.environmentattributes เพื่อแยก production vs staging ใน UI - เลือก storage ตาม scale — Badger เหมาะกับ < 1k span/s, Elasticsearch กลาง, Cassandra สำหรับ high throughput
- ตั้ง alert เมื่อ dropped span rate เกิน threshold — บ่งชี้ collector overload
สรุป
การ setup Jaeger สามารถเริ่มได้จาก all-in-one image ใน 1 คำสั่ง แล้วค่อยขยายเป็นสถาปัตยกรรมแยก component เมื่อ traffic เพิ่มขึ้น การเชื่อมกับ OpenTelemetry SDK ทำให้ instrument ได้ง่ายและไม่ผูกติดกับ vendor เดียว
สำหรับ production ที่มี traffic สูง ควรเน้นการ scale collector, เพิ่ม Kafka buffer, และตั้ง sampling strategy ให้เหมาะกับแต่ละ service เพื่อควบคุมต้นทุน storage พร้อมยังคงได้ข้อมูลที่มีประโยชน์ต่อการ debug

