Lab: OpenTelemetry + Jaeger
Lab สุดท้ายและมันที่สุด! คุณจะ instrument สอง service ด้วย OpenTelemetry แล้วดู trace แบบ waterfall ที่ request วิ่งข้าม service — เห็นกับตาว่า tracing ตอบคำถาม "ช้าตรงไหน" ได้ยังไง
ไฟล์พร้อมรันที่
docker/tracing/lab-traces-01/— มีgateway,order-service(แต่ละตัว auto-instrument ด้วย OTel) และ Jaeger all-in-one
Stack: gateway → order-service → (จำลอง DB) ทุก span ส่งเข้า Jaeger
ขั้นที่ 0: เข้าใจ flow
ผู้ใช้ → GET /checkout → [gateway] ──http──▶ [order-service] ──▶ (2 fake DB queries)
│ │
└── ส่ง trace (OTLP) ──┴──▶ [Jaeger] ◀── ดูที่ :16686
gateway เรียก order-service ผ่าน HTTP — OTel จะส่ง traceparent header ให้อัตโนมัติ (context propagation จากบทที่ 40) ทำให้ span ของทั้งสอง service อยู่ใน trace เดียวกัน
ขั้นที่ 1: ดูจุดสำคัญในโค้ด
gateway/tracing.js — auto-instrumentation ทั้งหมดอยู่ที่นี่ (บทที่ 41):
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter(), // ส่ง OTLP ไป Jaeger
instrumentations: [getNodeAutoInstrumentations()], // auto: express + http
});
sdk.start();
โหลดก่อนแอปด้วย node -r ./tracing.js app.js — เท่านี้ทุก HTTP request มี span เอง
gateway/app.js — เพิ่ม manual span ห่อ business logic:
const span = tracer.startSpan("checkout-flow");
const r = await fetch(`${ORDER_URL}/order`); // fetch แนบ traceparent อัตโนมัติ
span.setAttribute("order.id", data.orderId);
span.end();
ขั้นที่ 2: รัน stack
cd docker/tracing/lab-traces-01
docker compose up -d --build
docker compose ps
ครั้งแรก build จะนานหน่อยเพราะต้อง
npm installOTel packages (ค่อนข้างเยอะ) รอสักครู่
ขั้นที่ 3: ยิง request ให้เกิด trace
# ยิง checkout สัก 20 ครั้ง
for i in $(seq 1 20); do curl -s localhost:3000/checkout > /dev/null; echo "req $i"; done
แต่ละครั้งจะสร้าง trace ที่ผ่าน gateway → order-service → fake DB queries
ขั้นที่ 4: เปิด Jaeger ดู waterfall 🎉
- ช่อง Service เลือก
gateway - กด Find Traces
- คลิกที่ trace อันหนึ่ง
คุณจะเห็น waterfall view แบบนี้ (ตามบทที่ 40):
checkout-flow [gateway] ├──────────────────┤
└─ GET /order [gateway→http] │ ├───────────────┤
└─ GET /order [order-service] │ ├──────────────┤
├─ SELECT inventory [order-service] │ ├────┤
└─ INSERT order [order-service] │ ├───┤
สังเกตว่า span จาก สอง service ต่างกัน (gateway กับ order-service) อยู่ใน trace เดียวกัน เรียงตามเวลาจริง — นี่คือ context propagation ทำงาน! ทั้งที่เราไม่ได้เขียนโค้ดส่ง trace_id เองเลย OTel จัดการให้ผ่าน
traceparentheader
ขั้นที่ 5: หา trace ที่ช้าผิดปกติ
จำได้ว่า order-service จำลองความช้า 10% ของ request (หน่วง 500ms) ลองหา:
- ใน Jaeger ตั้ง Min Duration =
400msแล้ว Find Traces - เปิด trace ที่ช้า — จะเห็น span ที่กินเวลานานผิดปกติ ทำให้ระบุได้ทันทีว่าช้าตรงไหน
นี่คือ workflow จริงตอน debug: metric alert ว่า "p99 latency พุ่ง" (บทที่ 10) → เข้า Jaeger กรอง trace ที่ช้า → เห็น span ที่เป็นตัวการ → รู้ว่าต้องไปแก้ service ไหน (ลด MTTR จากบทที่ 00)
ขั้นที่ 6: สำรวจ attributes ของ span
คลิกที่ span แต่ละอันใน Jaeger เพื่อดู attributes (tags):
http.method,http.route,http.status_code(จาก auto-instrumentation)db.system,db.statement(จาก fake DB span ที่เราใส่เอง)order.id(attribute ที่เราเพิ่มใน manual span)
attribute พวกนี้คือ context ที่ทำให้ trace มีประโยชน์ตอน debug
ขั้นที่ 7 (คิดต่อ): เชื่อม 3 pillars
ลองจินตนาการต่อ (จะทำจริงในบทที่ 50):
- ถ้าแต่ละ log ของ service ใส่
trace_idเดียวกับ span (บทที่ 30) → จาก Jaeger กระโดดไปอ่าน log ของ trace นั้นได้ - ถ้า metric duration มี exemplar ลิงก์ไป trace → จากกราฟ latency คลิกไป trace ตัวอย่างได้เลย
ทำความสะอาด
docker compose down
✅ Checklist
- เข้าใจว่า
tracing.js+node -rทำ auto-instrumentation ยังไง - เห็น trace ข้าม 2 service ใน waterfall เดียว
- เข้าใจว่า context propagation ทำงานผ่าน traceparent header (ไม่ต้องเขียนเอง)
- หา trace ที่ช้าด้วย Min Duration filter
- อ่าน span attributes เพื่อ debug
สรุป
คุณเพิ่งเติม เสาที่สาม (Traces) ให้ repo ครบ! ตอนนี้คุณมีครบทั้ง 3 pillars:
- 📈 Metrics — Prometheus + Grafana (Module 2)
- 🪵 Logs — ELK (labs เดิมของคุณ + Module 3)
- 🔬 Traces — OpenTelemetry + Jaeger (Module 4)
🎉 จบ Module 4! เข้าสู่ Module สุดท้าย — Hero level: เอา 3 เสามารวมร่างและใช้จริงตอนระบบเกิดเหตุ