マイクロサービスアーキテクチャの普及に伴い、1つのリクエストが複数のサービスを横断して処理されるのが当たり前になりました。障害が発生したとき、「どのサービスで」「どの処理が」「どれだけ時間がかかったか」を追跡するのは、ログだけでは困難です。分散トレーシングは、サービス間を跨ぐリクエストの流れを可視化し、パフォーマンスのボトルネックや障害の原因を特定するための技術です。
OpenTelemetryは、CNCF(Cloud Native Computing Foundation)が推進するオブザーバビリティの標準フレームワークです。トレース、メトリクス、ログの3本柱を統一的なAPIで扱える。本記事では、OpenTelemetryを使った分散トレーシングの実装方法と、運用で得た知見を共有する。
分散トレーシングの世界では、3つの基本概念を正確に理解する必要があります。
1つのTraceは複数のSpanで構成され、Spanはツリー構造を持つ。ルートSpanが最上位に位置し、子Spanがネストされていく。
まず、Spring Bootプロジェクトに必要な依存関係を追加する。
<!-- pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.35.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>
OpenTelemetry SDKの初期化は、アプリケーション起動時に行う。
@Configuration
public class OtelConfig {
@Bean
public OpenTelemetry openTelemetry() {
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
ResourceAttributes.SERVICE_NAME, "order-service",
ResourceAttributes.SERVICE_VERSION, "1.2.0",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
)));
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("http://otel-collector:4317")
.setTimeout(Duration.ofSeconds(10))
.build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(
BatchSpanProcessor.builder(spanExporter)
.setMaxQueueSize(2048)
.setScheduleDelay(Duration.ofSeconds(5))
.build()
)
.setResource(resource)
.setSampler(Sampler.traceIdRatioBased(0.1))
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(
ContextPropagators.create(
W3CTraceContextPropagator.getInstance()
)
)
.build();
}
}
ここで注目すべきはSampler.traceIdRatioBased(0.1)の設定です。本番環境ですべてのリクエストをトレースすると、データ量が爆発的に増加する。サンプリングレートを10%に設定することで、運用コストを抑えつつ十分な可視性を確保できます。
自動計装だけでは不十分な場合、手動でSpanを作成する。
@Service
public class OrderService {
private final Tracer tracer;
public OrderService(OpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer(
"order-service", "1.2.0"
);
}
public Order createOrder(CreateOrderRequest request) {
Span span = tracer.spanBuilder("createOrder")
.setSpanKind(SpanKind.INTERNAL)
.setAttribute("order.item_count",
request.getItems().size())
.setAttribute("order.customer_id",
request.getCustomerId())
.startSpan();
try (Scope scope = span.makeCurrent()) {
Order order = validateAndCreate(request);
span.setAttribute("order.id", order.getId());
span.setStatus(StatusCode.OK);
return order;
} catch (Exception e) {
span.setStatus(StatusCode.ERROR,
e.getMessage());
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}
Scopeを使ってSpanをカレントコンテキストに設定することが重要です。これにより、このメソッド内で呼び出される後続の処理が、自動的にこのSpanの子として関連付けられます。
OpenTelemetry Collectorは、テレメトリデータの受信、処理、エクスポートを担うコンポーネントです。アプリケーションから直接バックエンドに送信するのではなく、Collectorを経由させることで柔軟な運用が可能になります。
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 10s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 512
spike_limit_mib: 128
tail_sampling:
policies:
- name: error-policy
type: status_code
status_code:
status_codes: [ERROR]
- name: slow-policy
type: latency
latency:
threshold_ms: 3000
exporters:
otlp:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp]
特にtail_samplingプロセッサは運用上極めて有用です。エラーが発生したトレースや、レイテンシが閾値を超えたトレースを優先的に保存することで、問題の分析に必要なデータを効率的に収集できます。
Spanに付与する属性は、後のクエリと分析を左右する。筆者のチームでは以下の指針を設けています。
分散トレーシングの運用で最も見落とされがちなのがコストです。サービス数とトラフィック量に比例してデータ量が増大するため、適切なサンプリング戦略とデータ保持期間の設計が不可欠です。Head-basedサンプリングとTail-basedサンプリングを組み合わせ、重要なトレースを確実に残しつつ全体のデータ量を制御するのがベストプラクティスです。
OpenTelemetryは分散システムの可観測性を実現するための標準として、急速に普及が進んでいる。導入のハードルは年々下がっているが、運用面での設計判断は依然として重要です。サンプリング戦略、属性設計、Collectorの構成を適切に行うことで、コストを抑えつつ効果的な分散トレーシングを実現できます。まずは1つのサービスから始めて、段階的に計装範囲を広げていくことをお勧めする。