| name | tempo |
| description | Tempo — distributed tracing backend for the Grafana observability stack. Use this skill whenever the user needs to store and query distributed traces, integrate with OpenTelemetry, set up trace-to-logs or trace-to-metrics correlation in Grafana, configure Tempo with Jaeger/Zipkin/OTLP ingest, or replace Jaeger/Zipkin with a cost-efficient trace backend. Trigger for "tempo tracing", "grafana traces", "TraceQL", "OTLP grafana", "distributed tracing grafana", or "replace jaeger". |
Tempo — Distributed Tracing for the Grafana Stack
Overview
Grafana Tempo is a high-scale, cost-efficient distributed tracing backend. Unlike Jaeger or Zipkin, Tempo stores traces in object storage (S3, GCS, Azure Blob) with no additional dependencies — it requires no Elasticsearch or Cassandra. Traces are queried via TraceQL, a query language similar to LogQL, and visualized in Grafana with native trace-to-logs and trace-to-metrics correlations. Tempo accepts OTLP, Jaeger, Zipkin, and Zipkin Protobuf formats, making it a drop-in replacement for most existing tracing backends.
When to Use
- Storing distributed traces generated by OpenTelemetry-instrumented services
- Replacing Jaeger/Zipkin with a cheaper, object-storage-backed backend
- Correlating traces with Loki logs and Prometheus metrics in Grafana
- Querying spans by attribute, duration, or service name via TraceQL
- High-volume tracing where Jaeger's Elasticsearch costs are prohibitive
Installation
helm repo add grafana https://grafana.github.io/helm-charts
helm install tempo grafana/tempo \
--namespace monitoring \
--create-namespace \
--set tempo.storage.trace.backend=local
helm install tempo grafana/tempo-distributed \
--namespace monitoring \
--values tempo-values.yaml
kubectl get pods -n monitoring -l app.kubernetes.io/name=tempo
Key Patterns
Tempo Configuration — S3 Backend
storage:
trace:
backend: s3
s3:
bucket: my-tempo-traces
endpoint: s3.us-east-1.amazonaws.com
region: us-east-1
pool:
max_workers: 100
queue_depth: 10000
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
jaeger:
protocols:
thrift_http:
endpoint: "0.0.0.0:14268"
grpc:
endpoint: "0.0.0.0:14250"
zipkin:
endpoint: "0.0.0.0:9411"
compactor:
compaction:
block_retention: 336h
search_enabled: true
OpenTelemetry Collector — Sending Traces to Tempo
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
timeout: 5s
send_batch_size: 1000
probabilistic_sampler:
hash_seed: 22
sampling_percentage: 10
exporters:
otlp:
endpoint: "tempo.monitoring.svc.cluster.local:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, probabilistic_sampler]
exporters: [otlp]
Python Service — OpenTelemetry Instrumentation
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
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
resource = Resource.create({"service.name": "my-api", "service.version": "1.2.0"})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint="http://otel-collector:4317", insecure=True)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
FastAPIInstrumentor.instrument_app(app)
tracer = trace.get_tracer("my-api")
def process_order(order_id: str):
with tracer.start_as_current_span("process-order") as span:
span.set_attribute("order.id", order_id)
span.set_attribute("order.source", "web")
try:
result = db.execute_order(order_id)
span.set_attribute("order.status", "success")
return result
except Exception as e:
span.record_exception(e)
span.set_status(trace.StatusCode.ERROR, (e))
TraceQL — Querying Traces
# Find all traces from the checkout service
{ .service.name = "checkout" }
# Find traces with errors
{ status = error }
# Find slow spans (>500ms) in the payment service
{ .service.name = "payment" && duration > 500ms }
# Find traces that hit the database and were slow
{ span.db.system = "postgresql" && duration > 200ms }
# Find traces with a specific HTTP path
{ span.http.route = "/api/checkout" && span.http.status_code >= 500 }
# Aggregate: p99 latency by service
{ } | rate() by (.service.name)
Grafana — Tempo Data Source Configuration
apiVersion: 1
datasources:
- name: Tempo
type: tempo
url: http://tempo.monitoring.svc.cluster.local:3100
jsonData:
tracesToLogs:
datasourceUid: loki
tags: ["job", "instance", "pod", "namespace"]
filterByTraceID: true
filterBySpanID: true
tracesToMetrics:
datasourceUid: prometheus
tags: [{key: "service.name", value: "job"}]
queries:
- name: "Request rate"
query: "rate(http_requests_total{$$__tags}[5m])"
serviceMap:
datasourceUid: prometheus
search:
hide: false
nodeGraph:
enabled: true
Service Graph Metrics (Prometheus)
metricsGenerator:
enabled: true
config:
storage:
remote_write:
- url: http://prometheus.monitoring.svc.cluster.local:9090/api/v1/write
Common Commands
curl http://tempo:3100/ready
curl http://tempo:3100/api/search/tags
curl "http://tempo:3100/api/search?tags=service.name%3Dmy-api&limit=20"
curl "http://tempo:3100/api/traces/<trace-id>"
curl http://tempo:3100/metrics | grep tempo_ingester
kubectl port-forward -n monitoring svc/tempo 3100
Pitfalls
- Trace retention vs storage cost: Tempo stores everything by default; always set
block_retention in the compactor config — 14 days is a reasonable default for most teams
- Head-based vs tail-based sampling: configure sampling at the collector level, not in individual services; tail-based sampling (keep only slow/error traces) requires the
tail_sampling processor in the OpenTelemetry Collector
- TraceQL search requires the search component: in distributed mode, search only works when the
query-frontend and querier components are running — a common oversight when deploying minimal setups
- OTLP gRPC vs HTTP: gRPC (port 4317) is preferred for high throughput; HTTP (4318) is easier to debug but has higher overhead
- Service Graph requires metrics generator: the Service Graph Grafana panel only works if you enable Tempo's metrics generator and remote-write to Prometheus — check this before promising the feature
Related Skills
loki — log aggregation (correlate with traces)
prometheus-recording-rules — metrics alongside tracing
alloy — OpenTelemetry collector that feeds Tempo
observability-engineer — full Grafana stack strategy
opentelemetry-instrumentation — instrumenting services for tracing
GitNexus Index
Index path: /Users/localuser/.claude/skills/tempo/.gitnexus
Created: 2026-05-24