マイクロサービス化されたシステム内の処理を分散トレーシングするツール「Jaeger」

# Jaegerとは

[Jaeger](https://github.com/jaegertracing/jaeger)はアプリケーションの内部の処理フローの情報をモニタリングするOSSのツールです。
マイクロサービス化されたサービスに対してユーザからのリクエストが来た際、その処理時の内部での各サービスとの通信処理時の経過時間等を一連のフローとしてモニタリングができます。
[OpenTracing](http://opentracing.io/)というPJでトレース処理に関する標準仕様が定められています。
OpenTracingの実装の一つがJaegerです。その他にはZipkin等のツールが該当します。
今回はこのJaegerという分散トレーシングツールを試しに動かしてみた内容を紹介します。


# Jaeger導入済みのDockerコンテナを稼働

Jaegerが導入済みのDockerコンテナが公式に公開されているので活用します。

```shell-session
$ docker run -d --name=jaeger -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:latest
```

All in oneのdocker imageなので、この状態で、以下にアクセスするとJaeger UIが起動しています。

http://ホストIP:16686

![jaeger1.png](https://qiita-image-store.s3.amazonaws.com/0/45851/2cbe66b6-721c-854e-51b1-bb6e3b0166f0.png)


# Go言語でJaegerにTrace情報を連携

Trace情報をプログラム側から連携します。今回はGoのプログラムで試してみました。

## ライブラリのインストール

```shell-session
$ go get github.com/uber/jaeger-client-go
$ go get github.com/opentracing/opentracing-go
$ go get github.com/pkg/errors
```

jaeger-client-goのインストール時にエラーが出る場合、
https://github.com/jaegertracing/jaeger-client-go/issues/255
こちらの記事を参考にthriftのバージョンを0.11.0未満(0.10.0)に変更してインストールし直します。

```shell-session
$ cd src/github.com/apache/thrift
$ git checkout 0.10.0
$ go get github.com/uber/jaeger-client-go
```
これで実装する準備が完了。

## Goのプログラム作成

以下の内容で実装します。

```golang:jaeger_sample.go
package main

import (
	"fmt"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/config"
	"time"
)

func main() {
	cfg := config.Configuration{
		Sampler: &config.SamplerConfig{
			Type:  "const",
			Param: 1,
		},
		Reporter: &config.ReporterConfig{
			LogSpans:            true,
			BufferFlushInterval: 1 * time.Second,
			LocalAgentHostPort:  "127.0.0.1:5775", //先程作成したDockerコンテナの5775ポートをレポート先ホストとして指定して設定(UDP通信)
		},
	}
	tracer, closer, err := cfg.New(
		"trace_sample",
		config.Logger(jaeger.StdLogger),
	)
	if err != nil {
		fmt.Println(err)
	}
	opentracing.SetGlobalTracer(tracer) //Tracerを作成
	defer closer.Close()

	someFunction()
}

func someFunction() {
	parent := opentracing.GlobalTracer().StartSpan("hello")  // helloという名前のspanを作成
	defer parent.Finish()
	child := opentracing.GlobalTracer().StartSpan(
		"world", opentracing.ChildOf(parent.Context()))  // worldという名前のspanを作成
	defer child.Finish()
}
```
この処理はtrace_sampleという名前のTracerを1つ作成し、そのTracerの中にhelloとworldの2種類のspanが流れるサンプルです。

## 実行

```shell-session
$ go run jaeger_sample.go
2018/04/17 00:47:16 Initializing logging reporter
2018/04/17 00:47:16 Reporting span f0c17aac0381b01:2616aa5ab70a3528:f0c17aac0381b01:1
2018/04/17 00:47:16 Reporting span f0c17aac0381b01:f0c17aac0381b01:0:1
```
先程のJaeger UIから確認してみます。

![jaeger2.png](https://qiita-image-store.s3.amazonaws.com/0/45851/eb4511fb-5782-8d37-4514-2143071b5f9c.png)

Serviceの欄に先程のコード内で指定したサービス名「trace_sample」が出てきます。
Find tracesを実行するとtrace_sampleのサービスで記録されたTrace情報が1件出てきます。
この情報の詳細をチェックすると以下の図のように、helloとworldの2つのspanが登録されていることがわかります。

![jaeger3.png](https://qiita-image-store.s3.amazonaws.com/0/45851/be5ba8de-20cb-a20b-335f-afa6beaf7d11.png)


# まとめ

今回は[こちら](http://opentracing.io/documentation/pages/quick-start)のOpenTracingのQuick Startに記載のサンプルコードを動かして挙動を確認した程度ではありますが、アプリケーションの中で複数のサービスと連携して処理フローが走るようなものに対して、トレース処理のコードを組み込めば、どの部分でボトルネックになっているかなどが分析しやすくなるかと思います。
うまく活用できると便利になるのではないでしょうか。