Zipkin
Zipkin 徹底解説 ― 分散トレーシングの基盤技術
目次
- はじめに ― 分散トレーシングとは何か
- Zipkin の歴史と背景
- 基本概念と用語
- アーキテクチャ概要
- データモデル
- 計装(Instrumentation)
- トランスポート層
- コレクター(Collector)
- ストレージバックエンド
- API と クエリサービス
- Zipkin UI
- デプロイメントパターン
- Docker / Kubernetes 環境での構築
- Spring Boot アプリケーションとの統合
- Go / Python / Node.js での計装
- サンプリング戦略
- パフォーマンスチューニング
- OpenTelemetry との統合
- 他の分散トレーシングシステムとの比較
- 本番運用のベストプラクティス
- トラブルシューティング
- まとめ
1. はじめに ― 分散トレーシングとは何か
1.1 マイクロサービス時代の可観測性
現代のソフトウェアアーキテクチャは、モノリシックなアプリケーションからマイクロサービスへと大きくシフトした。1つのユーザーリクエストが数十、時には数百のサービスを横断して処理されることは珍しくない。この状況下で「リクエストがどのサービスをどの順序で通過し、各サービスでどれだけの時間を費やしたか」を把握することは、パフォーマンス最適化や障害分析において不可欠である。
可観測性(Observability)の三本柱として、以下が広く知られている。
| 柱 | 概要 | 代表的ツール |
|---|---|---|
| メトリクス | 数値指標の時系列データ | Prometheus, Datadog |
| ログ | イベントの詳細な記録 | ELK Stack, Splunk |
| トレース | リクエストのエンドツーエンドの追跡 | Zipkin, Jaeger |
分散トレーシングは、この三本柱の一つであり、リクエストがシステム全体を通過する過程を可視化する技術である。
1.2 分散トレーシングが解決する課題
分散トレーシングがなければ、以下のような問題に直面する。
- レイテンシの原因特定が困難: あるAPIエンドポイントのレスポンスが遅い場合、どの下流サービスがボトルネックなのか特定できない
- 障害の伝播経路が不明: カスケード障害が発生した場合、根本原因を追跡できない
- 依存関係の把握が困難: サービス間の実際の通信パターンをドキュメントだけで管理することは現実的ではない
- 非同期処理のデバッグ: メッセージキューを介した非同期処理の追跡が極めて難しい
分散トレーシングは、一意の識別子(Trace ID)をリクエストに付与し、その識別子をサービス間で伝播させることで、リクエストの全体像を再構築する。
1.3 Zipkin の位置づけ
Zipkin は、オープンソースの分散トレーシングシステムである。2012年に Twitter 社が Google の Dapper 論文に基づいて開発し、その後オープンソースとして公開された。現在では、分散トレーシングの分野におけるデファクトスタンダードの一つとして広く利用されている。
Zipkin の主な特徴を以下に示す。
- シンプルなアーキテクチャ: 単一の JAR ファイルで起動可能で、初期導入が容易
- 豊富な言語サポート: Java, Go, Python, Ruby, JavaScript, C# など主要言語に対応
- 柔軟なストレージ: インメモリ、MySQL, Cassandra, Elasticsearch に対応
- 標準プロトコル対応: B3 Propagation, W3C Trace Context をサポート
- 活発なコミュニティ: OpenZipkin として GitHub 上で活発に開発が継続されている
2. Zipkin の歴史と背景
2.1 Google Dapper 論文
Zipkin を理解するには、その思想的基盤となった Google Dapper 論文(2010年発表)を理解することが重要である。
Dapper は Google 社内で開発された分散トレーシングシステムであり、以下の設計原則を打ち出した。
- 低オーバーヘッド: トレーシングによるパフォーマンスへの影響を最小限に抑える
- アプリケーション透過性: 開発者がトレーシングのために大幅なコード変更を行う必要がない
- スケーラビリティ: 大規模システムでも安定して動作する
- 迅速なデータ利用: 収集したデータを速やかに分析に利用できる
Dapper は以下の基本概念を定義した。
- Trace(トレース): 1つのリクエストに関連するすべての操作を表すツリー構造
- Span(スパン): トレースを構成する個々の操作単位
- Annotation(アノテーション): スパンに付加される時刻付きイベント情報
2.2 Twitter での誕生
2012年、Twitter 社のエンジニアリングチームは、自社のマイクロサービスアーキテクチャにおけるパフォーマンス問題の調査のため、Dapper の概念を実装した。これが Zipkin の原型である。
Twitter はかつて「Fail Whale(障害クジラ)」と呼ばれる頻繁な障害に悩まされていたが、マイクロサービス化を推進する中で、サービス間の依存関係とレイテンシの可視化が急務となった。Zipkin はこの課題を解決するために生まれた。
当初 Zipkin は以下のコンポーネントで構成されていた。
- Scala で記述されたコレクター
- Cassandra ベースのストレージ
- 基本的な Web UI
2.3 オープンソース化と進化
2012年6月に Twitter は Zipkin をオープンソースとして公開した。その後の主要なマイルストーンは以下の通りである。
| 年 | イベント |
|---|---|
| 2012 | Twitter が Zipkin をオープンソースとして公開 |
| 2013 | コミュニティによる多言語クライアントの開発が活発化 |
| 2015 | OpenZipkin プロジェクトとして再編。Java ベースに移行開始 |
| 2016 | Zipkin 2 のリリース。アーキテクチャの大幅刷新 |
| 2017 | B3 Propagation の標準化 |
| 2018 | Elasticsearch ストレージの公式サポート |
| 2019 | OpenTelemetry プロジェクトとの統合が進む |
| 2020 | Armeria ベースのサーバーに移行 |
| 2021-現在 | OpenTelemetry Collector との統合強化、継続的改善 |
2.4 Zipkin と B3 Propagation
Zipkin が生み出した最も影響力のある技術の一つが B3 Propagation である。B3 は "BigBrotherBird"(Zipkin の旧コードネーム)の略であり、HTTP ヘッダーを用いてトレースコンテキストをサービス間で伝播するための仕様である。
B3 ヘッダーの構成は以下の通りである。
X-B3-TraceId: 463ac35c9f6413ad48485a3953bb6124
X-B3-SpanId: 0020000000000001
X-B3-ParentSpanId: 0010000000000001
X-B3-Sampled: 1
X-B3-Flags: 0
また、単一ヘッダー形式も定義されている。
b3: 463ac35c9f6413ad48485a3953bb6124-0020000000000001-1-0010000000000001
B3 Propagation は多くの分散トレーシングシステムで採用され、事実上の標準となった。現在は W3C Trace Context との互換性も確保されている。
3. 基本概念と用語
Zipkin を効果的に活用するためには、その基本概念と用語を正確に理解する必要がある。
3.1 Trace(トレース)
トレースは、分散システムにおける1つのエンドツーエンドリクエストを表す。トレースは複数のスパンで構成されるツリー構造(正確には有向非巡回グラフ)である。
トレースの例を視覚的に表すと以下のようになる。
Trace ID: abcdef1234567890
[Service A: GET /api/orders] ─────────────────────────────────>
[Service B: GET /users/123] ──────────>
[Service C: GET /inventory] ──────────────────>
[Service D: SELECT * FROM products] ────>
[Service E: POST /payment] ──────────────────────────>
各行が1つのスパンに対応し、インデントが親子関係を、水平方向の長さがレイテンシを表す。
3.2 Span(スパン)
スパンは、トレースを構成する基本単位であり、1つの論理的な操作を表す。各スパンは以下の情報を持つ。
| フィールド | 説明 | 例 |
|---|---|---|
traceId | トレース全体を識別する128ビットID | 463ac35c9f6413ad48485a3953bb6124 |
id | スパン自身を識別する64ビットID | 0020000000000001 |
parentId | 親スパンのID(ルートスパンの場合はなし) | 0010000000000001 |
name | 操作名 | get /api/users |
timestamp | スパンの開始時刻(マイクロ秒) | 1620000000000000 |
duration | スパンの所要時間(マイクロ秒) | 50000 |
kind | スパンの種類 | CLIENT, SERVER, PRODUCER, CONSUMER |
localEndpoint | 操作を実行したサービス | {"serviceName": "api-gateway"} |
remoteEndpoint | 通信先のサービス | {"serviceName": "user-service"} |
tags | キーバリューペアのメタデータ | {"http.method": "GET", "http.status_code": "200"} |
annotations | 時刻付きイベント | {"value": "retry", "timestamp": 1620000025000} |
3.3 Span Kind(スパンの種類)
Zipkin では、スパンの種類(kind)を以下の4つに分類する。
CLIENT(クライアント): リクエストを送信する側のスパン。リモートサービスへのリクエストの開始と応答の受信を記録する。
SERVER(サーバー): リクエストを受信する側のスパン。リクエストの受信と応答の送信を記録する。
PRODUCER(プロデューサー): メッセージキューにメッセージを送信する側のスパン。
CONSUMER(コンシューマー): メッセージキューからメッセージを受信する側のスパン。
3.4 Annotation(アノテーション)
アノテーションは、スパンのライフサイクル中の特定の時点を記録するための機構である。Zipkin v1 では以下の4つの標準アノテーションが定義されていた。
| アノテーション | 意味 | 説明 |
|---|---|---|
cs | Client Send | クライアントがリクエストを送信した時刻 |
sr | Server Receive | サーバーがリクエストを受信した時刻 |
ss | Server Send | サーバーがレスポンスを送信した時刻 |
cr | Client Receive | クライアントがレスポンスを受信した時刻 |
Client Server
| |
| cs ──── Request ──── sr |
| |
| (処理)
| |
| cr ──── Response ─── ss |
| |
これらのアノテーションから以下のメトリクスを算出できる。
- ネットワークレイテンシ(往路):
sr - cs - サーバー処理時間:
ss - sr - ネットワークレイテンシ(復路):
cr - ss - クライアント視点の総所要時間:
cr - cs
Zipkin v2 では、これらの標準アノテーションは kind フィールドと timestamp / duration に置き換えられたが、カスタムアノテーションは引き続きサポートされている。
3.5 Tag(タグ)
タグは、スパンに付加するキーバリューペアのメタデータである。タグはスパンの検索やフィルタリングに使用される。
{
"http.method": "GET",
"http.path": "/api/users/123",
"http.status_code": "200",
"error": "true",
"sql.query": "SELECT * FROM users WHERE id = ?",
"peer.service": "mysql"
}
3.6 Endpoint(エンドポイント)
エンドポイントは、スパンを生成したサービスの情報を表す。localEndpoint はスパンを記録したサービス自身を、remoteEndpoint は通信先のサービスを表す。
4. アーキテクチャ概要
4.1 全体構成
Zipkin のアーキテクチャは、以下の主要コンポーネントで構成される。
┌─────────────────────────────────────────────────────────────────┐
│ Instrumented Applications │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Service A │ │Service B │ │Service C │ │Service D │ │
│ │(Reporter)│ │(Reporter)│ │(Reporter)│ │(Reporter)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼──────────────┼──────────────┼──────────────┼────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Transport Layer │
│ (HTTP / Kafka / RabbitMQ / gRPC / AMQP) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Zipkin Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Collector │ │ Storage │ │ Query Service │ │
│ │ │──▶│ Backend │◀──│ (API Server) │ │
│ └─────────────┘ └─────────────┘ └──────────┬──────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Zipkin UI │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
4.2 各コンポーネントの役割
- Reporter(レポーター): 各アプリケーションに組み込まれるライブラリ。スパンデータを生成し、Zipkin Server に送信する。
- Transport Layer(トランスポート層): レポーターからコレクターへスパンデータを転送する通信層。
- Collector(コレクター): スパンデータを受信し、バリデーション、変換、ストレージへの書き込みを行う。
- Storage Backend(ストレージバックエンド): スパンデータを永続化する。
- Query Service(クエリサービス): ストレージからスパンデータを検索・取得するための API を提供する。
- Zipkin UI(Web ダッシュボード): トレースの検索、可視化、分析を行うための Web インターフェース。
4.3 データフロー
- スパン生成: アプリケーションがリクエストを処理する際、計装ライブラリがスパンを生成する
- コンテキスト伝播: リクエストヘッダーを介してトレースコンテキストが下流サービスに伝播される
- スパン送信: 各サービスのレポーターが、バッファリングされたスパンデータを Zipkin Server に送信する
- スパン受信: コレクターがスパンデータを受信し、バリデーションを行う
- ストレージ書き込み: バリデーション済みのスパンがストレージバックエンドに書き込まれる
- クエリ: ユーザーが UI や API を通じてトレースデータを検索・閲覧する
4.4 スパンの送信モデル
バッチ送信: スパンは即時送信ではなく、一定数または一定時間ごとにバッチでまとめて送信される。
非同期送信: スパンの送信はアプリケーションのリクエスト処理とは独立した非同期スレッドで行われる。
フェイルセーフ: Zipkin Server が利用不能な場合、レポーターはスパンを破棄する。これは「トレーシングの障害がアプリケーションの障害を引き起こしてはならない」という設計原則に基づく。
5. データモデル
5.1 Zipkin v2 Span フォーマット
{
"traceId": "5982fe77008310cc80f1da5e10147517",
"id": "90394f6bcffb5d13",
"parentId": "5982fe77008310cc",
"name": "get /api/users/{id}",
"kind": "SERVER",
"timestamp": 1620000000000000,
"duration": 52340,
"localEndpoint": {
"serviceName": "user-service",
"ipv4": "10.0.0.5",
"port": 8080
},
"remoteEndpoint": {
"serviceName": "api-gateway",
"ipv4": "10.0.0.1",
"port": 54321
},
"annotations": [
{ "value": "cache-miss", "timestamp": 1620000000010000 }
],
"tags": {
"http.method": "GET",
"http.path": "/api/users/123",
"http.status_code": "200"
},
"shared": false,
"debug": false
}
5.2 Trace ID の形式
Zipkin v2 では、128ビット(32文字の16進数文字列)のトレースIDをサポートする。128ビットトレースIDの利用が推奨される。
5.3 スパンのマージ
同じトレースIDとスパンIDを持つスパンが、異なるサービスから報告された場合、Zipkin はこれらを1つの論理的なスパンとしてマージする。v2 では shared フラグを使用してこのマージ動作を制御する。
5.4 Protobuf フォーマット
message Span {
bytes trace_id = 1;
bytes parent_id = 2;
bytes id = 3;
Kind kind = 4;
string name = 5;
fixed64 timestamp = 6;
uint64 duration = 7;
Endpoint local_endpoint = 8;
Endpoint remote_endpoint = 9;
repeated Annotation annotations = 10;
map<string, string> tags = 11;
bool debug = 12;
bool shared = 13;
}
高スループット環境では Protobuf の利用が推奨される。JSON と比較して、データサイズが約30-50%削減される。
6. 計装(Instrumentation)
6.1 計装の概要
- 自動計装(Auto-Instrumentation): フレームワークのインターセプターやミドルウェアを利用して、コード変更なしにトレーシングを追加する方法。
- 手動計装(Manual Instrumentation): アプリケーションコード内で明示的にスパンの生成と管理を行う方法。
6.2 Brave(Java)
Brave は Zipkin 公式の Java 計装ライブラリである。
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>3.4.0</version>
</dependency>
import brave.Tracing;
import brave.sampler.Sampler;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.okhttp3.OkHttpSender;
public class TracingConfiguration {
public Tracing createTracing() {
OkHttpSender sender = OkHttpSender.create("http://zipkin:9411/api/v2/spans");
AsyncZipkinSpanHandler spanHandler = AsyncZipkinSpanHandler.create(sender);
return Tracing.newBuilder()
.localServiceName("my-service")
.addSpanHandler(spanHandler)
.sampler(Sampler.ALWAYS_SAMPLE)
.build();
}
}
手動スパン作成
public Order processOrder(OrderRequest request) {
Span span = tracer.nextSpan().name("process-order").start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
span.tag("order.id", request.getOrderId());
Order order = validateOrder(request);
span.annotate("order-validated");
processPayment(order);
return order;
} catch (Exception e) {
span.error(e);
throw e;
} finally {
span.finish();
}
}
6.3 Spring Boot との統合
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
</dependencies>
spring:
application:
name: order-service
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
上記の設定だけで、HTTP リクエスト、Spring MVC コントローラー、RestTemplate / WebClient によるHTTP呼び出し等が自動的に計装される。
6.4 Go での計装
package main
import (
"log"
"net/http"
"time"
"github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/middleware/http"
reporterhttp "github.com/openzipkin/zipkin-go/reporter/http"
)
func main() {
reporter := reporterhttp.NewReporter("http://zipkin:9411/api/v2/spans",
reporterhttp.BatchInterval(time.Second),
reporterhttp.BatchSize(100),
)
defer reporter.Close()
endpoint, _ := zipkin.NewEndpoint("my-go-service", "localhost:8080")
tracer, _ := zipkin.NewTracer(reporter,
zipkin.WithLocalEndpoint(endpoint),
zipkin.WithSampler(zipkin.AlwaysSample),
zipkin.WithTraceID128Bit(true),
)
serverMiddleware := zipkinhttp.NewServerMiddleware(tracer)
http.Handle("/api/orders", serverMiddleware(http.HandlerFunc(handler)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
6.5 Python での計装
from py_zipkin import Encoding
from py_zipkin.zipkin import zipkin_span, create_http_headers_for_new_span
@zipkin_span(service_name='order-service', span_name='process_order')
def process_order(order_id):
headers = create_http_headers_for_new_span()
user = call_downstream_service(headers)
return {'order_id': order_id, 'user': user, 'status': 'processed'}
6.6 Node.js での計装
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
traceExporter: new ZipkinExporter({
url: 'http://zipkin:9411/api/v2/spans',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
7. トランスポート層
7.1 HTTP トランスポート
最も基本的なトランスポート。設定が最も単純で、追加のインフラが不要。
7.2 Kafka トランスポート
高スループットな本番環境では推奨される。Kafka がバッファとして機能し、Zipkin Server のダウンタイム中もデータが保持される。
KafkaSender sender = KafkaSender.newBuilder()
.bootstrapServers("kafka-broker-1:9092,kafka-broker-2:9092")
.topic("zipkin")
.build();
KAFKA_BOOTSTRAP_SERVERS=kafka-broker-1:9092,kafka-broker-2:9092
KAFKA_TOPIC=zipkin
KAFKA_GROUP_ID=zipkin
7.3 RabbitMQ トランスポート
RabbitMQSender sender = RabbitMQSender.newBuilder()
.addresses("rabbitmq-host:5672")
.queue("zipkin")
.build();
7.4 トランスポート選択のガイドライン
| 要件 | 推奨トランスポート |
|---|---|
| 簡易セットアップ、小規模環境 | HTTP |
| 高スループット、大規模本番環境 | Kafka |
| 既存の RabbitMQ インフラがある | RabbitMQ |
| 低レイテンシ要件 | gRPC |
8. コレクター(Collector)
コレクターは、スパンデータの受信、デシリアライゼーション、バリデーション、変換、ストレージ書き込みを行う。
COLLECTOR_HTTP_ENABLED=true
COLLECTOR_KAFKA_ENABLED=true
COLLECTOR_KAFKA_BOOTSTRAP_SERVERS=kafka:9092
COLLECTOR_SAMPLE_RATE=1.0
9. ストレージバックエンド
9.1 インメモリストレージ
STORAGE_TYPE=mem
9.2 MySQL ストレージ
STORAGE_TYPE=mysql
MYSQL_HOST=mysql-host
MYSQL_TCP_PORT=3306
MYSQL_DB=zipkin
MYSQL_USER=zipkin
MYSQL_PASS=zipkin_password
9.3 Cassandra ストレージ
大規模環境に最も推奨される。
STORAGE_TYPE=cassandra3
CASSANDRA_CONTACT_POINTS=cassandra-1:9042,cassandra-2:9042,cassandra-3:9042
CASSANDRA_KEYSPACE=zipkin2
CASSANDRA_LOCAL_DC=datacenter1
9.4 Elasticsearch ストレージ
STORAGE_TYPE=elasticsearch
ES_HOSTS=http://elasticsearch-1:9200,http://elasticsearch-2:9200
ES_INDEX=zipkin
ES_INDEX_SHARDS=5
ES_INDEX_REPLICAS=1
9.5 ストレージ選択のガイドライン
| 要件 | 推奨ストレージ |
|---|---|
| 開発・テスト環境 | インメモリ |
| 小規模本番(〜100 spans/sec) | MySQL |
| 中〜大規模本番(〜10,000 spans/sec) | Elasticsearch |
| 大規模本番(10,000+ spans/sec) | Cassandra |
| 全文検索・柔軟なクエリ | Elasticsearch |
| 最大の書き込みスループット | Cassandra |
10. API とクエリサービス
10.1 主要エンドポイント
| メソッド | パス | 説明 |
|---|---|---|
POST | /api/v2/spans | スパンデータの送信 |
GET | /api/v2/traces | トレースの検索 |
GET | /api/v2/trace/{traceId} | 特定トレースの取得 |
GET | /api/v2/services | サービス一覧の取得 |
GET | /api/v2/dependencies | サービス依存関係の取得 |
10.2 トレース検索 API
curl -s "http://zipkin:9411/api/v2/traces?\
serviceName=api-gateway&\
spanName=get+/api/orders&\
minDuration=1000000&\
limit=20" | jq .
10.3 サービス依存関係 API
curl -s "http://zipkin:9411/api/v2/dependencies?\
endTs=$(date +%s000)&\
lookback=86400000" | jq .
11. Zipkin UI
Zipkin UI は、トレースの検索、タイムライン表示(ガントチャート形式)、依存関係グラフの表示を提供する。
ZIPKIN_UI_BASEPATH=/zipkin
ZIPKIN_UI_DEFAULT_LOOKBACK=3600000
ZIPKIN_UI_ENVIRONMENT=production
12. デプロイメントパターン
12.1 シングルインスタンス構成
docker run -d -p 9411:9411 openzipkin/zipkin
12.2 高可用性構成
Kafka によるバッファリングと Elasticsearch/Cassandra による永続化を組み合わせ、Zipkin Server を複数インスタンスでデプロイする。
12.3 サイドカーパターン
各サービスに Zipkin プロキシをサイドカーとしてデプロイし、送信レイテンシを最小化する。
13. Docker / Kubernetes 環境での構築
13.1 Docker Compose による構築
version: '3.8'
services:
zipkin:
image: openzipkin/zipkin:latest
ports:
- "9411:9411"
environment:
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://elasticsearch:9200
- KAFKA_BOOTSTRAP_SERVERS=kafka:29092
- JAVA_OPTS=-Xms512m -Xmx512m
depends_on:
elasticsearch:
condition: service_healthy
kafka:
condition: service_healthy
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
volumes:
- es-data:/usr/share/elasticsearch/data
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:7.5.0
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
volumes:
es-data:
13.2 Kubernetes での構築
apiVersion: apps/v1
kind: Deployment
metadata:
name: zipkin
namespace: observability
spec:
replicas: 2
selector:
matchLabels:
app: zipkin
template:
metadata:
labels:
app: zipkin
spec:
containers:
- name: zipkin
image: openzipkin/zipkin:latest
ports:
- containerPort: 9411
env:
- name: STORAGE_TYPE
value: elasticsearch
- name: ES_HOSTS
value: http://elasticsearch.observability.svc.cluster.local:9200
- name: JAVA_OPTS
value: "-Xms512m -Xmx512m -XX:+UseG1GC"
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 1000m
memory: 2Gi
readinessProbe:
httpGet:
path: /health
port: 9411
initialDelaySeconds: 30
livenessProbe:
httpGet:
path: /health
port: 9411
initialDelaySeconds: 60
---
apiVersion: v1
kind: Service
metadata:
name: zipkin
namespace: observability
spec:
type: ClusterIP
ports:
- port: 9411
targetPort: 9411
selector:
app: zipkin
14. Spring Boot アプリケーションとの統合
14.1 Spring Boot 3.x での詳細設定
spring:
application:
name: order-service
management:
tracing:
sampling:
probability: 1.0
propagation:
type: B3_MULTI
baggage:
enabled: true
remote-fields:
- requestId
- userId
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
connect-timeout: 1s
read-timeout: 10s
14.2 カスタムスパンの作成
@Service
public class OrderService {
private final ObservationRegistry observationRegistry;
public Order processOrder(OrderRequest request) {
return Observation.createNotStarted("order.process", observationRegistry)
.lowCardinalityKeyValue("order.type", request.getType())
.highCardinalityKeyValue("order.id", request.getId())
.observe(() -> {
validateOrder(request);
Payment payment = Observation.createNotStarted("order.payment", observationRegistry)
.observe(() -> processPayment(request));
return new Order(request.getId(), payment, "COMPLETED");
});
}
}
14.3 Baggage(手荷物)の伝播
try (BaggageInScope userId = tracer.createBaggageInScope("userId", userId);
BaggageInScope requestId = tracer.createBaggageInScope("requestId", requestId)) {
// この範囲内のすべての下流サービス呼び出しで伝播される
processRequest();
}
15. Go / Python / Node.js での計装(補足)
15.1 Go + gRPC の計装
server := grpc.NewServer(
grpc.StatsHandler(zipkingrpc.NewServerHandler(tracer)),
)
15.2 Python + FastAPI の計装
@app.middleware("http")
async def zipkin_middleware(request: Request, call_next):
with zipkin_span(
service_name='python-api-service',
span_name=f'{request.method} {request.url.path}',
transport_handler=HttpTransport(),
encoding=Encoding.V2_JSON,
sample_rate=100.0,
) as span:
response = await call_next(request)
return response
15.3 Node.js + OpenTelemetry 経由
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
traceExporter: new ZipkinExporter({ url: 'http://zipkin:9411/api/v2/spans' }),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
16. サンプリング戦略
16.1 固定確率サンプリング
Tracing.newBuilder().sampler(Sampler.create(0.1f)).build(); // 10%
16.2 レートリミットサンプリング
Tracing.newBuilder().sampler(RateLimitingSampler.create(100)).build(); // 100 traces/sec
16.3 カスタムサンプリング
SamplerFunction<HttpRequest> httpSampler = request -> {
if ("/health".equals(request.path())) return false; // ヘルスチェック除外
if (path.startsWith("/api/")) return Math.random() < 0.5; // API: 50%
return Math.random() < 0.1; // その他: 10%
};
16.4 Head-based vs Tail-based サンプリング
- Head-based: リクエスト開始時にサンプリング判断。シンプルだがエラーを見逃す可能性あり。
- Tail-based: リクエスト完了後にサンプリング判断。OpenTelemetry Collector で実現可能。
processors:
tail_sampling:
decision_wait: 30s
policies:
- name: errors-policy
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-traces-policy
type: latency
latency: {threshold_ms: 5000}
- name: probability-policy
type: probabilistic
probabilistic: {sampling_percentage: 10}
17. パフォーマンスチューニング
17.1 JVM 設定
JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
17.2 Elasticsearch チューニング
ES_INDEX_SHARDS=5
ES_INDEX_REPLICAS=1
ES_MAX_REQUESTS=64
17.3 Cassandra チューニング
CASSANDRA_MAX_CONNECTIONS=8
CASSANDRA_SPAN_TTL=604800 # 7日間
17.4 レポーターのチューニング
AsyncZipkinSpanHandler handler = AsyncZipkinSpanHandler.newBuilder(sender)
.messageMaxBytes(512 * 1024)
.messageTimeout(1, TimeUnit.SECONDS)
.build();
18. OpenTelemetry との統合
18.1 OpenTelemetry Collector の設定
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
zipkin:
endpoint: 0.0.0.0:9411
processors:
batch:
timeout: 1s
send_batch_size: 1024
memory_limiter:
limit_mib: 4096
tail_sampling:
decision_wait: 30s
policies:
- name: error-policy
type: status_code
status_code: { status_codes: [ERROR] }
- name: probabilistic-policy
type: probabilistic
probabilistic: { sampling_percentage: 10 }
exporters:
zipkin:
endpoint: "http://zipkin:9411/api/v2/spans"
service:
pipelines:
traces:
receivers: [otlp, zipkin]
processors: [memory_limiter, tail_sampling, batch]
exporters: [zipkin]
18.2 W3C Trace Context と B3 の互換性
TextMapPropagator propagator = TextMapPropagator.composite(
W3CTraceContextPropagator.getInstance(),
B3Propagator.injectingMultiHeaders()
);
19. 他の分散トレーシングシステムとの比較
| 特性 | Zipkin | Jaeger | Tempo | AWS X-Ray |
|---|---|---|---|---|
| 開発元 | Twitter (OSS) | Uber (CNCF) | Grafana Labs | AWS |
| 言語 | Java | Go | Go | マネージド |
| ストレージ | Mem/MySQL/Cassandra/ES | Cassandra/ES/Kafka/Badger | S3/GCS/Azure Blob | AWS 独自 |
| UI | 内蔵 | 内蔵 | Grafana | AWS Console |
| サンプリング | Head-based | Adaptive | Head/Tail | 独自 |
| ライセンス | Apache 2.0 | Apache 2.0 | AGPL v3 | 商用 |
| 成熟度 | 非常に高い | 高い | 中程度 | 高い |
Zipkin の優位点: 長い歴史、B3 Propagation、シンプルなデプロイ、Spring Boot との成熟した統合。
Jaeger の優位点: Adaptive Sampling、メモリ効率、CNCF Graduated。
Tempo の優位点: オブジェクトストレージによる低コスト、Grafana 統合。
20. 本番運用のベストプラクティス
20.1 アーキテクチャ設計
- Kafka を中間バッファとして使用する
- Elasticsearch または Cassandra をストレージに使用する
- Zipkin Server を複数インスタンスでデプロイし、ロードバランサーの背後に配置する
20.2 データ保持ポリシー
Hot Data (直近7日): 高速ストレージ(SSD)
Warm Data (7-30日): 標準ストレージ(HDD)
Cold Data (30-90日): オブジェクトストレージ
Archive (90日以降): 削除 or 長期アーカイブ
20.3 セキュリティ
- Nginx リバースプロキシによる認証の追加
- 機密データのマスキング
- Kubernetes NetworkPolicy による通信制御
20.4 監視とアラート
groups:
- name: zipkin-alerts
rules:
- alert: ZipkinDown
expr: up{job="zipkin"} == 0
for: 2m
- alert: ZipkinSpansDropped
expr: rate(zipkin_collector_spans_dropped_total[5m]) > 0
for: 5m
20.5 容量計画
1,000 req/sec * 10 spans * 500 bytes = 5 MB/sec = 432 GB/day
サンプリング率 10% の場合: 43.2 GB/day
保持期間 7日: 302.4 GB
21. トラブルシューティング
21.1 トレースが表示されない
curl -f http://zipkin:9411/health
curl -X POST http://zipkin:9411/api/v2/spans -H 'Content-Type: application/json' \
-d '[{"traceId":"test123","id":"span123","name":"test","timestamp":1620000000000000,"duration":1000,"localEndpoint":{"serviceName":"test"}}]'
curl "http://zipkin:9411/api/v2/traces?serviceName=test&limit=1"
21.2 スパンが欠落する
- NTP によるクロック同期を確認
- すべてのサービスで同じサンプリング設定を使用
- HTTP ヘッダーの伝播を確認
- 非同期処理でのコンテキスト伝播を確認
21.3 Elasticsearch への書き込みエラー
curl "http://elasticsearch:9200/_cluster/health?pretty"
curl "http://elasticsearch:9200/_cat/thread_pool/write?v&h=name,active,queue,rejected"
22. まとめ
22.1 Zipkin の強み
- 成熟したエコシステム: 10年以上の歴史と多くの言語・フレームワーク統合
- シンプルなデプロイメント: 単一 JAR で起動可能
- B3 Propagation: 事実上の標準伝播プロトコル
- 柔軟なストレージ: 環境に応じた選択が可能
- OpenTelemetry との統合: 現代の可観測性標準との互換性
22.2 導入ロードマップ
Phase 1 (Week 1-2): Zipkin Server 構築、1-2 サービスへの計装導入
Phase 2 (Week 3-4): 全サービスへの展開、Kafka + ES への移行
Phase 3 (Month 2): サンプリング最適化、アラート設定
Phase 4 (Month 3+): OpenTelemetry 移行検討、容量計画
参考文献
- Sigelman, B. H., et al. "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure." Google Technical Report, 2010.
- OpenZipkin Project. "Zipkin." https://zipkin.io/
- OpenZipkin. "Brave - Java distributed tracing implementation." https://github.com/openzipkin/brave
- OpenZipkin. "B3 Propagation." https://github.com/openzipkin/b3-propagation
- W3C. "Trace Context." https://www.w3.org/TR/trace-context/
- OpenTelemetry. "OpenTelemetry Documentation." https://opentelemetry.io/docs/
- Spring. "Micrometer Tracing." https://micrometer.io/docs/tracing
- OpenZipkin. "Zipkin Architecture." https://zipkin.io/pages/architecture.html