分散トレーシングの基礎と実装
マイクロサービス環境では、単一リクエストが複数サービスを経由するため、エンドツーエンドの可視化が不可欠です。Spring Cloud Sleuthはメンテナンスモードとなり、現在はMicrometer Tracingが標準ソリューションとして採用されています。
トレーシングの仕組み
トレースIDとスパンIDでリクエスト経路を特定します。例として、order-service → payment-service → inventory-service の呼び出しチェーンを想定:
- トレースID: 全経路を一意に識別(例:
7b3e9a1c4f2d) - スパンID: 個々のサービス呼び出し単位(例:
payment-serviceが生成する4a8f2b) - 親スパンID: 呼び出し元を参照(
payment-serviceのスパンはorder-serviceのスパンIDを親として保持)
Zipkinとの連携構成
ZipkinサーバーをDockerで起動する例:
docker run -d --name zipkin -p 9412:9411 openzipkin/zipkin
実装コードの再構築
親モジュールの依存関係定義(バージョン管理):
<properties>
<micrometer.version>1.12.0</micrometer.version>
<brave.version>5.17.0</brave.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
サービスモジュールの実装例:
@RestController
public class OrderTraceController {
@GetMapping("/api/v1/order/{id}")
public TraceResponse traceOrder(@PathVariable String id) {
return new TraceResponse(
"Order processed: " + id,
IdUtil.fastSimpleUUID()
);
}
record TraceResponse(String message, String traceId) {}
}
構成設定の最適化
application.ymlのトレース設定:
management:
tracing:
sampling:
probability: 0.8 # デフォルト0.1より高頻度で収集
zipkin:
tracing:
endpoint: http://localhost:9412/api/v2/spans
Spring Cloud Gatewayの高度な活用
Zuulに代わる次世代APIゲートウェイで、ルーティング・フィルタリングの中心的役割を担います。
基本構成要素
- Route: ID/URI/述語/フィルタで構成される基本単位
- Predicate: リクエスト条件(例: パス・ヘッダ・時刻)
- Filter: リクエスト/レスポンス処理(前処理/後処理)
動的サービスディスカバリの実装
固定URIではなくサービス名によるルーティング:
spring:
cloud:
gateway:
routes:
- id: order_route
uri: lb://order-service
predicates:
- Path=/api/v1/order/**
filters:
- AddRequestHeader=X-Correlation-ID, {traceId}
カスタム述語の実装
会員ランクによるアクセス制御の例:
@Component
public class MemberLevelPredicateFactory extends AbstractRoutePredicateFactory<MemberLevelPredicateFactory.Config> {
public MemberLevelPredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String level = exchange.getRequest()
.getQueryParams()
.getFirst("membership");
return level != null && level.equals(config.requiredLevel);
};
}
@Validated
public static class Config {
@NotEmpty
private String requiredLevel;
// getter/setter
}
}
グローバルフィルタの実装
リクエスト処理時間計測の例:
@Component
public class TimingFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "start_time";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long duration = System.currentTimeMillis() - exchange.getAttribute(START_TIME);
log.info("Request {} completed in {}ms",
exchange.getRequest().getURI(), duration);
}));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
ヘッダ操作フィルタの応用
セキュリティヘッダの動的設定:
filters:
- SetRequestHeader=X-Security-Context, {#request.remoteAddress}
- RemoveResponseHeader=Server
- AddResponseHeader=X-Content-Type-Options, nosniff