Wavefrontで学ぶ分散トレーシング Part-4

この文章は、Wavefrontで学ぶ分散トレーシング シリーズの第四回目です。

シリーズ

第一回 : 概要編
第二回 : Spring Bootで分散トレーシング
第三回 : REDメトリクスって何?
第四回 : サービスをつなげてみる**← いまここ**
第五回 : Pythonで分散トレーシング
第六回 : AMQPで分散トレーシング
第七回 ; サービスメッシュで分散トレーシング

始めに

第二回第三回で簡単なアプリで分散トレーシングをみました。

今回は2つのサービスをつなげてその時の動作をみたいと思います。 以下を目指します。

準備編

第二回と同じ手順でアプリを作ってください。 正しく構成されていれば、以下のURLで"Hello World"もしくは"Good Bye World"が表示されるはずです。

1curl localhost:8082/hello

なお、混乱をさけるため、このアプリは記事ではREDアプリと呼称します。 今回はこのREDアプリに加え、もうひとつアプリをつくり、くっつけてみます。

新しく作る方をHUBアプリと呼びます。

ソースコード

ここに公開しています。

https://github.com/mhoshi-vm/wf-demanabu-dis-tracing/tree/master/4

HUBアプリの準備

ここからはHUBアプリの作り方です。

(もうほぼテンプレですが) 準備ができたら、以下のURLにアクセスしてください。

start.spring.io

ログイン後、以下を実施します。

  • Add Dependencies を選択
  • Spring Webを検索して追加
  • Sleuth も同様に追加
  • Wavefront も同様に追加

最後にGenerateをクリックします。 zipファイルは現在のアプリと重複しない場所に展開してください。

お気に入りのエディターで以下のファイルを開いてください。

1mhoshino@mhoshino demo % vi src/main/java/com/example/demo/DemoApplication.java

これを以下の内容に差し替えてください。

 1package com.example.demo;
 2
 3import java.util.Map;
 4
 5import org.slf4j.Logger;
 6import org.slf4j.LoggerFactory;
 7import org.springframework.beans.factory.annotation.Autowired;
 8import org.springframework.beans.factory.annotation.Value;
 9import org.springframework.boot.SpringApplication;
10import org.springframework.boot.autoconfigure.SpringBootApplication;
11import org.springframework.context.annotation.Bean;
12import org.springframework.core.ParameterizedTypeReference;
13import org.springframework.http.HttpMethod;
14import org.springframework.web.bind.annotation.GetMapping;
15import org.springframework.web.bind.annotation.RequestHeader;
16import org.springframework.web.bind.annotation.RestController;
17import org.springframework.web.client.RestTemplate;
18
19@SpringBootApplication
20public class DemoApplication {
21
22	public static void main(String[] args) {
23		SpringApplication.run(DemoApplication.class, args);
24	}
25
26}
27
28@RestController
29class HubController{
30
31    private static final Logger LOGGER = LoggerFactory.getLogger(HubController.class);
32
33	@Value("${hub.urls}")
34	private String urls;
35
36    @Autowired
37    RestTemplate restTemplate;
38
39    @Bean
40    public RestTemplate getRestTemplate() {
41        return new RestTemplate();
42    }
43
44    @GetMapping(value="/hub")
45    public String MultiRest(@RequestHeader Map<String, String> header) {
46		printAllHeaders(header);
47
48		for( String url : urls.split(",") )
49		{
50			LOGGER.info(String.format("URLS = %s", url));
51			restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();
52		}
53        return "REST Complete";
54    }
55
56	private void printAllHeaders(Map<String, String> headers) {
57		headers.forEach((key, value) -> {
58			LOGGER.info(String.format("Header '%s' = %s", key, value));
59		});
60	}
61}

さらに以下のファイルを開きます。

1mhoshino@mhoshino demo % vi src/main/resources/application.properties

そして以下の内容を追記します。

1management.endpoints.web.exposure.include=wavefront
2server.port=8083
3wavefront.application.name=demo3
4wavefront.application.service=Hub
5
6hub.urls=http://localhost:8082/hello

コード編集は以上です。

サービスをくっつけてみる

さて、アプリを起動します。以下のコマンドをREDアプリとHUBアプリ両方で実行します。(同じコマンドなのでディレクトリーを混乱しないように)

1./mvnw sprint-boot:run

そしてしばらく、以下のURLに対してコマンドを何回か実行します。

1curl localhost:8083/hub

しばらくしたら、WavefrontのURLにログインしてみましょう。

http://localhost:8083/actuator/wavefront

そして、Wavefrontにログイン後、[Applications] > [Application Map(Beta)]を選択します。

うまくいってれば、以下のように図がつながっているはずです。

[Applications] > [Traces]を開くと、Traceログがみれるはずです。

