Jaeger
Jaeger 分散トレーシング 完全ガイド
目次
- はじめに
- コアコンセプト
- Jaeger アーキテクチャ
- デプロイメントパターン
- Jaeger UI と可視化機能
- セキュリティ
- 運用とベストプラクティス
- 高度なトピック
- 実践的なユースケース
- 他のトレーシングツールとの比較
- まとめと今後の展望
- 付録
1. はじめに
1.1 分散トレーシングとは何か
現代のソフトウェアアーキテクチャは、モノリシックなアプリケーションからマイクロサービスベースのアーキテクチャへと急速に移行している。この移行により、数十から数百のサービスが互いに連携してひとつのリクエストを処理する構成が一般的になった。このような環境では、ひとつのユーザーリクエストが複数のサービスを横断して処理されるため、問題の特定やパフォーマンスのボトルネックの発見が極めて困難になる。
分散トレーシング(Distributed Tracing)は、この課題を解決するための観測可能性(Observability)技術のひとつである。分散トレーシングでは、リクエストがシステム内のサービスを通過する際の経路を追跡し、各サービスでの処理時間、呼び出し関係、エラー情報などを記録する。これにより、以下のことが可能になる。
- レイテンシのボトルネック特定: どのサービスがリクエスト全体の遅延の原因になっているかを可視化する
- 障害の根本原因分析: エラーがどのサービスで発生し、どのように伝播したかを追跡する
- サービス間の依存関係の把握: 実際のトラフィックに基づいて、サービス間の呼び出し関係を明らかにする
- パフォーマンス最適化: 各サービスの処理時間の分布を分析し、最適化の優先順位を決定する
1.2 Jaeger の概要
Jaeger(イェーガー、ドイツ語で「猟師」の意)は、Uber Technologies が開発したオープンソースの分散トレーシングプラットフォームである。2017年に Uber がオープンソースとして公開し、その後 Cloud Native Computing Foundation(CNCF)に寄贈された。2019年に CNCF の Graduated プロジェクトに昇格し、Kubernetes や Prometheus と同等の成熟度を持つプロジェクトとして認定されている。
Jaeger は Google の Dapper 論文と Twitter の Zipkin に着想を得て開発された。主な特徴は以下の通りである。
- OpenTelemetry ネイティブ対応: OpenTelemetry SDK およびプロトコルとのシームレスな統合
- 高いスケーラビリティ: 大規模環境での運用を前提とした水平スケーリング設計
- 柔軟なストレージバックエンド: Cassandra、Elasticsearch、Kafka、Badger など複数のストレージに対応
- 豊富な可視化機能: トレースの検索、比較、依存関係グラフの表示
- 適応型サンプリング: トラフィック量に応じた動的なサンプリングレートの調整
- サービスパフォーマンス監視(SPM): RED メトリクス(Rate、Error、Duration)の自動生成
1.3 Jaeger の歴史とエコシステムにおける位置づけ
Jaeger の発展は、分散トレーシングの標準化の歴史と密接に関連している。
| 年 | 出来事 |
|---|---|
| 2012 | Google が Dapper 論文を発表 |
| 2012 | Twitter が Zipkin をオープンソースとして公開 |
| 2015 | OpenTracing プロジェクトが発足(CNCF) |
| 2016 | Uber が Jaeger を社内で開発・運用開始 |
| 2017 | Jaeger をオープンソースとして公開、CNCF に Incubation プロジェクトとして寄贈 |
| 2017 | W3C Trace Context 仕様の策定開始 |
| 2019 | Jaeger が CNCF Graduated プロジェクトに昇格 |
| 2019 | OpenTelemetry プロジェクトが発足(OpenTracing と OpenCensus の統合) |
| 2021 | OpenTelemetry Tracing 仕様が GA(Generally Available)に到達 |
| 2022 | Jaeger が OpenTelemetry SDK を公式に推奨し始める |
| 2023 | Jaeger v2 の開発が開始(OpenTelemetry Collector ベースのアーキテクチャ) |
| 2024 | Jaeger v2 が正式リリース、アーキテクチャが大幅に刷新 |
現在の Observability エコシステムにおいて、Jaeger は以下のように位置づけられる。
┌─────────────────────────────────────────────────────────┐
│ Observability の三本柱 │
├──────────────┬──────────────────┬────────────────────────┤
│ Metrics │ Logs │ Traces │
│ (Prometheus) │ (Loki/Fluentd) │ (Jaeger/Tempo/Zipkin) │
└──────────────┴──────────────────┴────────────────────────┘
│ │ │
└──────────────┴────────────────────┘
│
┌─────────────────┐
│ OpenTelemetry │
│ (統一的な計装) │
└─────────────────┘
OpenTelemetry はテレメトリデータの生成・収集の標準を提供し、Jaeger はトレースデータの保存・クエリ・可視化を担当するバックエンドとして機能する。この役割分担により、アプリケーション開発者は OpenTelemetry SDK で計装を行い、バックエンドは Jaeger、Tempo、Zipkin などから自由に選択できる。
2. コアコンセプト
2.1 トレーシングの基本用語
Trace(トレース)
トレースは、分散システムを横断するひとつのリクエストの完全な実行パスを表す。トレースは複数のスパンで構成され、有向非巡回グラフ(DAG: Directed Acyclic Graph)の構造を持つ。
Trace ID: abc123
[Service A: HTTP GET /api/orders] ─────────────────────────────────
│
├── [Service B: GetUserProfile] ──────────────
│
├── [Service C: QueryOrders] ──────────────────────
│ │
│ └── [Service D: QueryDatabase] ────────
│
└── [Service E: CheckPermissions] ──────
Span(スパン)
スパンは、トレース内のひとつの論理的な作業単位を表す。各スパンは以下の情報を含む。
| フィールド | 説明 |
|---|---|
| Trace ID | このスパンが属するトレースの一意識別子(128ビット) |
| Span ID | このスパンの一意識別子(64ビット) |
| Parent Span ID | 親スパンの識別子(ルートスパンの場合はなし) |
| Operation Name | このスパンが表す操作の名前(例: HTTP GET /api/orders) |
| Start Timestamp | 操作の開始時刻(マイクロ秒精度) |
| Duration | 操作の所要時間 |
| Tags / Attributes | キーバリューペアのメタデータ(例: http.status_code=200) |
| Logs / Events | タイムスタンプ付きのイベント記録 |
| Span Kind | スパンの種類(Client、Server、Producer、Consumer、Internal) |
| Status | スパンのステータス(OK、Error、Unset) |
スパンの種類(Span Kind):
- Client: リモートサービスへのリクエストを送信する側
- Server: リモートリクエストを受信して処理する側
- Producer: 非同期メッセージを送信する側(Kafka プロデューサーなど)
- Consumer: 非同期メッセージを受信する側(Kafka コンシューマーなど)
- Internal: サービス内部の処理。リモート呼び出しを伴わない操作
SpanContext(スパンコンテキスト)
SpanContext は、プロセス間でトレースの因果関係を伝播させるための情報を保持する。
- Trace ID: トレース全体の一意識別子
- Span ID: 現在のスパンの一意識別子
- Trace Flags: サンプリング判断などのフラグ
- Trace State: ベンダー固有の追加情報
W3C Trace Context 仕様では、以下の HTTP ヘッダーを使用してスパンコンテキストを伝播する。
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: jaeger=value1,other=value2
traceparent ヘッダーのフォーマット:
version-trace_id-parent_id-trace_flags
00 -4bf92f...-00f067...-01
version: 00(現在のバージョン)
trace_id: 32文字の16進数(128ビット)
parent_id: 16文字の16進数(64ビット)
trace_flags: 2文字の16進数(01 = サンプリング対象)
Baggage(バゲージ)
バゲージは、トレースコンテキストとともにサービス間を伝播するキーバリューペアのメタデータである。スパンのタグとは異なり、バゲージはリクエストパス上のすべてのダウンストリームサービスに自動的に伝播される。
Client → Service A → Service B → Service C
│ │ │ │
└─ baggage: user_id=12345 ────────────→ (すべてのサービスで参照可能)
典型的な用途:ユーザー ID やテナント ID の伝播、A/B テストのバリアント情報、デバッグフラグ、リクエストの優先度情報。
2.2 サンプリング戦略
すべてのリクエストのトレースデータを記録するのは、多くの本番環境において現実的ではない。サンプリングは、収集するトレースの量を制御するメカニズムである。
Head-based サンプリング
トレースの開始時にサンプリング判断を行う方式。
1. 定数サンプリング(Const Sampler)
sampler:
type: const
param: 1 # 1 = すべて記録、0 = すべて無視
2. 確率的サンプリング(Probabilistic Sampler)
sampler:
type: probabilistic
param: 0.1 # 10% のトレースを記録
3. レートリミティングサンプリング(Rate Limiting Sampler)
sampler:
type: ratelimiting
param: 2.0 # 1秒あたり最大2トレース
4. リモートサンプリング(Remote Sampler)
sampler:
type: remote
param: 0.1 # サーバーから取得できない場合のデフォルト値
sampling_server_url: http://jaeger-collector:14268/api/sampling
適応型サンプリング(Adaptive Sampling)
エンドポイントごとのトラフィック量に基づいて、自動的にサンプリングレートを調整する。
高トラフィックのエンドポイント:
/api/healthcheck → サンプリング率: 0.1%
低トラフィックのエンドポイント:
/api/admin/config → サンプリング率: 100%
目標: 各エンドポイントから最低 N 件/秒のトレースを確保する
設定例:
extensions:
jaeger_storage:
backends:
some_storage:
memory:
max_traces: 100000
remote_sampling:
adaptive:
sampling_store: some_storage
target_samples_per_second: 1.0
default_sampling_probability: 0.1
calculation_interval: 30s
aggregation_buckets: 10
Tail-based サンプリング
トレース全体が完了した後にサンプリング判断を行う方式。エラーが発生したトレースや異常に遅いトレースを確実に記録できる。
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
- name: errors-policy
type: status_code
status_code:
status_codes: [ERROR]
- name: latency-policy
type: latency
latency:
threshold_ms: 5000
- name: probabilistic-policy
type: probabilistic
probabilistic:
sampling_percentage: 10
- name: specific-endpoint
type: string_attribute
string_attribute:
key: http.route
values: [/api/checkout, /api/payment]
2.3 コンテキスト伝播(Context Propagation)
W3C Trace Context(推奨)
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: jaeger=abc123
B3 Propagation(Zipkin 互換)
b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1
Jaeger Propagation(レガシー)
uber-trace-id: 4bf92f3577b34da6a3ce929d0e0e4736:00f067aa0ba902b7:0:01
OpenTelemetry SDK での伝播設定例:
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.b3 import B3MultiFormat
set_global_textmap(CompositePropagator([
TraceContextTextMapPropagator(), # W3C Trace Context
B3MultiFormat(), # B3(Zipkin互換)
]))
2.4 OpenTelemetry との統合
Jaeger v2 では、OpenTelemetry との統合が大幅に強化された。公式推奨はアプリケーションの計装に OpenTelemetry SDK を使用することである。
┌─────────────────────────────────────────────────────┐
│ Application │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ OTel SDK │ │ Auto- │ │
│ │ (Manual) │ │ Instrumentation │ │
│ └──────┬───────┘ └──────┬───────────┘ │
│ └────────┬─────────┘ │
│ ┌────────▼────────┐ │
│ │ TracerProvider │ │
│ │ + SpanProcessor │ │
│ │ + Sampler │ │
│ └────────┬────────┘ │
│ ┌────────▼────────┐ │
│ │ SpanExporter │ │
│ │ (OTLP) │ │
│ └────────┬────────┘ │
└──────────────────┼──────────────────────────────────┘
│ OTLP (gRPC/HTTP)
▼
┌────────────────┐
│ Jaeger Backend │
└────────────────┘
Python での計装例
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.semconv.resource import ResourceAttributes
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from flask import Flask
resource = Resource.create({
ResourceAttributes.SERVICE_NAME: "order-service",
ResourceAttributes.SERVICE_VERSION: "1.2.0",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "production",
})
provider = TracerProvider(resource=resource)
otlp_exporter = OTLPSpanExporter(
endpoint="http://jaeger-collector:4317",
insecure=True,
)
provider.add_span_processor(
BatchSpanProcessor(otlp_exporter, max_queue_size=2048, max_export_batch_size=512)
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()
@app.route("/api/orders/<order_id>")
def get_order(order_id):
with tracer.start_as_current_span("get_order_details",
attributes={"order.id": order_id, "order.type": "standard"}
) as span:
with tracer.start_as_current_span("query_database") as db_span:
db_span.set_attribute("db.system", "postgresql")
db_span.set_attribute("db.statement", "SELECT * FROM orders WHERE id = ?")
order = query_order_from_db(order_id)
with tracer.start_as_current_span("call_inventory_service") as inv_span:
inv_span.set_attribute("peer.service", "inventory-service")
inventory = check_inventory(order)
if order is None:
span.set_status(trace.StatusCode.ERROR, "Order not found")
span.record_exception(ValueError(f"Order {order_id} not found"))
return {"error": "Not found"}, 404
return {"order": order, "inventory": inventory}
Go での計装例
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func initTracer() (*sdktrace.TracerProvider, error) {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("jaeger-collector:4317"),
otlptracegrpc.WithInsecure(),
)
if err != nil {
return nil, fmt.Errorf("failed to create exporter: %w", err)
}
res, _ := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("payment-service"),
semconv.ServiceVersion("2.1.0"),
semconv.DeploymentEnvironment("production"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter,
sdktrace.WithBatchTimeout(5*time.Second),
sdktrace.WithMaxQueueSize(2048),
),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
return tp, nil
}
func main() {
tp, err := initTracer()
if err != nil { log.Fatal(err) }
defer tp.Shutdown(context.Background())
tracer := otel.Tracer("payment-service")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "process_payment",
trace.WithAttributes(
attribute.String("payment.method", "credit_card"),
attribute.Float64("payment.amount", 99.99),
),
)
defer span.End()
// ビジネスロジック
})
wrappedHandler := otelhttp.NewHandler(handler, "ProcessPayment")
http.Handle("/api/payment", wrappedHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Java での計装例
import io.opentelemetry.api.trace.*;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.ResourceAttributes;
import java.time.Duration;
public class TracingConfig {
public static OpenTelemetry initOpenTelemetry() {
OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("http://jaeger-collector:4317")
.setTimeout(Duration.ofSeconds(10))
.build();
Resource resource = Resource.getDefault().merge(
Resource.create(Attributes.of(
ResourceAttributes.SERVICE_NAME, "user-service",
ResourceAttributes.SERVICE_VERSION, "3.0.1",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
))
);
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(exporter)
.setMaxQueueSize(2048).setMaxExportBatchSize(512)
.setScheduleDelay(Duration.ofSeconds(5)).build())
.setResource(resource).build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider).buildAndRegisterGlobal();
}
}
3. Jaeger アーキテクチャ
3.1 Jaeger v2 アーキテクチャ概要
Jaeger v2 は、OpenTelemetry Collector をベースとした全面的に再設計されたアーキテクチャを採用している。v1 からの最大の変更点は、独自のコンポーネント(Agent、Collector、Query)を統合し、単一の jaeger バイナリで全機能を提供するようになったことである。
┌────────────────────────────────────────────────────────────────┐
│ Jaeger v2 アーキテクチャ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Service A │ │ Service B │ │ Service C │ │
│ │ (OTel SDK) │ │ (OTel SDK) │ │ (OTel SDK) │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ OTLP │ OTLP │ OTLP │
│ └───────────────┼───────────────┘ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ Jaeger v2 Binary │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ OTel Collector Core │ │ │
│ │ │ Receivers → Processors │ │ │
│ │ │ → Exporters │ │ │
│ │ └────────────┬────────────┘ │ │
│ │ ┌────────────▼────────────┐ │ │
│ │ │ Jaeger Extensions │ │ │
│ │ │ (Storage, Query, │ │ │
│ │ │ Remote Sampling) │ │ │
│ │ └────────────┬────────────┘ │ │
│ └───────────────┼───────────────┘ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ Storage Backend │ │
│ │ (Cassandra / Elasticsearch │ │
│ │ / Badger / gRPC Remote) │ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ Jaeger UI (React) + API │◄── ブラウザ │
│ └───────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
3.2 v1 と v2 のアーキテクチャ比較
| 特性 | Jaeger v1 | Jaeger v2 |
|---|---|---|
| コアランタイム | 独自実装 | OpenTelemetry Collector |
| バイナリ | 複数(agent, collector, query, ingester) | 単一(jaeger) |
| Agent | 必須(UDP でデータ受信) | 不要(OTLP で直接送信) |
| 受信プロトコル | Jaeger Thrift (UDP/HTTP), Zipkin | OTLP (gRPC/HTTP), Jaeger, Zipkin |
| 推奨 SDK | Jaeger Client Libraries | OpenTelemetry SDK |
| 設定方式 | CLI フラグ + 環境変数 | YAML 設定ファイル |
| 拡張性 | 限定的 | OTel Collector のプラグインエコシステム |
| パイプライン | 固定 | 柔軟(Receiver→Processor→Exporter) |
| SPM | 別コンポーネント | 統合済み |
3.3 コンポーネント詳細
3.3.1 Receivers(レシーバー)
OTLP Receiver(推奨)
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
max_recv_msg_size_mib: 4
max_concurrent_streams: 100
tls:
cert_file: /certs/server.crt
key_file: /certs/server.key
client_ca_file: /certs/ca.crt
http:
endpoint: "0.0.0.0:4318"
Jaeger Receiver(後方互換性)
receivers:
jaeger:
protocols:
grpc:
endpoint: "0.0.0.0:14250"
thrift_http:
endpoint: "0.0.0.0:14268"
thrift_compact:
endpoint: "0.0.0.0:6831"
3.3.2 Processors(プロセッサー)
processors:
batch:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
attributes:
actions:
- key: environment
value: production
action: upsert
- key: db.password
action: delete
filter:
error_mode: ignore
traces:
span:
- 'attributes["http.route"] == "/healthz"'
resource:
attributes:
- key: cloud.region
value: ap-northeast-1
action: upsert
3.3.3 Exporters(エクスポーター)
exporters:
jaeger_storage_exporter:
trace_storage: main_storage
otlp:
endpoint: "tempo.monitoring.svc:4317"
tls:
insecure: false
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
3.3.4 Extensions(拡張機能)
extensions:
jaeger_storage:
backends:
main_storage:
elasticsearch:
server_urls: http://elasticsearch:9200
index_prefix: jaeger
tags_as_fields:
all: true
num_shards: 5
num_replicas: 1
archive_storage:
elasticsearch:
server_urls: http://elasticsearch-archive:9200
index_prefix: jaeger-archive
jaeger_query:
storage:
traces: main_storage
traces_archive: archive_storage
grpc:
endpoint: "0.0.0.0:16685"
http:
endpoint: "0.0.0.0:16686"
3.4 ストレージバックエンド
3.4.1 Elasticsearch / OpenSearch
extensions:
jaeger_storage:
backends:
es_main:
elasticsearch:
server_urls: "http://es-node1:9200,http://es-node2:9200,http://es-node3:9200"
index_prefix: "jaeger"
num_shards: 5
num_replicas: 1
create_index_templates: true
use_ilm: true
ilm_policy_name: "jaeger-traces-policy"
bulk_size: 5000000
bulk_workers: 3
bulk_actions: 1000
bulk_flush_interval: 200ms
tags_as_fields:
all: true
tls:
enabled: true
ca_file: /certs/ca.crt
username: jaeger
password: "${ES_PASSWORD}"
3.4.2 Apache Cassandra
extensions:
jaeger_storage:
backends:
cassandra_main:
cassandra:
servers: "cassandra-node1,cassandra-node2,cassandra-node3"
port: 9042
keyspace: "jaeger_v1_production"
connections_per_host: 3
consistency: LOCAL_QUORUM
schema:
datacenter: dc1
replication_factor: 3
span_store_write_cache_ttl: 12h
3.4.3 Badger(組み込みストレージ)
extensions:
jaeger_storage:
backends:
badger_storage:
badger:
directories:
keys: /var/jaeger/badger/keys
values: /var/jaeger/badger/values
ephemeral: false
span_store_ttl: 72h
3.4.4 Kafka を使用したアーキテクチャ
Services ──→ Jaeger Collector ──→ Kafka ──→ Jaeger Ingester ──→ Storage
Collector 側(Kafka への書き込み):
exporters:
kafka:
protocol_version: "3.0.0"
brokers: [kafka-broker1:9092, kafka-broker2:9092, kafka-broker3:9092]
topic: "jaeger-spans"
encoding: otlp_proto
producer:
compression: zstd
required_acks: all
Ingester 側(Kafka からの読み取り):
receivers:
kafka:
brokers: [kafka-broker1:9092, kafka-broker2:9092, kafka-broker3:9092]
topic: "jaeger-spans"
group_id: "jaeger-ingester"
encoding: otlp_proto
3.4.5 ストレージバックエンドの選択ガイド
| 特性 | Elasticsearch | Cassandra | Badger | Kafka+ES |
|---|---|---|---|---|
| スケーラビリティ | 高 | 非常に高 | 低 | 非常に高 |
| 検索機能 | 全文検索対応 | 制限的 | 基本的 | ES 依存 |
| 書き込み性能 | 中〜高 | 非常に高 | 高 | 非常に高 |
| 運用の複雑さ | 中 | 高 | 低 | 高 |
| 推奨環境 | 中〜大規模 | 超大規模 | 開発・テスト | 大規模・高信頼 |
4. デプロイメントパターン
4.1 All-in-One デプロイメント
# docker-compose.yml
services:
jaeger:
image: jaegertracing/jaeger:2.4
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "16686:16686" # Jaeger UI
volumes:
- ./jaeger-config.yaml:/etc/jaeger/config.yaml
command: ["--config", "/etc/jaeger/config.yaml"]
# jaeger-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
send_batch_size: 10000
timeout: 5s
extensions:
jaeger_storage:
backends:
some_storage:
memory:
max_traces: 100000
jaeger_query:
storage:
traces: some_storage
http:
endpoint: "0.0.0.0:16686"
exporters:
jaeger_storage_exporter:
trace_storage: some_storage
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger_storage_exporter]
4.2 Kubernetes デプロイメント
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm install jaeger jaegertracing/jaeger \
--namespace observability \
--create-namespace \
--values values-production.yaml
# values-production.yaml
collector:
replicaCount: 3
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: 2000m, memory: 2Gi }
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
query:
replicaCount: 2
ingress:
enabled: true
hosts:
- host: jaeger.example.com
paths: [{ path: /, pathType: Prefix }]
storage:
type: elasticsearch
elasticsearch:
host: elasticsearch.observability.svc
port: 9200
Kubernetes マニフェスト(直接定義)
apiVersion: v1
kind: ConfigMap
metadata:
name: jaeger-config
namespace: observability
data:
config.yaml: |
receivers:
otlp:
protocols:
grpc: { endpoint: "0.0.0.0:4317" }
http: { endpoint: "0.0.0.0:4318" }
processors:
batch: { send_batch_size: 10000, timeout: 10s }
memory_limiter: { check_interval: 1s, limit_mib: 1500, spike_limit_mib: 512 }
extensions:
jaeger_storage:
backends:
main_storage:
elasticsearch:
server_urls: "https://elasticsearch.observability.svc:9200"
index_prefix: jaeger
username: jaeger
password: "${ES_PASSWORD}"
num_shards: 5
num_replicas: 1
tags_as_fields: { all: true }
jaeger_query:
storage: { traces: main_storage }
http: { endpoint: "0.0.0.0:16686" }
exporters:
jaeger_storage_exporter: { trace_storage: main_storage }
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [jaeger_storage_exporter]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
namespace: observability
spec:
replicas: 3
selector:
matchLabels: { app: jaeger }
template:
metadata:
labels: { app: jaeger }
annotations: { prometheus.io/scrape: "true", prometheus.io/port: "8888" }
spec:
containers:
- name: jaeger
image: jaegertracing/jaeger:2.4
args: ["--config=/etc/jaeger/config.yaml"]
ports:
- { name: otlp-grpc, containerPort: 4317 }
- { name: otlp-http, containerPort: 4318 }
- { name: query-http, containerPort: 16686 }
- { name: metrics, containerPort: 8888 }
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: 2000m, memory: 2Gi }
livenessProbe:
httpGet: { path: /, port: 13133 }
readinessProbe:
httpGet: { path: /, port: 13133 }
volumeMounts:
- { name: config, mountPath: /etc/jaeger }
volumes:
- name: config
configMap: { name: jaeger-config }
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: jaeger
namespace: observability
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: jaeger
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }
4.3 サイドカーパターン
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
- name: order-service
image: myregistry/order-service:v1.2.0
env:
- { name: OTEL_EXPORTER_OTLP_ENDPOINT, value: "http://localhost:4317" }
- { name: OTEL_SERVICE_NAME, value: "order-service" }
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.96.0
args: ["--config=/etc/otel/config.yaml"]
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }
4.4 DaemonSet パターン
各ノードに OTel Collector を DaemonSet としてデプロイするパターン。サイドカーパターンよりもリソース効率が良い。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-collector
namespace: observability
spec:
template:
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.96.0
ports:
- { containerPort: 4317, hostPort: 4317 }
- { containerPort: 4318, hostPort: 4318 }
5. Jaeger UI と可視化機能
5.1 主要画面
- Search: サービス名、オペレーション名、タグ/属性、時間範囲、レイテンシ範囲でトレースを検索
- Trace Detail: タイムラインビュー(ガントチャート形式)、スパンの階層構造、各スパンの詳細情報
- Trace Comparison: 2つのトレースの並列比較と差分の自動検出
- System Architecture: DAG 形式のサービス依存関係グラフ
- Monitor (SPM): RED メトリクス(Rate, Error, Duration)のダッシュボード
5.2 トレース検索 API
curl "http://jaeger:16686/api/traces?\
service=order-service&\
operation=HTTP%20POST%20/api/checkout&\
minDuration=5s&\
lookback=3600000000&\
tags=http.status_code%3D500&\
limit=20"
5.3 トレース詳細ビュー
Trace: abc123 | 12 Spans | 2.3s
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
order-service: HTTP GET /api/orders/123 [2.3s]
├─ user-service: GetUserProfile [450ms]
├─ inventory-service: CheckStock [850ms]
│ └─ redis: GET inventory:123 [120ms]
└─ payment-service: ValidatePayment [1.2s] ← ボトルネック
├─ fraud-detection: AnalyzeTransaction [800ms]
└─ bank-gateway: AuthorizePayment [350ms]
5.4 Service Performance Monitoring(SPM)
SPM はトレースデータから RED メトリクスを自動生成する。
connectors:
spanmetrics:
histogram:
explicit:
buckets: [2ms, 4ms, 6ms, 8ms, 10ms, 50ms, 100ms, 200ms, 400ms, 800ms, 1s, 5s, 10s]
dimensions:
- name: http.method
- name: http.status_code
- name: http.route
exemplars:
enabled: true
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger_storage_exporter, spanmetrics]
metrics/spanmetrics:
receivers: [spanmetrics]
exporters: [prometheus]
生成されるメトリクスの例:
# リクエストレート
rate(calls_total{service_name="order-service"}[5m])
# エラーレート
rate(calls_total{service_name="order-service", status_code="STATUS_CODE_ERROR"}[5m])
/ rate(calls_total{service_name="order-service"}[5m])
# P99 レイテンシ
histogram_quantile(0.99, rate(duration_milliseconds_bucket{service_name="order-service"}[5m]))
5.5 Grafana との統合
# Grafana データソース設定
datasources:
- name: Jaeger
type: jaeger
url: http://jaeger-query:16686
jsonData:
tracesToLogsV2:
datasourceUid: 'loki'
filterByTraceID: true
tracesToMetrics:
datasourceUid: 'prometheus'
queries:
- name: 'Request Rate'
query: 'rate(calls_total{service_name="${__tags.service_name}"}[5m])'
nodeGraph:
enabled: true
6. セキュリティ
6.1 TLS / mTLS の設定
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
tls:
cert_file: /certs/server.crt
key_file: /certs/server.key
client_ca_file: /certs/ca.crt # mTLS
min_version: "1.3"
6.2 データのサニタイゼーション
processors:
attributes:
actions:
- key: db.statement
action: delete
- key: http.request.header.authorization
action: delete
- key: user.email
pattern: '(.).*@(.).*\.(.*)'
replacement: '$1***@$2***.$3'
action: update
6.3 RBAC
Jaeger UI へのアクセス制御は、リバースプロキシや OAuth2 Proxy で実装する。Ingress の auth-url アノテーションを使用した認証設定が一般的である。
7. 運用とベストプラクティス
7.1 パフォーマンスチューニング
processors:
memory_limiter:
check_interval: 1s
limit_mib: 4096
spike_limit_mib: 1024
limit_percentage: 80
batch:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
service:
telemetry:
logs:
level: warn # 本番環境ではログレベルを下げる
7.2 監視とアラート
# Prometheus アラートルール
groups:
- name: jaeger-collector
rules:
- alert: JaegerCollectorSpansDropped
expr: rate(otelcol_receiver_refused_spans_total[5m]) > 0
for: 5m
labels: { severity: warning }
- alert: JaegerCollectorQueueFull
expr: otelcol_exporter_queue_size / otelcol_exporter_queue_capacity > 0.8
for: 5m
labels: { severity: warning }
- alert: JaegerStorageWriteErrors
expr: rate(otelcol_exporter_send_failed_spans_total[5m]) > 0
for: 5m
labels: { severity: critical }
- alert: JaegerQueryHighLatency
expr: histogram_quantile(0.99, rate(jaeger_query_latency_bucket[5m])) > 10
for: 10m
labels: { severity: warning }
7.3 トラブルシューティング
スパンが表示されない場合:
# Collector がスパンを受信しているか確認
curl http://jaeger-collector:8888/metrics | grep otelcol_receiver_accepted_spans_total
# ストレージへの書き込みエラーを確認
curl http://jaeger-collector:8888/metrics | grep otelcol_exporter_send_failed_spans_total
メモリ不足の場合:
processors:
memory_limiter:
check_interval: 1s
limit_mib: 2048 # Pod のメモリ制限の 80% に設定
spike_limit_mib: 512
7.4 計装のベストプラクティス
スパンの命名規約:
良い例: HTTP GET /api/orders/{id}
悪い例: HTTP GET /api/orders/12345 (高カーディナリティ)
エラーの記録:
with tracer.start_as_current_span("process_order") as span:
try:
result = process_order(order_id)
except ValidationError as e:
span.set_status(StatusCode.ERROR, str(e))
span.record_exception(e)
raise
8. 高度なトピック
8.1 マルチテナンシー
テナントごとのパイプライン分離:
processors:
filter/tenant_a:
traces:
span:
- 'resource.attributes["tenant.id"] == "tenant-a"'
exporters:
jaeger_storage_exporter/tenant_a:
trace_storage: storage_tenant_a
jaeger_storage_exporter/tenant_b:
trace_storage: storage_tenant_b
service:
pipelines:
traces/tenant_a:
receivers: [otlp]
processors: [filter/tenant_a, batch]
exporters: [jaeger_storage_exporter/tenant_a]
8.2 クロスクラスタートレーシング
┌──────────────────┐ ┌──────────────────┐
│ Cluster A │ │ Cluster B │
│ Service A ────────────→ Service B │
│ OTel Collector │ │ OTel Collector │
└────────┬─────────┘ └────────┬─────────┘
└──────────┬─────────────┘
▼
Central Jaeger Collector
▼
Elasticsearch
各クラスターの Collector でリソース属性を付加:
processors:
resource:
attributes:
- key: k8s.cluster.name
value: cluster-a
action: upsert
8.3 ClickHouse バックエンド
CREATE TABLE jaeger_spans (
timestamp DateTime64(6),
traceID String,
spanID String,
parentSpanID String,
operationName LowCardinality(String),
serviceName LowCardinality(String),
duration UInt64,
tags Map(LowCardinality(String), String)
) ENGINE = MergeTree()
PARTITION BY toDate(timestamp)
ORDER BY (serviceName, operationName, timestamp)
TTL toDateTime(timestamp) + INTERVAL 30 DAY;
8.4 カスタムコンポーネント開発
OpenTelemetry Collector のカスタムプロセッサを Go で開発できる。processor.Factory インターフェースを実装し、ConsumeTraces メソッドでスパンデータを処理する。
9. 実践的なユースケース
9.1 レイテンシ分析
チェックアウト API の P99 レイテンシが 5 秒を超えている場合の分析手順:
- 遅いトレースの検索:
minDuration=5sで検索 - ボトルネックの特定: タイムラインビューで各スパンの所要時間を確認
- 根本原因の調査: 最も遅いスパンの属性を確認
- 対策の実施と効果測定: 改善後に同じ条件でトレースを比較
9.2 障害の根本原因分析(RCA)
import requests
from collections import Counter
def analyze_errors(service, lookback_hours=1):
resp = requests.get(f"{JAEGER_URL}/api/traces", params={
"service": service,
"tags": "error=true",
"limit": 200,
})
traces = resp.json()["data"]
error_services = Counter()
for trace_data in traces:
for span in trace_data["spans"]:
is_error = any(
tag["key"] == "error" and tag["value"] is True
for tag in span.get("tags", [])
)
if is_error:
process = trace_data["processes"][span["processID"]]
error_services[process["serviceName"]] += 1
for svc, count in error_services.most_common(10):
print(f" {svc}: {count}")
9.3 CI/CD パイプラインでのトレーシング
デプロイ前後のパフォーマンス比較を自動化し、リグレッション検出に活用する。P50/P95/P99 レイテンシとエラーレートを比較し、20% 以上の悪化があればアラートを発行する。
10. 他のトレーシングツールとの比較
10.1 Jaeger vs Zipkin
| 特性 | Jaeger | Zipkin |
|---|---|---|
| 開発元 | Uber → CNCF | |
| 言語 | Go | Java |
| CNCF ステータス | Graduated | なし |
| OTel 統合 | ネイティブ(v2 は OTel Collector ベース) | OTel SDK 経由 |
| 適応型サンプリング | あり | なし |
10.2 Jaeger vs Grafana Tempo
| 特性 | Jaeger | Grafana Tempo |
|---|---|---|
| ストレージ | ES、Cassandra 等 | オブジェクトストレージ(S3、GCS) |
| コスト | ストレージに依存 | 非常に低い |
| 検索機能 | 全文検索(ES 使用時) | TraceQL |
| UI | 自前の UI あり | Grafana に依存 |
10.3 選定ガイドライン
Jaeger を選ぶべき場合:
- オンプレミスまたはマルチクラウド環境
- 完全なカスタマイズが必要
- CNCF エコシステムとの統合
- 適応型サンプリングが必要
Tempo を選ぶべき場合:
- Grafana スタックを使用中
- ストレージコストを最小化したい
- TraceQL による高度なクエリが必要
Zipkin を選ぶべき場合:
- シンプルなセットアップを重視
- 小〜中規模のシステム
11. まとめと今後の展望
11.1 Jaeger v2 のまとめ
- 統一されたアーキテクチャ: 単一バイナリへの統合により運用が簡素化
- OpenTelemetry ネイティブ: 業界標準との完全な統合
- 拡張可能なパイプライン: Receiver→Processor→Exporter モデル
- 豊富なストレージ選択肢: ワークロードに応じた最適なバックエンド選択
- SPM の統合: RED メトリクスの自動生成
11.2 今後の展望
- OpenTelemetry Profiling: トレースとプロファイルの相関分析
- eBPF ベースの自動計装: コード変更なしのトレーシング
- AI/ML によるトレース分析: 異常検出と根本原因分析の自動化
- コスト最適化: 列指向ストレージ、適応型サンプリングの高度化
11.3 導入のロードマップ
Phase 1: 評価・PoC(1-2週間)
- All-in-One デプロイで評価
- 1-2 サービスに OTel SDK を導入
Phase 2: パイロット運用(2-4週間)
- ES/Cassandra のセットアップ
- 主要サービスの計装
Phase 3: 本番展開(4-8週間)
- 全サービスへの計装展開
- 適応型サンプリング、SPM、Grafana 統合
Phase 4: 最適化・高度化(継続的)
- Tail-based サンプリング
- カスタムプロセッサ開発
- CI/CD パイプライン統合
付録
A. ポート番号一覧
| ポート | プロトコル | 用途 |
|---|---|---|
| 4317 | gRPC | OTLP Receiver(推奨) |
| 4318 | HTTP | OTLP Receiver(推奨) |
| 14250 | gRPC | Jaeger gRPC Receiver(レガシー) |
| 14268 | HTTP | Jaeger HTTP Receiver(レガシー) |
| 6831 | UDP | Jaeger Compact Thrift(レガシー) |
| 6832 | UDP | Jaeger Binary Thrift(レガシー) |
| 9411 | HTTP | Zipkin Receiver |
| 16686 | HTTP | Jaeger Query UI |
| 16685 | gRPC | Jaeger Query gRPC |
| 8888 | HTTP | Collector メトリクス |
| 8889 | HTTP | SPM メトリクス(Prometheus) |
| 13133 | HTTP | ヘルスチェック |
B. 環境変数一覧(OpenTelemetry SDK 共通)
| 環境変数 | 説明 | デフォルト値 |
|---|---|---|
OTEL_SERVICE_NAME | サービス名 | unknown_service |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP エンドポイント | http://localhost:4317 |
OTEL_EXPORTER_OTLP_PROTOCOL | プロトコル | grpc |
OTEL_TRACES_SAMPLER | サンプラータイプ | parentbased_always_on |
OTEL_TRACES_SAMPLER_ARG | サンプラーパラメータ | なし |
OTEL_RESOURCE_ATTRIBUTES | リソース属性 | なし |
OTEL_PROPAGATORS | コンテキスト伝播形式 | tracecontext,baggage |
OTEL_BSP_SCHEDULE_DELAY | バッチエクスポート間隔 | 5000ms |
OTEL_BSP_MAX_QUEUE_SIZE | キューの最大サイズ | 2048 |
OTEL_BSP_MAX_EXPORT_BATCH_SIZE | バッチの最大サイズ | 512 |
C. 参考リンク
- Jaeger 公式サイト: https://www.jaegertracing.io/
- Jaeger GitHub: https://github.com/jaegertracing/jaeger
- OpenTelemetry 公式サイト: https://opentelemetry.io/
- OpenTelemetry Collector: https://github.com/open-telemetry/opentelemetry-collector
- W3C Trace Context: https://www.w3.org/TR/trace-context/
- Google Dapper 論文: https://research.google/pubs/pub36356/
- CNCF Jaeger: https://www.cncf.io/projects/jaeger/