distributed-tracing について
Jaeger と Tempo を使った分散トレーシングの実装スキルです。マイクロサービス間のリクエスト経路を調べ、遅延、依存関係、障害箇所を把握する方法を扱います。
主な機能
- 01トレースとスパンの基礎
- 02Jaeger のデプロイ手順
- 03サービス依存関係グラフ
- 04エラー条件によるトレース検索
- 05トレースとログの関連付け
- 06サンプリングと計装の手法
活用シーン
- 01サービス間の遅延診断
- 02サービス依存関係の把握
- 03エラー伝播経路の追跡
- 04リクエスト経路のボトルネック特定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ hermeticormus/ libreuiux-claude-code --skill distributed-tracingインストール後、Claude Code で/distributed-tracingを入力して読み込まれたか確認します。
詳細
分散トレーシング
マイクロサービス間のリクエストフローを可視化するため、Jaeger と Tempo を使った分散トレーシングを実装します。
目的
分散システム全体でリクエストを追跡し、レイテンシ、依存関係、障害発生箇所を把握します。
使用する場面
- レイテンシの問題をデバッグする
- サービス間の依存関係を把握する
- ボトルネックを特定する
- エラーの伝播を追跡する
- リクエスト経路を分析する
分散トレーシングの概念
トレースの構造
Trace (Request ID: abc123)
↓
Span (frontend) [100ms]
↓
Span (api-gateway) [80ms]
├→ Span (auth-service) [10ms]
└→ Span (user-service) [60ms]
└→ Span (database) [40ms]
主なコンポーネント
- トレース - リクエストのエンドツーエンドの経路
- スパン - トレース内の単一の操作
- コンテキスト - サービス間で伝播されるメタデータ
- タグ - フィルタリングに使うキーと値のペア
- ログ - スパン内のタイムスタンプ付きイベント
Jaeger のセットアップ
Kubernetes へのデプロイ
# Deploy Jaeger Operator
kubectl create namespace observability
kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.51.0/jaeger-operator.yaml -n observability
# Deploy Jaeger instance
kubectl apply -f - <<EOF
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger
namespace: observability
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch:9200
ingress:
enabled: true
EOF
Docker Compose
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686" # UI
- "14268:14268" # Collector
- "14250:14250" # gRPC
- "9411:9411" # Zipkin
environment:
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
参考: references/jaeger-setup.md を参照してください。
アプリケーションの計装
OpenTelemetry(推奨)
Python(Flask)
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from flask import Flask
# Initialize tracer
resource = Resource(attributes={SERVICE_NAME: "my-service"})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(JaegerExporter(
agent_host_name="jaeger",
agent_port=6831,
))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Instrument Flask
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
@app.route('/api/users')
def get_users():
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("get_users") as span:
span.set_attribute("user.count", 100)
# Business logic
users = fetch_users_from_db()
return {"users": users}
def fetch_users_from_db():
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("database_query") as span:
span.set_attribute("db.system", "postgresql")
span.set_attribute("db.statement", "SELECT * FROM users")
# Database query
return query_database()
Node.js(Express)
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
// Initialize tracer
const provider = new NodeTracerProvider({
resource: { attributes: { 'service.name': 'my-service' } }
});
const exporter = new JaegerExporter({
endpoint: 'http://jaeger:14268/api/traces'
});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();
// Instrument libraries
registerInstrumentations({
instrumentations: [
new HttpInstrumentation(),
new ExpressInstrumentation(),
],
});
const express = require('express');
const app = express();
app.get('/api/users', async (req, res) => {
const tracer = trace.getTracer('my-service');
const span = tracer.startSpan('get_users');
try {
const users = await fetchUsers();
span.setAttributes({ 'user.count': users.length });
res.json({ users });
} finally {
span.end();
}
});
Go
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
func initTracer() (*sdktrace.TracerProvider, error) {
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://jaeger:14268/api/traces"),
))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-service"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func getUsers(ctx context.Context) ([]User, error) {
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(ctx, "get_users")
defer span.End()
span.SetAttributes(attribute.String("user.filter", "active"))
users, err := fetchUsersFromDB(ctx)
if err != nil {
span.RecordError(err)
return nil, err
}
span.SetAttributes(attribute.Int("user.count", len(users)))
return users, nil
}
参考: references/instrumentation.md を参照してください。
コンテキストの伝播
HTTP ヘッダー
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE
HTTP リクエストでの伝播
Python
from opentelemetry.propagate import inject
headers = {}
inject(headers) # Injects trace context
response = requests.get('http://downstream-service/api', headers=headers)
Node.js
const { propagation } = require('@opentelemetry/api');
const headers = {};
propagation.inject(context.active(), headers);
axios.get('http://downstream-service/api', { headers });
Tempo のセットアップ(Grafana)
Kubernetes へのデプロイ
apiVersion: v1
kind: ConfigMap
metadata:
name: tempo-config
data:
tempo.yaml: |
server:
http_listen_port: 3200
distributor:
receivers:
jaeger:
protocols:
thrift_http:
grpc:
otlp:
protocols:
http:
grpc:
storage:
trace:
backend: s3
s3:
bucket: tempo-traces
endpoint: s3.amazonaws.com
querier:
frontend_worker:
frontend_address: tempo-query-frontend:9095
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tempo
spec:
replicas: 1
template:
spec:
containers:
- name: tempo
image: grafana/tempo:latest
args:
- -config.file=/etc/tempo/tempo.yaml
volumeMounts:
- name: config
mountPath: /etc/tempo
volumes:
- name: config
configMap:
name: tempo-config
参考: assets/jaeger-config.yaml.template を参照してください。
サンプリング戦略
確率的サンプリング
# Sample 1% of traces
sampler:
type: probabilistic
param: 0.01
レート制限サンプリング
# Sample max 100 traces per second
sampler:
type: ratelimiting
param: 100
アダプティブサンプリング
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
# Sample based on trace ID (deterministic)
sampler = ParentBased(root=TraceIdRatioBased(0.01))
トレース分析
処理の遅いリクエストを見つける
Jaeger クエリ:
service=my-service
duration > 1s
エラーを見つける
Jaeger クエリ:
service=my-service
error=true
tags.http.status_code >= 500
サービス依存関係グラフ
Jaeger は以下を示すサービス依存関係グラフを自動的に生成します。
- サービス間の関係
- リクエスト率
- エラー率
- 平均レイテンシ
ベストプラクティス
- 適切にサンプリングする(本番環境では 1〜10%)
- 意味のあるタグを追加する(user_id、request_id)
- すべてのサービス境界でコンテキストを伝播する
- スパンに例外を記録する
- 操作名に一貫性を持たせる
- トレーシングのオーバーヘッドを監視する(CPU への影響を 1% 未満にする)
- トレースのエラーに対するアラートを設定する
- 分散コンテキスト(baggage)を実装する
- 重要な節目にスパンイベントを使う
- 計装の標準を文書化する
ロギングとの統合
相関ログ
import logging
from opentelemetry import trace
logger = logging.getLogger(__name__)
def process_request():
span = trace.get_current_span()
trace_id = span.get_span_context().trace_id
logger.info(
"Processing request",
extra={"trace_id": format(trace_id, '032x')}
)
トラブルシューティング
トレースが表示されない場合:
- コレクターのエンドポイントを確認する
- ネットワーク接続を確認する
- サンプリング設定を確認する
- アプリケーションログを確認する
レイテンシのオーバーヘッドが大きい場合:
- サンプリング率を下げる
- バッチスパンプロセッサーを使う
- エクスポーターの設定を確認する
参考ファイル
references/jaeger-setup.md- Jaeger のインストールreferences/instrumentation.md- 計装のパターンassets/jaeger-config.yaml.template- Jaeger の設定
関連スキル
prometheus-configuration- メトリクス用grafana-dashboards- 可視化用slo-implementation- レイテンシ SLO 用
コメント
Markdown 対応