JaegerでOpentracing形式の分散トレーシングをGoのサーバに追加～EHW2018「アプリ⑧」

# 概要

このエントリは、「Enterprise "hello, world" 2018 Advent Calendar 2018」の12/25向けのものです。このAdvent Calendarでは、複数個のエントリにまたがる話の流れも鑑みつつ、なるべく1エントリで１つのトピックをカバーできるようにしています。

このエントリで記載するトピックは、[Jaeger](https://www.jaegertracing.io/)で[Opentracing](https://opentracing.io/)形式の分散トレーシングをGoのサーバに追加することです。

対象とするGoのサーバは、[Go-lang+GinでAPIサーバを作る一歩目～EHW2018「アプリ②」](https://qiita.com/hrkt/items/a524910b14fc8c05038f)で作成したものを利用します。

# 前提

## おことわり

- このEnterpfise "hello, world"シリーズは、ネタのためのエントリです。実環境でそのまま利用ことを目的とはしていません。
- 動かしやすさを優先してセキュリティを意図的に低くする設定など入れてありますのでご注意ください。

## 想定読者

「Enterprise "hello, world" 2018」的なネタとしては、下記のような状況を想定しています。

> 作ってきたアプリケーションに対するリクエストを計測するため、分散トレーシングのライブラリを適用する必要がある。ついでに計測データを核にするための仕組みも入れたい。

# Jaegerでデータを扱うための準備

## Jaegerとは

[Jaeger](https://www.jaegertracing.io/)は、[CNCF(Cloud Native Computing Foundation)](https://www.cncf.io/)のプロジェクトの一つで、主にマイクロサービス指向のアーキテクチャを持つシステムにおいて、コンポーネント間の呼び出しをトレースするためのツールです。

![jaeger-site.PNG](https://qiita-image-store.s3.amazonaws.com/0/65002/8bb5086f-06d2-69c1-200c-33f2b6ed5dd3.png)

## OpenTracingとは

Jaegerは、データの記録形式として[Opentracing](https://opentracing.io/)形式を利用しています。Opentracing形式で計測データを取得するためのライブラリが、様々な言語用に提供されています。

![opentracing-site.png](https://qiita-image-store.s3.amazonaws.com/0/65002/a00a0751-8bfd-3f20-8184-1247a63b1f8e.png)

## Jaegerのサーバをコンテナで立ち上げる

Jaegerは、ログの格納先として、[Cassandra、Elasticsearch、Kafkaを利用することもできますが](https://www.jaegertracing.io/docs/1.10/architecture/#collector)、このエントリでは簡単のため、コンテナ単体で使用します。

[Jaegerのサイト](https://www.jaegertracing.io/docs/1.10/getting-started/)に従って作業を進めます。エントリ執筆の3日前にリリース1.10が出ているようなので、これを使います。

サイトの記載をもとに、docker-compose.ymlを書いてみました。下記のような感じです。

```
version: '3.7'

services:

  jaeger:
    image: jaegertracing/all-in-one:1.10
    container_name: jaeger 
    environment:
      - COLLECTOR_ZIPKIN_HTTP_PORT=9411
    ports:
      - "5775:5775/udp"
      - "6831:6831/udp"
      - "6832:6832/udp"
      - "5778:5778"
      - "16686:16686"
      - "14268:14268"
      - "9411:9411"
```

docker-composeで起動します。

```
$ sudo docker-compose up
```

# アプリ側の対応

## 利用ライブラリ

Jaegerで[Go用に提供されているクライアントライブラリ](https://github.com/jaegertracing/jaeger-client-go)を利用します。

## ソース

指定されてたクライアントライブラをインポートします。
メトリクスを取得するAPI自体は、opentracingのgo版の形式となります。

```golang
import (
	"github.com/fvbock/endless"
	"github.com/gin-gonic/gin"

	opentracing "github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-lib/metrics"

	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
        jaegerlog "github.com/uber/jaeger-client-go/log"	
)
```

初期化方法は、[サンプルが提供されていますので](https://github.com/jaegertracing/jaeger-client-go/blob/master/config/example_test.go)、参考にします。このエントリでは、サンプルで提供されている開発用とプロダクション用の設定のうち、開発用を使用しています。開発用と本番では、対象のイベントをすべて記録するか、ある程度サンプリングして記録するかなどの指定を分けることになります。

```golang
	// Sample configuration for testing. Use constant sampling to sample every trace
	// and enable LogSpan to log every span via configured Logger.
	cfg := jaegercfg.Configuration{
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans: true,
		},
	}

	// Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
	// and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
	// frameworks.
	jLogger := jaegerlog.StdLogger
	jMetricsFactory := metrics.NullFactory

	// Initialize tracer with a logger and a metrics factory
	closer, err := cfg.InitGlobalTracer(
		"greetings-server",
		jaegercfg.Logger(jLogger),
		jaegercfg.Metrics(jMetricsFactory),
	)
	if err != nil {
		log.Printf("Could not initialize jaeger tracer: %s", err.Error())
		return
	}
defer closer.Close()
```

APIのエンドポイントで、opentracingの"span"を記録する設定を入れます。
opentracingは、複数個の"span"からなる"trace"を分散環境での一連の流れとして追えることがメリットですが、ここでは、単発のspanとしてトレースするためのコードを入れています。

OpenTracingのライブラリでは、context.Contextを使うことができますが、Ginのgin.Contextも、今回のエントリの範囲では、StartSpanFromContextの引数として使うことができています。

spanはfinishしてあげることが必要なので、goのdeferを使っています。

```golang
	router.GET("/api/greeting", func(ctx *gin.Context) {
		span, _ := opentracing.StartSpanFromContext(ctx, "api-greeting")
		defer span.Finish()
		
		ctx.JSON(200, gin.H{
			"message": "hello, world",
		})
	})
```

## アプリ起動

dep ensureして必要なライブラリをインポートして、いざ実行。

```shell-session
$ go run greetings-server.go
```

# 実行

## APIの呼び出し

ブラウザで、「http://localhost:8080/api/greeting」にアクセスしてみます。

![Screenshot_2019-02-19_05-23-10.png](https://qiita-image-store.s3.amazonaws.com/0/65002/bc43c3cc-35b7-f368-6ad4-09cc449f72bd.png)

## Jaegerサーバでの確認

ブラウザで、「http://localhost:16686」にアクセスしてみます。

左側のFind Tracesメニューで「Service」として”greeting-server”(ソース内で指定したサービス名)を指定し、メニュー下側の「Find Traces」ボタンをクリックします。

![Screenshot_2019-02-19_05-22-23.png](https://qiita-image-store.s3.amazonaws.com/0/65002/38a42d00-d4ab-c046-4a46-569f6457f8a1.png)

画面右上のチャートの中の丸印が、ブラウザからAPIにアクセスがあったタイミングを示します。
ブラウザでAPI呼び出しを何度か行い、再度「Find Traces」ボタンをクリックすると、トレース数が増えて表示されます。



# まとめ

このエントリでは、「Enterprise "hello, world" 2018 Advent Calendar 2018」(EHW2018)の25日目として、[Jaeger](https://www.jaegertracing.io/)で[Opentracing](https://opentracing.io/)形式の分散トレーシングをGoのサーバに追加することことをトピックとして取り上げました。

本エントリのソースコードは、[GitHubに](https://github.com/hrkt/greetings-server/releases/tag/0.0.3)あげてあります。

EHW2018、このエントリで25個目となりました。メリークリスマス。