何が起きている?

さて、いつもどおり後回しの解説です。 今回作ったHUBアプリは非常に単純に、前回作ったREDアプリへREST APIを送るシンプルなアプリです。 コードでいうと以下の箇所

 1    @GetMapping(value="/hub")
 2    public String MultiRest(@RequestHeader Map<String, String> header) {
 3		printAllHeaders(header);
 4
 5		for( String url : urls.split(",") )
 6		{
 7			LOGGER.info(String.format("URLS = %s", url));
 8			restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<String>() {}).getBody();
 9		}
10        return "REST Complete";
11    }

どのURLへ送るかはapplication.propertieshub.urlsパラメーターで指定します。

1hub.urls=http://localhost:8082/hello

もうすこし詳細な挙動をみてみましょう。

HUBアプリのログを見てみます。 以下のようなログになっているかと思います。

12020-07-20 09:18:40.787  INFO [,5f14e2e08374d381d7534b5d89939527,d7534b5d89939527,true] 34562 --- [nio-8083-exec-2]
22020-07-20 09:18:40.787  INFO [,5f14e2e08374d381d7534b5d89939527,d7534b5d89939527,true] 34562 --- [nio-8083-exec-2]
32020-07-20 09:18:40.787  INFO [,5f14e2e08374d381d7534b5d89939527,d7534b5d89939527,true] 34562 --- [nio-8083-exec-2]
42020-07-20 09:18:40.787  INFO [,5f14e2e08374d381d7534b5d89939527,d7534b5d89939527,true] 34562 --- [nio-8083-exec-2]

注目するのが[,5f14e2e08374d381d7534b5d89939527,d7534b5d89939527,true] の箇所で、第一回目で説明したよう、5f14e2e08374d381d7534b5d89939527がTrace ID でd7534b5d89939527がスパンIDです。

REDアプリのログをみてみます。 以下のようなログになっているかと思います。

1[hellorest,5f14e2e08374d381d7534b5d89939527,7ce53e353e48c2aa,true] 34007 --- [nio-8082-exec-2]
2[hellorest,5f14e2e08374d381d7534b5d89939527,7ce53e353e48c2aa,true] 34007 --- [nio-8082-exec-2]
3[hellorest,5f14e2e08374d381d7534b5d89939527,7ce53e353e48c2aa,true] 34007 --- [nio-8082-exec-2]
4[hellorest,5f14e2e08374d381d7534b5d89939527,7ce53e353e48c2aa,true] 34007 --- [nio-8082-exec-2]

ここで着目すると

  • 5f14e2e08374d381d7534b5d89939527のTrace IDが一致している
  • Span ID がREDアプリとHUBアプリの間で異なる

さらに、このログを右の箇所注目するとこのようになっていると思います

12020-07-20 09:18:40.789  INFO Header 'accept' = text/plain, application/json, application/*+json, */*
22020-07-20 09:18:40.789  INFO Header 'x-b3-traceid' = 5f14e2e08374d381d7534b5d89939527
32020-07-20 09:18:40.789  INFO Header 'x-b3-spanid' = 067b8ed01abdb759
42020-07-20 09:18:40.789  INFO Header 'x-b3-parentspanid' = d7534b5d89939527
52020-07-20 09:18:40.789  INFO Header 'x-b3-sampled' = 1
62020-07-20 09:18:40.789  INFO Header 'user-agent' = Java/14.0.1
72020-07-20 09:18:40.789  INFO Header 'host' = localhost:8082
82020-07-20 09:18:40.789  INFO Header 'connection' = keep-alive

これは、HUBアプリからやってきた、HTTPのヘッダーを出力しています。 ここからわかることが

  • x-b3-trace-id にTrace IDが含まれている
  • x-b3-parentspanid にHUBアプリのSpan IDが含まれている

x-b3?

さて、x-b3 ってなんぞや?ですが、これはb3-propagationとよばれるZipkinで定義されているTrace情報を共有するためのHTTPヘッダーです。

詳しくは、ここを参照ください。

Spring Bootではこのx-b3のアップストームからの展開とダウンストリームへの挿入をSluethがほぼコードからは透過的に実施します。 以下の③の箇所で定義したときのものです。

まとめ

  • サービス同士をくっつけるには、Spring Bootでは単純にREST APIを実行すればよい
  • サービス同士はTrace ID、Span IDをHTTPの"x-b3-*“ヘッダーでやり取りしている
  • “x-b3-*“ヘッダーは、Spring BootではSluethが勝手にやってくれるのでうれしい

さて、次回はこのx-b3-ヘッダーの取り扱いがどれだけややこしいか、pythonをつかって説明したいと思います。 次回は「Pythonで分散トレーシング」です。