マイクロサービスのトレーサビリティを確保するための分散トレーシングとJaeger

こんにちは、shoito(しょいと)です。
今回はCloud Native Computing Foundation(CNCF)のプロジェクトリストを見ていて、気になった分散トレーシング関連プロジェクトのOpenTracingJaegerについて紹介します。

分散トレーシングとOpenTracing

マイクロサービスのように、アプリケーションが複数のサービスで構成され複雑になると、1つのリクエスト(例えばREST API)が内部的に複数のサービスを跨いで処理されることになります。
そのため、リクエスト全体の流れを分析することが難しくなり、そのままではトレーサビリティの低下を招くことになります。

分散トレーシングシステムは、そのような分散したシステムのリクエストを分析するためのものであり、OpenTracingは分散トレーシングのためのベンダ非依存のAPI仕様とライブラリを提供しています。

分散トレーシングシステムのマネージド・サービスとしてはAWSが提供するAWS X-Ray、Google Cloudが提供するStackdriver Traceなどがあります。

Jaegerとは

Jaegerは、Uber TechnologiesDapperやOpenZipkinにインスパイアされ開発・OSSした分散トレーシングシステムです。OpenTracing互換のデータモデルとInstrumentationライブラリを提供します。
JaegerはAgentとCollector, Queryのコンポーネントから構成され、以下を提供します。

  • トレースする仕組み
  • トレース結果をモニタリングする仕組み

Jaeger公式サイトのArchitecture解説より
技術仕様としては、以下のようになっています。

  • バックエンドはGoによる実装
  • ReactによるWeb UI実装
  • サポートストレージバックエンド
    • Cassandra 3.4+
    • Elasticsearch 5.x, 6.x
    • メモリストレージ

Jaegerを試してみた

動作確認環境

  • OS: macOS Mojave(10.14.1)
  • Docker: Docker for Mac Community Edition Version 18.06.1-ce-mac73 (26764)

JaegerバックエンドとJaeger UIを立ち上げる

まずはJaeger公式のGetting startedに従って、ローカルテスト用のAll-in-oneのDockerイメージを使って起動します。
あとで使うデモアプリケーションで不要なポートの分は-pオプションから省いています。

$ docker run -d --name jaeger \
   -p 6831:6831/udp \
   -p 16686:16686 \
   jaegertracing/all-in-one:1.7
Unable to find image 'jaegertracing/all-in-one:1.7' locally
1.7: Pulling from jaegertracing/all-in-one
a83feccf3672: Pull complete
d518ddfa1466: Pull complete
Digest: sha256:4e3779c7c1f39af39f998d3056e020a1e2e6ae5babc4aa41496bec4a73b261f0
Status: Downloaded newer image for jaegertracing/all-in-one:1.7
2c022c4b480267e71402ed50f7d7e7fac45a2e6d0457c420ffc6a6c380037397

コンテナが起動したら、ブラウザで http://localhost:16686 を開いてJaeger UIにアクセスします。
この段階では、データストアにはトレース情報がないため、Jaeger UIで見られるデータはありません。

$ open -a "Google Chrome" http://localhost:16686


上記の docker run 時に指定したポートは以下のような役割になっています。

ポート プロトコル コンポーネント 機能
6831 udp agent jaeger.thriftエンドポイント(compact)
16686 http query Jaeger UIフロントエンドの提供

ちょっと気になったので設定情報を返すエンドポイントの 5778 番ポートに以下のようなGETリクエストを送ってみたところ、jaeger-queryサービスのサンプリングレートの設定値が取得できました。
サンプリングしないため、デフォルト値の1となっています。

$ curl "http://localhost:5778/?service=jaeger-query"
{"strategyType":0,"probabilisticSampling":{"samplingRate":1}}

トレース対象のデモアプリケーション HotROD

HotRODはOpenTracingを使ったライドシェアアプリケーションです(テキストベースですが)。
画面上のボタンをクリックすると、ドライバーを呼び出し、乗客のところへ配車し、車のナンバープレートと到着までにかかる時間が表示されます。

さらにデバッグ情報として、リクエストIDとバックエンド応答までのレイテンシが表示されます。

HotRODのコードはGitHubのjaegertracing/jaegerリポジトリにあるのですが、試すだけならばDockerイメージ(jaegertracing/example-hotrod)が簡単です。

$ docker run --rm -it \
   --link jaeger \
   -p8080-8083:8080-8083 \
   jaegertracing/example-hotrod:1.7 \
   --jaeger-agent.host-port=jaeger:6831 \
   all
