痛点
微服务架构下,一个用户请求可能穿越 10+ 个服务。当延迟飙升或错误率突增时,仅靠日志(Loki)和指标(Mimir/Prometheus)很难定位到底是哪个服务、哪个调用链出了问题。你需要的是 分布式链路追踪(Distributed Tracing)。
传统方案如 Jaeger、Zipkin 依赖 Elasticsearch 或 Cassandra 做存储后端,运维复杂且成本高昂——尤其在高流量场景下,存储费用能占到可观测性总成本的 40% 以上。
Grafana Tempo 的核心优势:只用对象存储(S3/GCS/MinIO)做后端,无需索引,成本降低一个数量级,且与 Grafana 生态(Loki、Mimir、Alloy)无缝集成,补齐可观测性三大支柱的最后一块拼图。
方案
采用 Tempo Distributed 模式 部署到 Kubernetes,配合 Grafana Alloy 作为 Trace 收集器,应用侧通过 OpenTelemetry SDK 上报,存储使用 S3(或 MinIO)。架构如下:
App (OTel SDK) → Grafana Alloy (OTel Collector) → Tempo Distributor → Tempo Ingester → S3
↓
Grafana ← Tempo Query Frontend ← Tempo Querier → S3
关键组件职责: - Distributor:接收 Span,按 Trace ID 哈希分发到 Ingester - Ingester:内存聚合 Trace,定期 Flush 为 Block 写入对象存储 - Compactor:后台合并小 Block,优化查询性能 - Querier:从 Ingester(近期数据)和对象存储(历史数据)拉取 Trace - Query Frontend:分片大查询,缓存结果
实操步骤
Step 1:Helm 部署 Tempo Distributed
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
cat > tempo-values.yaml << 'EOF'
# Tempo Distributed mode values
tempo:
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.ap-northeast-1.amazonaws.com
region: ap-northeast-1
wal:
path: /var/tempo/wal
block:
version: vParquet4 # 最新列式格式,查询性能提升 2-5x
# 全局配置
global_overrides:
max_bytes_per_trace: 5000000 # 单条 Trace 最大 5MB
metrics_generator:
enabled: true
storage:
path: /var/tempo/wal-metrics
remote_write:
- url: http://mimir-distributor:8080/api/v1/push # Trace 生成 RED 指标推送到 Mimir
# 各组件副本数(中等流量参考)
distributor:
replicas: 3
resources:
requests:
cpu: 500m
memory: 512Mi
ingester:
replicas: 3
persistence:
enabled: true
size: 20Gi # WAL 磁盘,建议 SSD
resources:
requests:
cpu: "1"
memory: 2Gi
querier:
replicas: 2
resources:
requests:
cpu: 500m
memory: 1Gi
queryFrontend:
replicas: 2
compactor:
replicas: 1
resources:
requests:
cpu: "1"
memory: 2Gi
# 采样率控制(Alloy 侧做 tail-based sampling 更推荐)
overrides:
ingestion_rate_limit_bytes: 15000000 # 15MB/s per tenant
ingestion_burst_size_bytes: 20000000
EOF
helm install tempo grafana/tempo-distributed \
-n observability --create-namespace \
-f tempo-values.yaml
Step 2:配置 Grafana Alloy 采集 Trace
在已有的 Alloy 配置中添加 OTel Receiver 和 Tempo Exporter:
// 接收 OTel gRPC 协议的 Trace 数据
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
output {
traces = [otelcol.processor.batch.default.input]
}
}
// 批量处理,减少网络开销
otelcol.processor.batch "default" {
timeout = "5s"
send_batch_size = 1000
output {
traces = [otelcol.processor.tail_sampling.default.input]
}
}
// Tail-based Sampling:只保留有价值的 Trace
otelcol.processor.tail_sampling "default" {
decision_wait = "10s"
num_traces = 100000
policy {
name = "errors"
type = "status_code"
status_code {
status_codes = ["ERROR"]
}
}
policy {
name = "slow-requests"
type = "latency"
latency {
threshold_ms = 2000
}
}
policy {
name = "random-sample"
type = "probabilistic"
probabilistic {
sampling_percentage = 10
}
}
output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}
// 发送到 Tempo Distributor
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo-distributor.observability.svc.cluster.local:4317"
tls {
insecure = true
}
}
}
Step 3:应用侧接入 OpenTelemetry(Python 示例)
pip install opentelemetry-api opentelemetry-sdk \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation-fastapi \
opentelemetry-instrumentation-requests \
opentelemetry-instrumentation-sqlalchemy
# tracing.py - 应用初始化代码
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
def init_tracing(service_name: str):
resource = Resource.create({"service.name": service_name})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(
endpoint="grafana-alloy.observability.svc.cluster.local:4317",
insecure=True,
)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
# main.py
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
app = FastAPI()
init_tracing("order-service")
FastAPIInstrumentor.instrument_app(app)
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
# 自动生成 Span,包含 HTTP method、status code、latency
...
Step 4:Grafana 中配置 Tempo 数据源并关联 Loki
在 Grafana 中添加 Tempo 数据源后,配置 Trace to Logs 关联:
# Grafana datasource provisioning
apiVersion: 1
datasources:
- name: Tempo
type: tempo
url: http://tempo-query-frontend.observability.svc.cluster.local:3100
jsonData:
tracesToLogsV2:
datasourceUid: loki
filterByTraceID: true
filterBySpanID: true
customQuery: true
query: '{namespace="$${__span.tags.k8s.namespace.name}"} | json | trace_id="$${__span.traceId}"'
tracesToMetrics:
datasourceUid: mimir
queries:
- name: Request Rate
query: 'sum(rate(traces_spanmetrics_calls_total{service="$${__span.tags["service.name"]}"}[5m]))'
serviceMap:
datasourceUid: mimir # Metrics Generator 产生的 Service Graph 指标
nodeGraph:
enabled: true
配置完成后,在 Grafana Explore 中可以: 1. 通过 Trace ID 直接搜索完整调用链 2. 从 Loki 日志点击跳转到对应 Trace 3. 查看 Service Map(服务依赖拓扑 + RED 指标)
避坑
1. Ingester OOM:Trace 太大或流量突增
现象:Ingester Pod 频繁 OOMKilled
解决:
# tempo-values.yaml 限制单 Trace 大小
tempo:
global_overrides:
max_bytes_per_trace: 5000000 # 5MB 上限
max_spans_per_trace: 10000 # 单 Trace 最多 1 万 Span
# 同时在 Alloy 侧做 tail-based sampling 降低总量
# 生产建议:只保留错误 + 慢请求 + 10% 随机采样
2. 查询超时:大时间范围查询卡死
现象:查询 24h 内的 Trace 超时
解决:确保 Query Frontend 开启分片 + 使用 vParquet4 格式:
queryFrontend:
config:
search:
max_duration: 168h # 允许查询最近 7 天
query_backend_after: 15m # 15 分钟前的数据走对象存储
query_ingesters_until: 30m # 最近 30 分钟查 Ingester
trace_by_id:
query_shards: 20 # 按 Trace ID 查询的并行分片数
3. 存储成本失控:采样策略缺失
现象:S3 存储费用远超预期
解决:三层采样策略组合使用: - Head-based(SDK 侧):高流量服务设置 0.1(10%)采样率 - Tail-based(Alloy 侧):保留全部错误和慢请求,正常请求只留 10% - Retention(Tempo 侧):配置 Compactor 保留策略
compactor:
config:
compaction:
block_retention: 336h # 保留 14 天,超期自动清理
总结
| 维度 | Tempo | Jaeger (ES) |
|---|---|---|
| 存储成本 | 低(对象存储,~$0.023/GB) | 高(ES 热数据 ~$0.10/GB) |
| 运维复杂度 | 中(无状态组件 + 对象存储) | 高(ES 集群运维) |
| 查询能力 | TraceQL 强大 | 基础标签查询 |
| 生态集成 | Grafana 原生 | 需额外配置 |
| 适用规模 | 中大型(日均 TB 级 Trace) | 中小型 |
核心结论:
- 如果你已经在用 Grafana + Loki + Mimir/Prometheus 栈,Tempo 是分布式追踪的最佳选择——统一 UI、统一查询、数据互相关联
- 生产部署务必配置 tail-based sampling,否则存储成本会失控
- 开启 Metrics Generator,让 Tempo 自动生成 RED 指标(Rate/Error/Duration),无需额外 instrumentation 即可获得 Service Map
- 使用 vParquet4 格式,相比旧版本查询性能提升显著
日均 10TB Trace 数据参考成本:S3 存储 ~$5/天 + EC2 计算 ~$15/天,远低于等量 Elasticsearch 方案。