Jaeger 入門:用 OpenTelemetry 串起分散式追蹤

一個請求打進來,經過 gateway、好幾個 service、資料庫,最後回應。哪一段慢了?log 散在各台機器很難拼。分散式追蹤把這條請求的每一段串成一棵 span 樹,Jaeger 就是拿來收集與檢視這棵樹的工具。這篇講 Jaeger 是什麼、OpenTelemetry 為什麼是關鍵、gortex 的 Tracer 介面怎麼接上,以及怎麼用 Docker 在本機快速跑起來。

更正(2026-11-02 18:45):補充 OTelTracerAdapter 當一般 Tracer 用時不會匯出 OTel span,要改用 StartSpanWithOptions 並結束 SpanAdapter,關閉前呼叫 TracerProvider.Shutdown(修正建議:Codex(gpt-6-astra),Claude 驗證);註明 compose 範本用的是已 EOL 的 Jaeger v1 image(修正建議:Claude,Codex(gpt-6-astra)驗證)。

分散式追蹤要解什麼

微服務下,一個外部請求會散成內部好幾個呼叫,出問題時看單台的 log 只看到片段。分散式追蹤的單位是 span:一段有名字、有起訖時間的工作,像一次 HTTP handler、一次 DB query。span 之間有 parent / child 關係,串起來就是一個 trace,一棵完整的請求樹,看這棵樹就知道時間花在哪、哪一段報錯。

關鍵是 trace context 要能跨服務傳遞:上游把 trace ID 放進 header,下游接著用,這條 trace 才接得起來。gortex 在程式內部用 context 傳 span(見可觀測性那篇),跨服務則靠 OTel 的 propagation。

Jaeger 是什麼

Jaeger 是 CNCF 的分散式追蹤平台,負責收集、儲存、查詢 span,並提供一個 Web UI 把 trace 畫成時間軸瀑布圖。正式部署會拆成幾個角色:collector 收 span、儲存後端存資料、query 加 UI 給你查。

開發階段不用拆這麼細,官方有一個 all-in-one image,把 collector、query、UI 包在一個 container 裡、用記憶體存資料,起來就能用,重開就清空,正好適合本機開發。

OpenTelemetry:共通語言

這裡的關鍵字是 OpenTelemetry(OTel)。它是一套廠商中立的可觀測性標準:一組 instrumentation 的 API / SDK,加上一個傳輸協定 OTLP。你的程式只對 OTel 產生 span,要送去哪個後端是另一回事。

重點在於 instrumentation 與後端解耦。今天送 Jaeger,明天想換 Grafana Tempo 或 Zipkin,改的是 exporter 設定,不是你滿地的 instrumentation 程式。

gortex 把這個解耦做進框架:它只依賴一個 Tracer 介面,附一個 OTelTracerAdapter 把 gortex span 轉成 OTel span(severity 對映、tags、status 都搬過去)。要注意的是,adapter 當一般 Tracer 用時(StartSpan、FinishSpan)只會產生 gortex span,不會匯出 OTel span;要讓 span 進到 Jaeger,得用 StartSpanWithOptions 開 span,拿回的 SpanAdapter 用 defer span.End() 結束,tags 與 status 也透過它設定,服務關閉前再呼叫 TracerProvider.Shutdown,把 batcher 裡還沒送出的 span 送完。設定大致長這樣:

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"

    gotel "github.com/yshengliao/gortex/observability/otel"
    "github.com/yshengliao/gortex/observability/tracing"
)

// 標準 OTel SDK:OTLP exporter 指到 Jaeger 的 4317
exp, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithEndpoint("localhost:4317"),
    otlptracegrpc.WithInsecure(),
)
otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp)))

// gortex 端:包一層 adapter;要匯出的 span 用 StartSpanWithOptions 開、用 SpanAdapter.End 結束
tracer := gotel.NewOTelTracerAdapter(tracing.NewSimpleTracer(), "gortex")

exporter 是標準 OTel SDK 的東西,不是 gortex 自己發明的;gortex 只負責把自家 span 轉成 OTel span。

用 Docker 在 OrbStack 上快速起

最快的方式是 compose,用現成範本(jaeger,跑 jaegertracing/all-in-one:latest)。在 OrbStack 上 docker compose up -d,幾秒就好:

git clone https://github.com/yshengliao/docker-compose-templates
cd docker-compose-templates/jaeger
docker compose up -d

幾個會用到的 port:

  • 16686:Jaeger UI,瀏覽器開 http://localhost:16686 就能查 trace。
  • 4317:OTLP gRPC、4318:OTLP HTTP,你的程式 exporter 指這裡。
  • 9411:Zipkin 相容端點。

範本已經設了 COLLECTOR_OTLP_ENABLED=true,OTLP 收 trace 預設開著。把上面那段的 exporter 指到 localhost:4317,發幾個請求,UI 上就能看到 trace 一條條進來。

這個範本用的是 Jaeger v1 的 image,官方下載頁註明 v1 已在 2025-12-31 終止支援(EOL),v2 改用單一的 jaegertracing/jaeger image;要遷到 v2,OTLP receiver、port 與儲存設定得照 v2 文件重新核對,不能只換 image 名稱、沿用上面的 v1 環境變數。

換成 Grafana 也一樣

不想用 Jaeger UI,想統一在 Grafana 看?同一套 OTel instrumentation 不用動。Grafana 這邊用 Tempo 當 trace 後端,Tempo 一樣收 OTLP:把 exporter 的 endpoint 從 Jaeger 改成 Tempo,再在 Grafana 接 Tempo data source,trace 就出現在 Grafana 裡,還能跟 metrics、log 並排看。

Jaeger、Grafana Tempo 與 Zipkin 都能接在同一套 OTel instrumentation 後面。gortex 只依賴 adapter 介面,所以換 tracing backend 是維運設定的變動,不用重寫應用程式。

補充筆記

  • 分散式追蹤的單位是 span,串成 trace(請求樹);要靠 trace context 跨服務傳遞才接得起來。
  • Jaeger 是 CNCF 的追蹤平台,all-in-one image 把 collector / query / UI 包成一個 container,最適合本機開發。
  • OpenTelemetry 是廠商中立標準(API/SDK 加 OTLP),讓 instrumentation 與後端解耦;gortex 用 Tracer 介面加 OTelTracerAdapter 接上。
  • 用 jaeger 範本 docker compose up -d:UI 在 16686,OTLP 在 4317 / 4318,exporter 指過去即可。
  • 後端可換成 Grafana Tempo(一樣收 OTLP),instrumentation 一行不動,這是 OTel 標準加 adapter 模式的好處。

原始碼與更多框架細節:yshengliao/gortex。


Sheng 撰寫,Claude 協助文字與範例 · 環境 Jaeger all-in-one + OpenTelemetry + Docker(OrbStack) · 列入 20260613 blog 翻新計劃,新漆未乾。