DaprでRubyとJavaの分散トレーシングをしてみる

## はじめに

「[DaprでRubyとJavaを連携させてみる](https://qiita.com/koduki/items/59268c531a1b82887328)」に引き続き分散トレーシングを実施してみます。

DaprではOpenTelemetryに対応しており分散トレーシングをサイドカー側で行う事ができます。
そのためマイクロサービスで必須の分散トレーシングを実現するのにアプリ側にOpenZipkinやJaegerのライブラリを入れる必要はありません。

下記を参考に分散トレースの実装をしていきます。
- [Dapr docs - Observerability](https://github.com/dapr/docs/tree/master/howto#observerability)
- [Dapr docs - Azure Monitor](https://github.com/dapr/docs/blob/master/howto/diagnose-with-tracing/azure-monitor.md)

また今回はAzureMonitorを使うため`dapr-localforwarder`を利用します。上記のドキュメントにはOpenZipkinの説明もあり最初はそれを試してたのですが、どうもコンテキストがアプリ間で伝播せずそれぞれのSpanが独立して表示されていしまいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/85118e75-e606-2da5-b0d1-95b3f80efa25.png)

おそらくコンテキストの伝播を`X-correlation-Id`という独自の仕組みから[W3C Trace-Contextに準拠して`traceparent`を使うことにv0.7より変更した](https://github.com/dapr/dapr/issues/1371)影響です。

AzureMonitorのAzureInsightなら上手くいくようなので今回はそれを利用します。ドキュメントや詳細はv0.8でアップデートされるという事なのでそこに期待。

今回利用したコードは下記に配置しています。
https://github.com/koduki/example-dapr/tree/v02

## クライアントコードの改修

まずRubyで作成したClientコードを修正します。別にCLIのままでも良いのですいがコンテキストの伝播がわかりやすいようにWebアプリケーションに変更しました。
`add`というAPIでorderIDの登録と最新情報の取得を同時にやります。

```ruby
require 'net/https'
require "json"
require 'webrick'
include WEBrick

MY_DAPR_PORT=ARGV[0]

n = 0
s = WEBrick::HTTPServer.new(
    :Port => 1080,
    :HTTPVersion => WEBrick::HTTPVersion.new('1.1')
)
s.mount_proc('/add') do |req, res|
    n += 1
    req_headers =  req.raw_header.map{|x| x.split(/: /).map{|s| s.strip} }.to_h
    traceparent = req_headers["Traceparent"]
    
    p "orderId: #{n}, traceparent-id: #{traceparent}"

    params = {data: {orderId: n}}
    url = "http://localhost:#{MY_DAPR_PORT}/v1.0/invoke/javaapp/method/neworder"
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)

    headers = { 
        "Content-Type" => "application/json",
        "Traceparent" => traceparent
    }
    response = http.post(uri.path, params.to_json, headers)


    url = "http://localhost:#{MY_DAPR_PORT}/v1.0/invoke/javaapp/method/order"
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)

    headers = { 
        "Content-Type" => "application/json",
        "Traceparent" => traceparent
    }
    response = http.get(uri.path, headers)


    res.status = 200
    res['Content-Type'] = 'application/json'
    res.body = response.body
end

Signal.trap('INT'){s.shutdown}
s.start
```

コンテキストを伝播させるために`Traceparent`をリクエストから取得してAPIに投げる時にヘッダーに書き込んでいます。

## Azure Insightの設定

まずはAzureアカウントを作成してください。まだの人は[こちら](https://azure.microsoft.com/en-us/free/)から登録できます。
続いて、「Application Insights」の作成をします。Azureのサイドメニューからリソースの選択を「Create resource」を選択肢、`Application Insights`を探してこちらを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/e3066188-623c-40ff-4e28-9a2c65bc9acf.png)

続いてApplication Insightsのサイドメニューから「Configure -> API Access」を選択してAPIキーを作成します。以下は全て有効にしてください。

- Read telemetry
- Write annotations
- Authenticate SDK control channel

作成したAPIキーをメモしてください。

Application InsightsのOverviewに戻ってInstrumentation Keyを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/db30a02b-7319-b7dd-8e3a-0b5e93462fe2.png)


## Local Forwarderの設定

Azure Monitorにトレース情報を連携するには[Local Forwarder](https://docs.microsoft.com/en-us/azure/azure-monitor/overview)を利用します。Daprチームとしては今後は[Opentelemetry collector](https://github.com/open-telemetry/opentelemetry-collector)に寄せていくことを考えているようですが、現状はドキュメント化されてないので今回はLocal Forwarderを利用します。

Local Fowarderは以下のコマンドでDockerコンテナとして実行できます。先ほど取得したInstrumentation KeyとAPI Keyに置換して実行してください。

```bash
$ docker run -e APPINSIGHTS_INSTRUMENTATIONKEY=<Your Instrumentation Key> -e APPINSIGHTS_LIVEMETRICSSTREAMAUTHENTICATIONAPIKEY=<Your API Key> -d -p 55678:55678 daprio/dapr-localforwarder:latest
```

続いて、APIとクライアントそれぞれのcompornentディレクトリ配下に`native.yaml`と`tracing.yaml`を配置します。

```compornent/native.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: native
  namespace: default
spec:
  type: exporters.native
  metadata:
  - name: enabled
    value: "true"
  - name: agentEndpoint
    value: "localhost:55678"
```


```compornent/tracing.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: tracing
  namespace: default
spec:
  tracing:
    samplingRate: "1"
```

`native.yaml`には先ほど起動したLocal Fowaderの情報を記載します。`tracing.yaml`は分散トレース自体の設定でこちらはAzureMonitorを使う場合もOpenZipkinをつかう場合も基本同じものになるようです。

では、実行してみましょう。

```bash
$ dapr run --app-id javaapp --app-port 8080 --port 3500 --config ./components/tracing.yaml ./mvnw quarkus:dev
$ dapr run --app-id rubyapp --app-port 1080 --port 3600 --config ./components/tracing.yaml ruby app.rb 3600
```

## Azure Monitorで確認する

準備が整ったのでAzure Monitorで確認しましょう。

```bash
$ curl http://localhost:3600/v1.0/invoke/rubyapp/method/add
```

上記のコマンドでリクエストを投げたらApplication Insightsにアクセスします。 Application mapから以下のように分散トレーシングのグラフが確認できるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54904/b1055277-f6b4-79e3-7cd8-39f393b9d69d.png)

アプリケーション間接続だけではなく、State Managementに利用したredisへのトレーシングもきちんとできていますね。

## まとめ

Daprで分散トレーシングを確認してみました。サイドカーアプローチなので直接SDKを組み込む必要がなく便利だと思います。
より細かなカスタムスパンを取りたいときにはSDKを入れてそれを伝播させれば恐らく必要な詳細どでとることもできるはずです。ただ、もしかしたら考え方としては十分に各マイクロサービスが小さければサイドカーだけで十分なのかもしれません。

また、現在はTraceContext, OpenTelemetryといった標準にAzure独自の仕様からアーキテクチャを変更しようとしてる最中のようなのでドキュメント類が追いついて居ないです。
この辺りが整理されたらGCPのCloud TraceやOpenCensusとの連携も確認していければと思います。

それではHappy Hacking!

## 参考

- [サイドカーライブラリ Dapr の分散トレーシングを試す](https://kencharos.hatenablog.com/entry/2019/11/26/022555)