Unable to find image 'jaegertracing/example-hotrod:1.7' locally
1.7: Pulling from jaegertracing/example-hotrod
fbbcd8295019: Pull complete
Digest: sha256:1b21aa8d851903c823d38a942aa7e98234d0918ffcc119f866e9f3e23807250d
Status: Downloaded newer image for jaegertracing/example-hotrod:1.7
2018-11-08T05:27:24.453Z    INFO    cmd/root.go:86  Using expvar as metrics backend
2018-11-08T05:27:24.454Z    INFO    cmd/all.go:25   Starting all services
2018-11-08T05:27:25.590Z    INFO    log/logger.go:37    Starting    {"service": "route", "address": "http://0.0.0.0:8083"}
2018-11-08T05:27:25.592Z    INFO    log/logger.go:37    Starting    {"service": "frontend", "address": "http://0.0.0.0:8080"}
2018-11-08T05:27:25.700Z    INFO    log/logger.go:37    Starting    {"service": "customer", "address": "http://0.0.0.0:8081"}
2018-11-08T05:27:25.702Z    INFO    log/logger.go:37    TChannel listening  {"service": "driver", "hostPort": "[::]:8082"}

起動ログを見ると frontend, customer, route, driver という4つのマイクロサービスからHotRODは構成されています。

コンテナが起動したら、ブラウザで http://localhost:8080 を開いてHotRODのfrontendサービスにアクセスします。

$ open -a "Google Chrome" http://localhost:8080

画面上のボタンを何度かクリックして、トレース情報をJaegerに送りましょう。

Jaeger UIでトレース情報を確認

トレース情報が貯まったところで、Jaeger UIを確認していきます。
Jaeger UIには、Search, Compare, Dependenciesのメニューがあります。

Search - 大量のトレースから目的のトレース情報を探す

Search画面はトレース情報の検索UIを提供します。

左側の Find Traces では、

  • Service: frontend, driver, customer, route のようなサービスによる絞り込み
  • Operation: all, HTTP GET, HTTP GET パスのような操作による絞り込み
  • Tags: error=true, http.status_code=200のようなスパン・タグによる絞り込み
  • Min Duration: レイテンシの最短時間による絞り込み

...のような絞り込み機能を提供しています。

右上部には、各トレース情報の発生時刻とレイテンシがプロットされていて、遅延の発生がひと目で分かるようになっています。
右下部には、絞り込み条件に合ったトレース情報が1件ずつ表示されます。

さらに詳細を知りたいトレース情報を画面から選択すると、詳細ビューに遷移します。
ここでは、トレースの各スパンの詳細情報が表示されますが、HTTPリクエストであれば、URLやレイテンシ、HTTPステータスコードを確認できますし、MySQLのようなDBアクセスであれば、発行されたSQLまでも確認が可能です。

トレースとスパンの関係については、トレースはリクエストからレスポンスまでのトランザクションを構成するスパンの集合であり、スパンは1つのサービス内の処理を表現しています。

Jaeger公式サイトのArchitecture解説より

Compare - 2つのトレース情報を比較して問題の原因箇所を特定する

Compare画面は2つのトレース情報の比較UIを提供します。
比較した結果、パフォーマンスのボトルネックがどこなのか可視化されます。

Dependencies - サービス間の依存関係を可視化する

Dependencies画面はサービス間の依存関係を表示するUIを提供します。

スクリーンショットの例だと、HotRODのWeb UIを提供するfrontendサービスからcustomerサービス、driverサービス、routeサービスへ、さらにcustomerサービスからmysqlへ、driverサービスからredisへとRPCリクエストがされていることが、その呼び出し回数も含めて分かります。

Jaegerストレージバックエンドの補足

技術仕様にも記載したように、トレース情報を保存するストレージとして、インメモリストレージ、CassandraElasticsearchがサポートされています。 なお、本記事のDockerイメージを使ったやり方では、インメモリストレージとなるため、Dockerコンテナを停止するとトレース情報は消えてしまいますので、ご注意ください。

さいごに

分散トレーシングとOpenTracing、そして分散トレーシングシステムのJaegerを紹介しました。
OpenTracingに関しては、以前の業務でSpring BootアプリケーションにDatadog APMを導入した際に扱っていたのですが、紹介したようにパフォーマンスボトルネックやサービス/ミドルウェアの依存関係が可視化され、サービス運用の助けになるものでした。

マイクロサービスによりシステムの構成が複雑になると、サービス間の依存関係やログの把握が難しく、問題が発生した場合に原因の調査が困難になります。
そんなシステムでいざ問題が発生した場合、効率的に問題の事象と原因を把握するために、Jaegerのような分散トレーシングシステムが重要になるのではないでしょうか。

最後に、私のいるプロダクトグループでは一緒にプロダクト開発をするメンバーを募集しています。
少しでも興味のある方は以下の記事を是非ご覧ください。

AWS事業本部プロダクトグループで一緒に働いてくれる方を募集しています!

参考