這個實驗室在證明什麼問題
「用 OpenTelemetry 就好」跟「用 Prometheus + Grafana 就好」常常在同一句對話裡被講出來,讓人誤以為兩者是互相競爭的選項。其實不是 — 它們是不同的「層」。把它們混為一談,正是為什麼「為什麼我的 Prometheus + Grafana 組合看不到 trace」會是個結構性、無解的問題:那個技術組合裡根本沒有任何東西能存 trace。
最少需要具備的概念:
- Telemetry signal model(遙測訊號模型)(
telemetry-signal-model)— trace 和 metric 是為了回答不同問題而存在的不同資料形狀,不是同一種「telemetry」大雜燴。 - OpenTelemetry API/SDK 分離(
opentelemetry-api-sdk-separation)— 應用程式呼叫的是與廠商無關的 API,程式碼裡從來不會指名任何後端(backend)。 - OTLP 作為廠商中立協定(
otlp-vendor-neutral-telemetry-protocol)— 把訊號從 SDK 送到 Collector、再送到後端的傳輸格式。 - Collector pipeline 架構(
collector-pipeline-architecture)— 每種訊號類型各自有一條具名、可設定的路徑:receiver → processor → exporter。
系統架構 — 五層,一張圖
這張圖要讓兩件事變得無可否認:(1) App 從來不會指名 Prometheus、Tempo 或 Grafana — 它只知道 OTLP 和一個 Collector 的位址;(2) Grafana 完全落在分界線下方,只從兩個它從不寫入的後端讀資料。「你的核心任務」標記以上的一切都屬於 OTel。metrics/traces 分岔以下的每一個方塊,都是 OTel 路由進去、而不是取代掉的單一訊號專用後端。
每個工具負責什麼 — 責任邊界在哪裡交接
| 層 / 工具 | 負責什麼 | 不負責什麼 |
|---|---|---|
| App + OTel SDK | 透過廠商中立的 API 建立 span 與 metric instrument;編碼並送出 OTLP | 不知道 telemetry 最後會進哪個後端 — 那是 Collector config 的事,App 程式碼完全不用管 |
| OTLP | 提供共通的傳輸格式,讓任何 OTel SDK 都能對任何支援 OTLP 的 receiver 說話 | 不負責「意義」— 兩個系統可以成功交換合法的 OTLP bytes,卻仍然對某個欄位的意思有分歧,除非共用同一套 semantic conventions |
| OpenTelemetry Collector | 接收 OTLP,再透過具名的 service.pipelines 把每種訊號類型路由到為它而建的 exporter | 不負責長期儲存或查詢 telemetry — 它是路由器/處理器,不是資料庫 |
| Prometheus | 只儲存並查詢 metrics,透過 pull 的方式向 Collector 的 prometheus exporter 頁面抓取 | 不負責 trace 或 log — 它的 exporter 元件根本沒有 traces/logs 的 consumer(見下方失敗案例) |
| Tempo | 只儲存並查詢 traces,直接以 OTLP 從 Collector 接收 | 不負責 metrics 或儀表板 |
| Grafana | 透過各自獨立的 datasource 外掛查詢 Prometheus 與 Tempo,渲染出面板/Explore 畫面 | 不接收 OTLP、不儲存 telemetry、也不做任何路由決策 — 資料在進入後端「之前」是怎麼來的,Grafana 完全不在意 |
學習者的步驟與檢查點
- 先讀懂 scaffold — 從
collector/otel-collector-config.yaml開始,再快速瀏覽app/server.js。 - 在動手跑之前,先把 5 個檢查點的預測 寫下來。
- 在
service.pipelines下把兩行exporters: []填好 — 這是你唯一要編輯的地方。 - 快速檢查:
./scripts/validate-config.sh— 不需要整套 stack,填對了就會 exit 0。 - 完整執行:
./scripts/run.sh— 啟動 app、Collector、Prometheus、Tempo、Grafana。 - 觀察:在 Grafana 透過 Tempo datasource 看到一筆 trace,也透過 Prometheus datasource(以及 Prometheus 自己的介面)看到持續上升的計數器。
- 執行失敗案例,讀懂錯誤訊息,再把你的 config 復原成可運作的版本。
每個階段的可觀察訊號:validator 的 exit code 與錯誤訊息文字、docker compose ps 的容器健康狀態、Prometheus 自己的查詢介面,以及 Grafana Explore 對兩個 datasource 的畫面。
失敗案例 — 該檢查什麼(不是該怎麼修)
把 traces 接到 prometheus exporter
有一份預先寫好、刻意寫錯的 config,把 traces pipeline 接到 prometheus exporter,而不是 otlp/tempo。用 ./scripts/failure-case.sh 執行它。Collector 並不會先啟動、再默默把 trace 丟掉 — 它根本不會啟動:在任何 receiver port 打開之前就直接失敗,並以非零結束碼在 stderr 印出一則 exporter/訊號不相容的錯誤。
該檢查什麼:那則錯誤訊息的確切文字,以及為什麼它發生在「啟動當下」,而不是事後才變成「怎麼一直沒有 trace 進來」這種現象。這個時間點正是重點 — 對 prometheus 這種訊號受限的 exporter 而言,訊號與後端配錯是一個「config 建置階段」的失敗,不是一個會默默流失資料的執行期 bug。
確切的錯誤文字,以及更深一層「這個失敗是不是雙向對稱」的細節,都放在這個實驗室的私有解答(不公開)裡 — 請在自己跑過一次、並且跑過 lab-review.md 之後再對答案。
驗收標準
./scripts/validate-config.sh對你填好的 config 執行後 exit 0。docker compose ps顯示全部五個容器都在執行中。- 在 Grafana 透過 Tempo datasource,至少能看到一筆
otel-lab-app的 trace。 otel_lab_requests_total在 Prometheus 本身、以及 Grafana 的 Prometheus datasource 上都看得到,而且持續上升。./scripts/failure-case.sh以非零結束碼結束,並印出 exporter/訊號不相容的錯誤。- 你能用一句話說出 OTel/Prometheus/Tempo/Grafana 各自坐落在哪一層。
前置需求、時間、成本與範圍
| 需要 | Docker + Docker Compose(v2 的 docker compose 語法)。不需要雲端帳號、不需要 API key、沒有費用 — 全部在本機執行。 |
| 預估時間 | 45–75 分鐘。 |
| 成本風險 | 沒有 — 全部是本機容器,不涉及計費 API。 |
| 沒有 Docker 時的退路 | 當本機沒有 Docker 時,./scripts/validate-config.sh 會用一個獨立的 otelcol-contrib執行檔(可自動下載、不需要 root 權限)來驗證你的 service.pipelines 連線 — 雖然少了完整的 Grafana / Prometheus / Tempo 體驗,但「我的路由設定到底建不建得起來」這個核心回饋迴圈還是拿得到。 |
為什麼 logs 和 OTTL 不是第二個必做任務
Logs 是 OTel 的第三種訊號,會遵循這裡教的完全相同模式:一個 loki exporter 定義,加上一個 logs: { receivers: [otlp], exporters: [loki] } pipeline — 同一份檔案、同樣的形狀,跟你已經接好的那兩個一模一樣。如果把一個可執行的 Loki 後端也塞進這個實驗室,篇幅會膨脹成三倍,卻只是在教一個你已經證明會了兩次的模式。OTTL(宣告式的 pipeline 內轉換 — 例如在資料經過時遮蔽或改寫欄位)則是完全不同的關注點:它改變的是「pipeline 裡面裝了什麼」,不是「訊號走哪一條 pipeline」。等路由這一課學會之後,這兩者都只是一段話就能講完的延伸,不需要另外變成核心任務。