在微服務架構中,一個使用者請求可能會流經數十個獨立的服務。當發生延遲或錯誤時,要找出問題的根源就像大海撈針。這就是「分散式追蹤 (Distributed Tracing)」發揮作用的地方。
今天,我們將入門分散式追蹤的世界,並學習如何使用 Grafana Tempo 這款專為追蹤而生的高效能後端系統。
分散式追蹤是一種用來監控和分析橫跨多個服務的請求流程的方法。它幫助我們視覺化一個請求的完整生命週期。
(圖片來源: Grafana Labs)
透過分析 Trace 中各個 Span 的耗時與關係,我們可以輕易地發現系統中的效能瓶頸。
Grafana Tempo 是一個開源、高擴展性、低成本的分散式追蹤後端。它的設計哲學是「簡單且海量」。
接下來,我們將使用 Docker Compose 建立一個完整的本地環境,包含:
/day24
├── app/
│ ├── main.py
│ └── requirements.txt
├── docker-compose.yml
├── tempo.yaml
├── otel-collector.yaml
└── grafana-datasource.yaml
首先,在 app/requirements.txt
中加入必要的套件:
# app/requirements.txt
fastapi
uvicorn
opentelemetry-api
opentelemetry-sdk
opentelemetry-exporter-otlp
opentelemetry-instrumentation-fastapi
然後,撰寫 app/main.py
,並使用 OpenTelemetry 進行自動埋點。
# app/main.py
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# 1. 設定服務名稱
resource = Resource(attributes={
"service.name": "fastapi-demo-app"
})
# 2. 設定 Tracer Provider
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)
# 3. 設定 OTLP Exporter,將數據發送到 OTEL Collector
otlp_exporter = OTLPSpanExporter(
endpoint="otel-collector:4317", # Collector 的 gRPC 端口
insecure=True
)
# 4. 設定 Span Processor
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)
# 建立並自動埋點 FastAPI App
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)
@app.get("/")
def read_root():
return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
# 可以在特定端點內建立自訂的 Span
with tracer.start_as_current_span("process_item") as span:
span.set_attribute("item.id", item_id)
# 模擬一些工作
import time
time.sleep(0.1)
return {"item_id": item_id}
otel-collector.yaml
: 設定 Collector 接收 OTLP 數據,並將其匯出到 Tempo。
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
exporters:
otlp:
endpoint: "tempo:4317" # Tempo 的 gRPC 端口
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
tempo.yaml
: 設定 Tempo 接收 OTLP 數據,並將追蹤儲存在本地檔案系統。
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocol:
grpc:
endpoint: 0.0.0.0:4317
storage:
trace:
backend: local
local:
path: /tmp/tempo/blocks
grafana-datasource.yaml
: 自動為 Grafana 配置 Tempo 資料來源。
apiVersion: 1
datasources:
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
isDefault: true
docker-compose.yml
version: '3.8'
services:
app:
build: ./app
ports:
- "8000:8000"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector.yaml"]
volumes:
- ./otel-collector.yaml:/etc/otel-collector.yaml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
depends_on:
- tempo
tempo:
image: grafana/tempo:latest
command: ["-config.file=/etc/tempo.yaml"]
volumes:
- ./tempo.yaml:/etc/tempo.yaml
- ./tempo-data:/tmp/tempo
ports:
- "3200:3200"
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- ./grafana-datasource.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
depends_on:
- tempo
別忘了在 app
目錄下建立一個簡單的 Dockerfile
# app/Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
day24
目錄下執行 docker-compose up --build
。curl http://localhost:8000/
curl http://localhost:8000/items/123
http://localhost:3000
。Explore
。Tempo
。Search
分頁下的 Service Name
下拉選單中,你應該能看到 fastapi-demo-app
。點擊 Run query
,你就能看到剛剛產生的 Trace 列表!點擊任一個 Trace,你就可以看到請求的完整火焰圖 (Flame Graph),清楚地呈現了每個 Span 的耗時與呼叫關係。
今天,我們成功地踏出了分散式追蹤的第一步。我們理解了其核心概念,並親手部署了 Tempo,還使用 OpenTelemetry 檢測了一個 FastAPI 應用。明天,我們將探索更強大的功能:如何在 Grafana 中將日誌與追蹤關聯起來。