Zipkin

Zipkin 徹底解説 ― 分散トレーシングの基盤技術

目次

  1. はじめに ― 分散トレーシングとは何か
  2. Zipkin の歴史と背景
  3. 基本概念と用語
  4. アーキテクチャ概要
  5. データモデル
  6. 計装(Instrumentation)
  7. トランスポート層
  8. コレクター(Collector)
  9. ストレージバックエンド
  10. API と クエリサービス
  11. Zipkin UI
  12. デプロイメントパターン
  13. Docker / Kubernetes 環境での構築
  14. Spring Boot アプリケーションとの統合
  15. Go / Python / Node.js での計装
  16. サンプリング戦略
  17. パフォーマンスチューニング
  18. OpenTelemetry との統合
  19. 他の分散トレーシングシステムとの比較
  20. 本番運用のベストプラクティス
  21. トラブルシューティング
  22. まとめ

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 社内で開発された分散トレーシングシステムであり、以下の設計原則を打ち出した。

  1. 低オーバーヘッド: トレーシングによるパフォーマンスへの影響を最小限に抑える
  2. アプリケーション透過性: 開発者がトレーシングのために大幅なコード変更を行う必要がない
  3. スケーラビリティ: 大規模システムでも安定して動作する
  4. 迅速なデータ利用: 収集したデータを速やかに分析に利用できる

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 をオープンソースとして公開した。その後の主要なマイルストーンは以下の通りである。

イベント
2012Twitter が Zipkin をオープンソースとして公開
2013コミュニティによる多言語クライアントの開発が活発化
2015OpenZipkin プロジェクトとして再編。Java ベースに移行開始
2016Zipkin 2 のリリース。アーキテクチャの大幅刷新
2017B3 Propagation の標準化
2018Elasticsearch ストレージの公式サポート
2019OpenTelemetry プロジェクトとの統合が進む
2020Armeria ベースのサーバーに移行
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ビットID463ac35c9f6413ad48485a3953bb6124
idスパン自身を識別する64ビットID0020000000000001
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つの標準アノテーションが定義されていた。

アノテーション意味説明
csClient Sendクライアントがリクエストを送信した時刻
srServer Receiveサーバーがリクエストを受信した時刻
ssServer Sendサーバーがレスポンスを送信した時刻
crClient 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 データフロー

  1. スパン生成: アプリケーションがリクエストを処理する際、計装ライブラリがスパンを生成する
  2. コンテキスト伝播: リクエストヘッダーを介してトレースコンテキストが下流サービスに伝播される
  3. スパン送信: 各サービスのレポーターが、バッファリングされたスパンデータを Zipkin Server に送信する
  4. スパン受信: コレクターがスパンデータを受信し、バリデーションを行う
  5. ストレージ書き込み: バリデーション済みのスパンがストレージバックエンドに書き込まれる
  6. クエリ: ユーザーが 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. 他の分散トレーシングシステムとの比較

特性ZipkinJaegerTempoAWS X-Ray
開発元Twitter (OSS)Uber (CNCF)Grafana LabsAWS
言語JavaGoGoマネージド
ストレージMem/MySQL/Cassandra/ESCassandra/ES/Kafka/BadgerS3/GCS/Azure BlobAWS 独自
UI内蔵内蔵GrafanaAWS Console
サンプリングHead-basedAdaptiveHead/Tail独自
ライセンスApache 2.0Apache 2.0AGPL 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 スパンが欠落する

  1. NTP によるクロック同期を確認
  2. すべてのサービスで同じサンプリング設定を使用
  3. HTTP ヘッダーの伝播を確認
  4. 非同期処理でのコンテキスト伝播を確認

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 の強み

  1. 成熟したエコシステム: 10年以上の歴史と多くの言語・フレームワーク統合
  2. シンプルなデプロイメント: 単一 JAR で起動可能
  3. B3 Propagation: 事実上の標準伝播プロトコル
  4. 柔軟なストレージ: 環境に応じた選択が可能
  5. 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 移行検討、容量計画

参考文献

  1. Sigelman, B. H., et al. "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure." Google Technical Report, 2010.
  2. OpenZipkin Project. "Zipkin." https://zipkin.io/
  3. OpenZipkin. "Brave - Java distributed tracing implementation." https://github.com/openzipkin/brave
  4. OpenZipkin. "B3 Propagation." https://github.com/openzipkin/b3-propagation
  5. W3C. "Trace Context." https://www.w3.org/TR/trace-context/
  6. OpenTelemetry. "OpenTelemetry Documentation." https://opentelemetry.io/docs/
  7. Spring. "Micrometer Tracing." https://micrometer.io/docs/tracing
  8. OpenZipkin. "Zipkin Architecture." https://zipkin.io/pages/architecture.html