Zipkin は、Twitter によって開発された、リアルタイムデータをトレースするためのオープンソース分散トレーシングシステムです。複数の異種システムから収集されたリアルタイム監視データを集約します。このガイドでは、Brave ライブラリを使用して Java アプリケーションをインストルメント化し、Zipkin 互換エンドポイントを介して ARMS トレーシング分析にトレースデータをレポートする方法を説明します。
インストルメンテーション方法の選択
フレームワークに適した方法を選択してください。Spring Sleuth は最小限の設定で済むため、Spring Boot プロジェクトに推奨されます。
| 方法 | 最適な用途 | 複雑さ |
|---|---|---|
| Spring Sleuth | Spring Cloud マイクロサービス (推奨) | 低 |
| Spring 4.0 MVC または Spring Boot | 最新のアノテーションベース Spring プロジェクト | 中 |
| Spring 2.5 または 3.0 MVC | レガシー XML ベース Spring プロジェクト | 中 |
| Dubbo | Dubbo RPC アプリケーション | 中 |
| 手動インストルメンテーション | スパンとタグの完全な制御 | 高 |
データフロー
アプリケーションは Brave ライブラリを使用してスパンを作成し、Zipkin 互換エンドポイントを介して ARMS にレポートします。
前提条件
Zipkinエンドポイントの取得
トレースデータをレポートするには、トレーシング分析コンソールから Zipkin 互換エンドポイントが必要です。
-
トレーシング分析コンソール にログインします。
-
左側のナビゲーションペインで、[クラスター設定] をクリックします。次に、[アクセスポイント情報] タブをクリックします。
-
トップナビゲーションバーで、リージョンを選択します。[クラスター情報] セクションで、[トークンの表示] をオンにします。
-
[クライアント] セクションで Zipkin をクリックします。
[関連情報] 列からエンドポイントをコピーします。
アプリケーションが Alibaba Cloud の本番環境で実行される場合は、Alibaba Cloud VPC アクセスポイントを使用してください。それ以外の場合は、パブリックエンドポイントを使用してください。
v1 を使用する特別な理由がない限り、v2 エンドポイントを使用してください。
サポートされているフレームワーク
Brave は以下の Java フレームワークのインストルメンテーションを提供しています。完全なリストについては、「brave-instrumentation」をご参照ください。
Apache HttpClient、Dubbo、gRPC、JAX-RS 2.X、Jersey Server、JMS、Kafka、MySQL、Netty、OkHttp、Servlet、Spark、Spring Boot、Spring MVC
デモプロジェクト
各インストルメンテーション方法に対応する動作可能なデモが用意されています。デモプロジェクトをダウンロードし、対応するディレクトリの README に従ってください。
| 方法 | デモディレクトリ |
|---|---|
| 手動インストルメンテーション | manualDemo |
| Spring 2.5 または 3.0 MVC | springMvcDemo\webmvc3|webmvc25 |
| Spring 4.0 MVC または Spring Boot | springMvcDemo\webmvc4-boot|webmvc4 |
| Dubbo | dubboDemo |
| Spring Sleuth | sleuthDemo |
Spring Sleuthによるインストルメンテーション
Spring Cloud Sleuth は、最小限の設定で Spring Boot アプリケーションに自動分散トレーシングを提供します。Zipkin とのネイティブ統合により、トレースレポートが可能です。
ステップ1:依存関係の追加
pom.xml に以下の依存関係を追加します。
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
ステップ2:application.ymlの設定
Zipkin ベース URL とサンプリングレートを設定します。<endpoint_short> を、トレース分析コンソールの [アクセスポイント情報] タブから取得した、api/v2/spans で終わるパブリックエンドポイントに置き換えます。
spring:
application:
# Zipkin のサービス名として使用されます
name: sleuthDemo
zipkin:
# Zipkin に送信する場合はコメントを外し、192.168.99.100 を Zipkin の IP アドレスに置き換えます
baseUrl: <endpoint_short>
sleuth:
sampler:
probability: 1.0
sample:
zipkin:
# enabled: true に設定すると、トレースが Zipkin に送信されます。false の場合、トレースはコンソールに記録されます。
enabled: true
ステップ3:セットアップの検証
HTTP リクエストを送信して、トレースレポートをトリガーします。
http://localhost:3380/traced
リクエストを送信した後、トレーシング分析コンソールで受信トレースデータを確認します。追加のリクエストパスについては、デモプロジェクトの com.alibaba.apm.SampleController 配下のメソッドをご参照ください。
Spring 4.0 MVCまたはSpring Bootによるインストルメンテーション
Spring 4.0 MVC または Spring Boot アプリケーション向けに、Java アノテーションを使用してトレーシングを設定します。
動作可能な例については、デモプロジェクトの springMvcDemo\webmvc4-boot|webmvc4 ディレクトリをご参照ください。
ステップ1:トレーシングとフィルターBeanの設定
以下の設定クラスを追加します。<endpoint> を、前提条件で取得したエンドポイントに置き換えます。
/** Zipkin へのスパン送信方法の設定 */
@Bean Sender sender() {
return OkHttpSender.create("<endpoint>");
}
/** Zipkin 用のメッセージにスパンをバッファリングする方法の設定 */
@Bean AsyncReporter<Span> spanReporter() {
return AsyncReporter.create(sender());
}
/** UI に表示される名前などのトレーシングの側面を制御します */
@Bean Tracing tracing(@Value("${spring.application.name}") String serviceName) {
return Tracing.newBuilder()
.localServiceName(serviceName)
.propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "user-name"))
.currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
.addScopeDecorator(MDCScopeDecorator.create()) // トレース ID をログに記録します
.build()
)
.spanReporter(spanReporter()).build();
}
/** スパンの命名とタグ付けの方法を決定します。デフォルトでは、HTTP メソッドと同じ名前が付けられます。*/
@Bean HttpTracing httpTracing(Tracing tracing) {
return HttpTracing.create(tracing);
}
/** HTTP リクエストのクライアントスパンを作成します */
// 先行する設定で RestTemplate Bean が提供されるため、BPP を使用しています
@Bean BeanPostProcessor connectionFactoryDecorator(final BeanFactory beanFactory) {
return new BeanPostProcessor() {
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override public Object postProcessAfterInitialization(Object bean, String beanName) {
if (!(bean instanceof RestTemplate)) return bean;
RestTemplate restTemplate = (RestTemplate) bean;
List<ClientHttpRequestInterceptor> interceptors =
new ArrayList<>(restTemplate.getInterceptors());
interceptors.add(0, getTracingInterceptor());
restTemplate.setInterceptors(interceptors);
return bean;
}
// BPP が何かをプロキシする必要がないように、遅延ルックアップを行います。
ClientHttpRequestInterceptor getTracingInterceptor() {
return TracingClientHttpRequestInterceptor.create(beanFactory.getBean(HttpTracing.class));
}
};
}
/** HTTP リクエストのサーバースパンを作成します */
@Bean Filter tracingFilter(HttpTracing httpTracing) {
return TracingFilter.create(httpTracing);
}
@Autowired SpanCustomizingAsyncHandlerInterceptor webMvcTracingCustomizer;
/** アプリケーション定義の Web タグでサーバースパンを装飾します */
@Override public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webMvcTracingCustomizer);
}
ステップ2:自動設定の有効化
src/main/resources/META-INF/spring.factories に以下の行を追加します。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
brave.webmvc.TracingConfiguration
Spring 2.5または3.0 MVCによるインストルメンテーション
Spring 2.5 または 3.0 MVC アプリケーション向けに、XML Bean 定義を使用してトレーシングを設定します。
動作可能な例については、デモプロジェクトの springMvcDemo\webmvc3|webmvc25 ディレクトリをご参照ください。
ステップ1:トレーシングオブジェクトの設定
applicationContext.xml に以下の Bean 定義を追加します。<endpoint> を、前提条件で取得したエンドポイントに置き換えます。
<bean class="zipkin2.reporter.beans.OkHttpSenderFactoryBean">
<property name="endpoint" value="<endpoint>"/>
</bean>
<!-- Spring 設定からサービス名を読み取ることができます -->
<context:property-placeholder/>
<bean class="brave.spring.beans.TracingFactoryBean">
<property name="localServiceName" value="brave-webmvc3-example"/>
<property name="spanReporter">
<bean class="zipkin2.reporter.beans.AsyncReporterFactoryBean">
<property name="encoder" value="JSON_V2"/>
<property name="sender" ref="sender"/>
<!-- クローズ時に実行中のスパンを最大 0.5 秒待機します -->
<property name="closeTimeout" value="500"/>
</bean>
</property>
<property name="propagationFactory">
<bean class="brave.propagation.ExtraFieldPropagation" factory-method="newFactory">
<constructor-arg index="0">
<util:constant static-field="brave.propagation.B3Propagation.FACTORY"/>
</constructor-arg>
<constructor-arg index="1">
<list>
<value>user-name</value>
</list>
</constructor-arg>
</bean>
</property>
<property name="currentTraceContext">
<bean class="brave.spring.beans.CurrentTraceContextFactoryBean">
<property name="scopeDecorators">
<bean class="brave.context.log4j12.MDCScopeDecorator" factory-method="create"/>
</property>
</bean>
</property>
</bean>
<bean class="brave.spring.beans.HttpTracingFactoryBean">
<property name="tracing" ref="tracing"/>
</bean>
ステップ2:インターセプターの追加
トレーシング HTTP クライアントビルダーとハンドラーインターセプターを登録します。
<bean class="brave.httpclient.TracingHttpClientBuilder"
factory-method="create">
<constructor-arg type="brave.http.HttpTracing" ref="httpTracing"/>
</bean>
<bean factory-bean="httpClientBuilder" factory-method="build"/>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<bean class="brave.spring.webmvc.SpanCustomizingHandlerInterceptor"/>
</list>
</property>
</bean>
<!-- コントローラーをロードします -->
<context:component-scan base-package="brave.webmvc"/>
ステップ3:サーブレットフィルターの追加
web.xml にトレーシングフィルターを追加して、すべての受信リクエストをインターセプトします。
<!-- 標準トレーシングフィルターにデリゲートを追加し、すべてのパスにマッピングします -->
<filter>
<filter-name>tracingFilter</filter-name>
<filter-class>brave.spring.webmvc.DelegatingTracingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>tracingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Dubboによるインストルメンテーション
Brave Dubbo インストルメンテーションライブラリは、Dubbo RPC アプリケーションに分散トレーシングを追加します。
動作可能な例については、デモプロジェクトの dubboDemo ディレクトリをご参照ください。
ステップ1:依存関係の追加
pom.xml に以下の依存関係を追加します。
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-dubbo-rpc</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-beans</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-context-slf4j</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>2.7.9</version>
</dependency>
ステップ2:トレーシングオブジェクトの設定
Spring XML 設定に以下の Bean 定義を追加します。<endpoint> を、前提条件で取得したエンドポイントに置き換えます。
<bean class="zipkin2.reporter.beans.OkHttpSenderFactoryBean">
<property name="endpoint" value="<endpoint>"/>
</bean>
<bean class="brave.spring.beans.TracingFactoryBean">
<property name="localServiceName" value="double-provider"/>
<property name="spanReporter">
<bean class="zipkin2.reporter.beans.AsyncReporterFactoryBean">
<property name="sender" ref="sender"/>
<!-- クローズ時に実行中のスパンを最大 0.5 秒待機します -->
<property name="closeTimeout" value="500"/>
</bean>
</property>
<property name="currentTraceContext">
<bean class="brave.spring.beans.CurrentTraceContextFactoryBean">
<property name="scopeDecorators">
<bean class="brave.context.slf4j.MDCScopeDecorator" factory-method="create"/>
</property>
</bean>
</property>
</bean>
ステップ3:トレーシングフィルターの追加
プロバイダーとコンシューマーの両方の設定にトレーシングフィルターを適用します。
// サーバー設定
<dubbo:provider filter="tracing" />
// クライアント設定
<dubbo:consumer filter="tracing" />
Javaアプリケーションの手動インストルメンテーション
手動インストルメンテーションにより、トレースする操作と各スパンに付加するメタデータをきめ細かく制御できます。自動インストルメンテーションがフレームワークをカバーしていない場合や、カスタムスパン粒度が必要な場合に、この方法を使用します。
動作可能な例については、デモプロジェクトの manualDemo ディレクトリをご参照ください。
ステップ1:依存関係の追加
pom.xml に以下の依存関係を追加します。
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>2.7.9</version>
</dependency>
ステップ2:トレーサーの作成
Zipkin エンドポイントを使用してトレーサーを初期化します。<endpoint> を、前提条件で取得したエンドポイントに置き換えます。
private static final String zipkinEndPoint = "<endpoint>";
...
// スパンデータを送信するための Sender を作成します
OkHttpSender sender = OkHttpSender.newBuilder().endpoint(zipkinEndPoint).build();
// 非同期レポーターを作成します
Reporter<Span> reporter = AsyncReporter.builder(sender).build();
tracing = Tracing.newBuilder().localServiceName(localServiceName).spanReporter(reporter).build();
ステップ3:スパンの作成
ルートスパンを作成し、子スパンをネストしてサブオペレーションを表現します。
private void firstBiz() {
// ルートスパンを作成し、try-with-resources でスコープを管理します
try (Scope scope = tracing.tracer().startScopedSpan("parentSpan")) {
Span span = tracing.tracer().currentSpan();
span.tag("key", "firstBiz");
secondBiz();
} // スコープがクローズされると、スパンも自動的に終了します
}
private void secondBiz() {
// 親のコンテキストを引き継いで子スパンを作成します
try (Scope scope = tracing.tracer().startScopedSpanWithParent("childSpan", tracing.tracer().currentSpan().context())) {
Span childSpan = tracing.tracer().currentSpan();
childSpan.tag("key", "secondBiz");
System.out.println("end tracing,id:" + childSpan.context().traceIdString());
} // スコープがクローズされると、スパンも自動的に終了します
}
ステップ4:カスタムタグの追加 (オプション)
トラブルシューティングを容易にするために、スパンにメタデータを付加します。例えば、HTTP ステータスコードを記録します。
tracer.activeSpan().tag("http.status_code", "500");
ステップ5:サービス間でのトレースコンテキストの伝播
分散システムでは、トレースコンテキスト (TraceId、ParentSpanId、SpanId、Sampled) を各 RPC リクエストとともに伝播させる必要があります。クライアント側で inject を呼び出してコンテキストをリクエストヘッダーに埋め込み、サーバー側で extract を呼び出して読み取ります。
クライアント側 -- コンテキストのインジェクト
// クライアントリクエストを表す新しいスパンを開始します
oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
--snip--
// リクエストにトレースコンテキストを追加して、インバンドで伝播できるようにします
tracing.propagation().injector(Request::addHeader)
.inject(oneWaySend.context(), request);
// リクエストを非同期で実行し、レスポンスを完全に破棄します
request.execute();
// クライアント側を開始し、finish ではなく flush を実行します
oneWaySend.start().flush();
サーバー側 -- コンテキストのエクストラクト
// 受信リクエストからコンテキストを取り出します
extractor = tracing.propagation().extractor(Request::getHeader);
// そのコンテキストを、名前を付けてタグを追加できるスパンに変換します
oneWayReceive = nextSpan(tracer, extractor.extract(request))
.name("process-request")
.kind(SERVER)
... タグなどを追加
// サーバー側を開始し、finish ではなく flush を実行します
oneWayReceive.start().flush();
// このスパンは完了しているため、これ以上変更しないでください。ただし、
// 後続作業を表す子スパンを作成できます。
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();
よくある質問
Q:デモを実行してもトレースデータが表示されません。
A:通常、これはエンドポイント設定が正しくないことを示しています。問題を診断するには、次の手順を実行してください。
-
エンドポイントが Tracing Analysis コンソールの[アクセスポイント情報]タブに表示されているものと一致することを確認してください。
-
エンドポイントをコピーする際に、正しいクライアントタイプ (Jaeger ではなく Zipkin) を選択したことを確認してください。
-
VPC アクセスポイントを使用する場合は、アプリケーションが同じ VPC 内で実行されていることを確認してください。
-
さらにデバッグするには、
zipkin2.reporter.okhttp3.HttpCallのparseResponseメソッドにブレークポイントを設定し、HTTP レスポンスを検査してください。403エラーは、ユーザー名の設定が無効であることを示しています。エンドポイント設定を確認してください。