マイクロサービスアーキテクチャでは、1つのリクエストが複数のサービスを横断するため、問題発生時の原因特定が困難になります。分散トレーシングは、リクエストの流れを可視化し、パフォーマンスボトルネックやエラーの発生箇所を特定するための重要な手法です。本記事では、Spring Boot 3.xでMicrometer TracingとZipkinを使用した分散トレーシングの実装方法を解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Java |
17以上 |
| Spring Boot |
3.4.x |
| Micrometer Tracing |
1.4.x |
| Zipkin |
3.x |
| ビルドツール |
Gradle |
| IDE |
VS Code(Extension Pack for Javaインストール済み) |
| Docker |
Docker Desktop(Zipkin起動用) |
また、本記事はSpring Boot REST API入門 - @RestControllerでCRUDエンドポイントを実装するの続編として、基本的なSpring Boot REST APIの実装経験があることを前提としています。
期待される学習成果#
本記事を読み終えると、以下のことができるようになります。
- 分散トレーシングの概念(Trace ID、Span ID)を理解できる
- Micrometer TracingとBraveを使用したトレーシング設定ができる
- Zipkinにトレース情報を送信し、可視化できる
- ログにTrace IDを自動付与し、ログとトレースを関連付けられる
- RestClientを使用したサービス間呼び出しでトレースを伝播できる
分散トレーシングの基本概念#
分散トレーシングとは#
分散トレーシングは、分散システム内でリクエストがどのように処理されているかを追跡する技術です。従来のモノリシックアプリケーションでは、ログを時系列で追うことで処理の流れを把握できました。しかし、マイクロサービス環境では複数のサービスが連携するため、個別のログだけでは全体像を把握できません。
分散トレーシングでは、以下の2つの識別子を使用してリクエストを追跡します。
graph LR
subgraph "Trace ID: abc123"
A[Client] -->|Span A| B[API Gateway]
B -->|Span B| C[User Service]
B -->|Span C| D[Order Service]
D -->|Span D| E[Database]
endTrace IDとSpan IDの仕組み#
分散トレーシングでは、2つの重要な識別子を使用します。
| 識別子 |
説明 |
例 |
| Trace ID |
リクエスト全体を一意に識別する32文字の16進数 |
803B448A0489F84084905D3093480352 |
| Span ID |
個別の処理単位を識別する16文字の16進数 |
3425F23BB2432450 |
Trace IDは最初のリクエストで生成され、後続のすべてのサービス呼び出しに伝播されます。各サービスは独自のSpan IDを生成し、処理の開始時刻、終了時刻、メタデータを記録します。
Micrometer Tracingの役割#
Micrometer Tracingは、トレーシングのファサード(抽象化レイヤー)を提供するライブラリです。Spring Cloud Sleuthの後継として開発され、以下の特徴があります。
- ベンダーロックインの回避(OpenTelemetry、Braveなど複数のトレーサーに対応)
- Spring Boot Actuatorとの統合
- Micrometer Observationとの連携によるメトリクスとトレースの統合
本記事では、トレーサー実装としてOpenZipkin Braveを使用し、バックエンドとしてZipkinを使用します。
Micrometer Tracingの設定#
依存関係の追加#
Spring Boot 3.xでMicrometer TracingとBraveを使用するには、以下の依存関係を追加します。
1
2
3
4
5
6
7
8
9
10
11
|
// build.gradle
dependencies {
// Spring Boot Actuator(必須)
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Micrometer Tracing Bridge for Brave
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
// Zipkin Reporter for Brave
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
}
|
依存関係の構成を図で示すと以下のようになります。
graph TB
A[Spring Boot Application] --> B[Micrometer Tracing]
B --> C[micrometer-tracing-bridge-brave]
C --> D[Brave Tracer]
D --> E[zipkin-reporter-brave]
E --> F[Zipkin Server]アプリケーション設定#
application.ymlでトレーシングの設定を行います。
1
2
3
4
5
6
7
8
9
10
11
12
|
# application.yml
spring:
application:
name: user-service # サービス名(Zipkinで表示される)
management:
tracing:
sampling:
probability: 1.0 # サンプリング率(1.0 = 100%、本番では0.1程度を推奨)
export:
zipkin:
endpoint: http://localhost:9411/api/v2/spans # Zipkinのエンドポイント
|
| プロパティ |
説明 |
推奨値 |
spring.application.name |
サービス識別名 |
サービスごとに一意の名前 |
management.tracing.sampling.probability |
トレースのサンプリング率 |
開発: 1.0、本番: 0.1 |
management.tracing.export.zipkin.endpoint |
Zipkinのエンドポイント |
Zipkinサーバーのアドレス |
サンプリング率はパフォーマンスとトレース収集量のトレードオフです。開発環境ではすべてのリクエストを収集し、本番環境では10%程度に抑えることを推奨します。
Zipkinへのトレース情報送信#
Zipkinサーバーの起動#
Zipkinはトレース情報を収集・可視化するためのオープンソースツールです。Dockerを使用して簡単に起動できます。
1
2
|
# Zipkinサーバーの起動
docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin
|
起動後、ブラウザでhttp://localhost:9411にアクセスするとZipkin UIが表示されます。
サンプルAPIの実装#
トレーシングの動作を確認するため、シンプルなREST APIを実装します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
logger.info("Getting user with id: {}", id);
// 処理をシミュレート
simulateProcessing();
return new User(id, "John Doe", "john@example.com");
}
private void simulateProcessing() {
try {
Thread.sleep(100); // 100msの処理時間をシミュレート
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
|
1
2
3
4
|
package com.example.demo.controller;
public record User(Long id, String name, String email) {
}
|
トレースの確認#
アプリケーションを起動し、APIにリクエストを送信します。
1
|
curl http://localhost:8080/api/users/1
|
Zipkin UI(http://localhost:9411)で「Run Query」ボタンをクリックすると、収集されたトレースが表示されます。トレースをクリックすると、以下の情報を確認できます。
- サービス名とオペレーション名
- 処理時間
- Span間の親子関係
- タグ情報(HTTPメソッド、ステータスコードなど)
ログへのTrace ID自動付与#
ログ相関の設定#
Spring Boot 3.xでは、Micrometer Tracingを設定するとログにTrace IDとSpan IDが自動的に付与されます。デフォルトのログフォーマットでは[traceId-spanId]の形式で出力されます。
ログパターンをカスタマイズする場合は、application.ymlで設定します。
1
2
3
4
5
6
7
8
|
# application.yml
logging:
pattern:
correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "
include-application-name: false
level:
root: INFO
com.example.demo: DEBUG
|
この設定により、ログ出力は以下のような形式になります。
1
|
[user-service,803B448A0489F84084905D3093480352,3425F23BB2432450] INFO c.e.demo.controller.UserController - Getting user with id: 1
|
Logback設定のカスタマイズ#
より詳細なログフォーマットが必要な場合は、logback-spring.xmlを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!-- src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="appName" source="spring.application.name"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [${appName},%X{traceId:-},%X{spanId:-}] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
|
ログとトレースの関連付けによるメリット#
ログにTrace IDを付与することで、以下のメリットが得られます。
- 問題の迅速な特定: エラーログからTrace IDを取得し、Zipkinで関連するすべてのサービスのトレースを確認できる
- ログ集約ツールとの連携: ELKスタックやDatadogなどのログ集約ツールでTrace IDによる検索が可能
- 監査証跡: 特定のリクエストに関するすべてのログを追跡可能
サービス間呼び出しのトレース伝播#
RestClientを使用したトレース伝播#
マイクロサービス間でトレース情報を伝播するには、Spring Bootが提供するRestClient.Builderを使用する必要があります。自動構成されたビルダーを使用することで、トレース情報が自動的にHTTPヘッダーに追加されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder) {
// 自動構成されたBuilderを使用することが重要
return builder
.baseUrl("http://localhost:8081")
.build();
}
}
|
以下は、別サービスを呼び出すサービスクラスの実装例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
private final RestClient restClient;
public OrderService(RestClient restClient) {
this.restClient = restClient;
}
public Order getOrderWithUserDetails(Long orderId) {
logger.info("Fetching order with id: {}", orderId);
// 外部サービス(User Service)を呼び出し
// Trace IDが自動的にHTTPヘッダーに追加される
User user = restClient.get()
.uri("/api/users/{id}", 1L)
.retrieve()
.body(User.class);
logger.info("Retrieved user: {}", user.name());
return new Order(orderId, user, "COMPLETED");
}
}
|
1
2
3
4
5
6
7
|
package com.example.demo.service;
public record Order(Long id, User user, String status) {
}
public record User(Long id, String name, String email) {
}
|
トレース伝播の仕組み#
トレース情報は、W3C Trace Context標準に基づいてHTTPヘッダーで伝播されます。
sequenceDiagram
participant C as Client
participant A as Order Service
participant B as User Service
C->>A: GET /orders/1
Note over A: Trace ID: abc123<br/>Span ID: span1 (新規生成)
A->>B: GET /users/1<br/>traceparent: 00-abc123-span1-01
Note over B: Trace ID: abc123 (継続)<br/>Span ID: span2 (新規生成)
B-->>A: User Response
A-->>C: Order Response
| ヘッダー |
説明 |
例 |
traceparent |
W3C Trace Context標準のヘッダー |
00-abc123...-span1...-01 |
b3 |
B3形式のヘッダー(レガシー対応) |
abc123...-span1...-1 |
カスタムSpanの作成#
特定の処理を個別のSpanとして追跡したい場合は、ObservationRegistryを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
package com.example.demo.service;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
private final ObservationRegistry observationRegistry;
public PaymentService(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
public PaymentResult processPayment(Long orderId, Double amount) {
// カスタムObservation(Span)の作成
return Observation.createNotStarted("payment.process", observationRegistry)
.lowCardinalityKeyValue("orderId", orderId.toString())
.lowCardinalityKeyValue("paymentMethod", "CREDIT_CARD")
.observe(() -> {
logger.info("Processing payment for order: {}", orderId);
// 決済処理
validatePayment(amount);
executePayment(orderId, amount);
return new PaymentResult(orderId, "SUCCESS", amount);
});
}
private void validatePayment(Double amount) {
Observation.createNotStarted("payment.validate", observationRegistry)
.observe(() -> {
logger.info("Validating payment amount: {}", amount);
// バリデーション処理
});
}
private void executePayment(Long orderId, Double amount) {
Observation.createNotStarted("payment.execute", observationRegistry)
.observe(() -> {
logger.info("Executing payment for order: {}", orderId);
// 決済実行処理
try {
Thread.sleep(200); // 処理時間をシミュレート
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
|
1
2
3
4
|
package com.example.demo.service;
public record PaymentResult(Long orderId, String status, Double amount) {
}
|
この実装により、Zipkinで以下のような階層構造のSpanが表示されます。
gantt
title Payment Processing Trace
dateFormat X
axisFormat %L ms
section Spans
payment.process :0, 300
payment.validate :10, 50
payment.execute :60, 260本番環境での運用設定#
サンプリング戦略#
本番環境では、すべてのリクエストをトレースするとストレージコストとパフォーマンスに影響が出ます。適切なサンプリング戦略を設定しましょう。
1
2
3
4
5
|
# application-prod.yml
management:
tracing:
sampling:
probability: 0.1 # 10%のリクエストのみトレース
|
エラーが発生したリクエストを必ずトレースしたい場合は、カスタムサンプラーを実装できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package com.example.demo.config;
import brave.sampler.Sampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class TracingConfig {
@Bean
@Profile("prod")
public Sampler productionSampler() {
// 本番環境では10%のリクエストをサンプリング
return Sampler.create(0.1f);
}
@Bean
@Profile("!prod")
public Sampler developmentSampler() {
// 開発環境ではすべてのリクエストをトレース
return Sampler.ALWAYS_SAMPLE;
}
}
|
機密情報のマスキング#
HTTPヘッダーやパラメータに含まれる機密情報がトレースに記録されないよう注意が必要です。
1
2
3
4
5
6
7
8
9
|
# application.yml
management:
tracing:
baggage:
remote-fields:
- x-request-id # 伝播するカスタムヘッダー
correlation:
fields:
- x-request-id # MDCに追加するフィールド
|
ヘルスチェックとの統合#
Actuatorのヘルスチェックエンドポイントで、Zipkin接続状態を確認できます。
1
2
3
4
5
6
7
8
9
|
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
health:
defaults:
enabled: true
|
トラブルシューティング#
よくある問題と解決策#
| 問題 |
原因 |
解決策 |
| トレースがZipkinに表示されない |
サンプリング率が0 |
management.tracing.sampling.probabilityを確認 |
| Span IDがログに表示されない |
MDC設定の不備 |
ログパターンに%X{traceId}を追加 |
| サービス間でTrace IDが伝播しない |
RestClientの設定不備 |
自動構成されたRestClient.Builderを使用 |
| Zipkin接続エラー |
エンドポイントURL誤り |
management.tracing.export.zipkin.endpointを確認 |
デバッグログの有効化#
トレーシングの問題を調査する際は、関連するデバッグログを有効化します。
1
2
3
4
5
6
|
# application.yml
logging:
level:
io.micrometer.tracing: DEBUG
brave: DEBUG
zipkin2: DEBUG
|
まとめ#
本記事では、Spring Boot 3.xでMicrometer TracingとZipkinを使用した分散トレーシングの実装方法を解説しました。
主要なポイントを整理すると以下のとおりです。
- 分散トレーシングの基本: Trace IDとSpan IDを使用してリクエストの流れを追跡する
- Micrometer Tracingの設定:
micrometer-tracing-bridge-braveとzipkin-reporter-braveを依存関係に追加する
- ログ相関: 自動的にTrace IDがログに付与され、ログとトレースを関連付けられる
- トレース伝播: 自動構成された
RestClient.Builderを使用することで、サービス間でトレース情報が自動伝播される
- カスタムSpan:
ObservationRegistryを使用して、独自の処理単位をトレースできる
分散トレーシングを導入することで、マイクロサービス環境での問題調査が大幅に効率化されます。本番環境では適切なサンプリング率を設定し、パフォーマンスとトレーサビリティのバランスを取ることが重要です。
参考リンク#