見出し画像

「Makuake」の分散トレーシング環境を整えたら運用がかなり楽になった件(OpenTelemetry+Datadog)

こんにちは、開発本部 Re-Architectureチームの松田(@ymtdzzz)です。今日は「Makuake」の分散トレーシング環境と導入効果についてご紹介したいと思います。

この記事では下記のことについて述べます

  • 「Makuake」における分散トレーシングの現状の構成と、技術選定

  • 分散トレーシング導入により改善したかった課題と、その導入成果

  • 導入にあたり、技術的に苦労した点

なお、下記については述べません

  • 分散トレーシング関連技術の歴史的変遷など

  • オブザーバビリティエンジニアリングなどの体系的なお話

  • 実装の詳細

導入背景

以前より「Makuake」では、SREを筆頭にDatadogを用いたメトリクス及びログベースの監視を積極的に取り入れることで安定的なサービスの運用を行ってきました。

なお、マクアケの開発組織は「チーム自治主義」のため、チームそれぞれがオーナーシップを持つサービスについて開発から運用まで責任を持って行っています。例えば私の所属するRe-Architectureチームでは、下記のような依存関係を有する認証基盤サービスの運用を行っています(かなり簡略化しています)。

こうしたシステムを運用していく中で、下記のような課題が出てきました。

  • サービスを跨いだパフォーマンス解析が困難

  • サービス(及びコンポーネント)を跨いだエラー調査が困難

これらの課題について、もう少し詳細に見てみましょう。


サービスを跨いだパフォーマンス解析が困難

「Makuake」ではSLI/SLOの運用もチーム単位で行っているため、Latencyなどパフォーマンス関連のメトリクスを日々追っています。その中で、パフォーマンス劣化が発生した場合にボトルネックを解析する必要があります。

しかし、認証サービスだけを見てもDBや他サービスなど様々な外部通信を行っているため、メトリクスやログだけでボトルネックを解析するのが難しいことがわかりました。

また、様々なマイクロサービスと通信を行っているMakuakeモノリスで同様の解析をしたいとなるとさらに難易度が上がります。APIのクライアントコードに呼び出し時間をログとして出力するような処理を記述していたこともありますが、そうした計測用の独自実装を加えると追加や削除のコストが高くなりがちでメンテされずに放置されてしまうリスクも増えます。


サービス(及びコンポーネント)を跨いだエラー調査が困難

エラーログやレスポンスコードによるアラート設定を行い、障害発生時などにすぐに気付ける仕組み作りはできていましたが、エラー調査が非常に大変であることがわかりました。逆に、アプリエラーからエンドユーザーの影響を調査するのも同様でした。

1)Makuakeモノリスのアクセスログ(500エラー)から根本原因を特定したい

2)認証サービスのエラーログからエンドユーザーの影響を特定したい

どちらのケースも、各関連ログの紐付けをタイムスタンプやIPアドレスなどのデータにより手動で紐付けることが必要になるのが辛い点です(色々なLog Viewerのタブを行ったり来たりしながら調査する運用担当者を見たことがある人は多いと思います・・・)。

シンプルなサービスであれば良いのですが、「Makuake」を構成するサービスが増え複雑度が増していくにつれログをはじめとしたシステムのイベントを関連付ける方法の必要性を感じるようになりました。

これらの課題を解決・改善するため、マクアケ社内のWorking Groupという、普段のチームでの開発とは別に組織横断で技術的な取り組みを行える時間を使ってトレーシング関連プロダクトの導入を試験的に進めてみることになりました。

なお、トレーシング(及びログやメトリクスなどのテレメトリ)の概念的で一般的な部分の説明については下記のようなわかりやすい記事に譲りたいと思います。

技術選定

OpenTelemetryの採用

オープンソースのTrace形式としてはJaegerやZipkinなどがあったり、各SaaSでも独自形式のTrace形式を用意してSDKから簡単にinstrumentする方法が提供されていますが、下記の理由からOpenTelemetryを採用することになりました。

1)テレメトリデータ収集方法としてデファクトになりつつある

OpenTelemetry自体は比較的新しいプロジェクトですが、その前身であるOpenCensusやOpenTracingの存在や、Jaegerなどでも独自形式のinstrumentation libraryがDeprecateされOpenTelemetryへの移行が推奨されたりしています。そのため、OpenTelemetryは今後テレメトリデータ収集のデファクトになりつつあると言えます。

2)ベンダーに依存しない

アプリケーションのinstrumentationはログよりも実装的な影響が大きいです(Trace出力対象によるものの)。自動収集のログやメトリクスであればベンダー依存の方法で収集していたとしても実装に影響を与えない方法(agentなど)で切り替えられるようにしておく余地がありますが、実装レベルでベンダーと結合してしまうと、収集方法や収集先のスイッチングコストが大きくなります。

そのため、スモールな状態からベンダーフリーな技術スタックを用いることでそのリスクを最小化したいと考えました。

Datadogとの併用

「Makuake」では主に監視系はDatadogを利用しています。DatadogにもTracingの機能がAPM(Application Performance Monitoring)の一部として提供されていますが、Datadog独自形式の他にOpenTelemetry形式もサポートしています。

コスト的にも運用的にもできるだけ今使っているサービスに寄せたかったため送信先としてはDatadogを採用し、送信するTraceの形式やinstrumentation libraryとしてはできるだけOpenTelemetryを使いつつ、完全自動計装など工数的なメリットがあったり選択肢が限られる場合のみDatadog SDKを採用する方針としました。

全体構成

サービス間通信を含む全体像は下記の通りです(相互通信を示すため、実際の構成とは異なります)。

Trace収集&送信

  • Trace収集はSidecarやDaemonsetとして動かしているdatadog agentコンテナが担当する

  • Traceの収集先はDatadogとする(DD形式、OpenTelemetry形式をサポート

実装言語別のinstrument

  • PHP:PHP extension(dd-trace-php)を用いた完全自動計装(実装に手を加えずにinstrumentする方法)

  • Golang:各ライブラリ別に用意されたOpenTelemtryのinstrumentation library(Registry参照)を用いた半自動計装(対象コンポーネントの初期化時に差し込むことで自動的にTraceを出力でき、実装修正を最小限に済ませる方法)

サービス間のContext Propagation

  • Golang⇔Golang:OpenTelemetry形式(W3C tracecontext)でやりとりするので特別な処理は不要

  • PHP→Golang:Client side(リクエスト送信側)でOpenTelemetry形式(W3C tracecontext)のcontextをリクエストに注入してpropagation(DD_TRACE_PROPAGATION_STYLE_INJECT

  • Golang→PHP:Server side(リクエスト受信側)でOpenTelemetry形式(W3C tracecontext)のcontextをリクエストから抽出してpropagation(DD_TRACE_PROPAGATION_STYLE_EXTRACT

導入時のハマりポイント

他チームで管理しているサービスも含めて横断的にinstrumentationを行う中で、色々と検討が必要なポイントがありましたのでいくつかピックアップしてご紹介します。

なお、紙面の都合上技術的な詳細については割愛させていただきます。

サービス“内” context propagation(Golang)

サービス間のcontext propagationについてはcollectorやagentの機能で済んだのですが、Golangで実装されたサービス内部のコンポーネントやレイヤー間のcontextの受け渡しについては想定よりも工数がかかりました。

1)interfaceの調整

Golangではcontextの受け渡しにはcontext.Contextを各コンポーネントまで伝播させてあげる必要があります。そのため、元々のアプリケーション構成によっては各関数の引数にcontext.Contextが追加されることになり、instrumentation libraryを使ってもコードの変更量が増加することがあります。

実際、今回導入を行った一部サービスについてはリクエストを受けるレイヤーから外部通信を行うレイヤーまでcontextを渡す構成ではなく、interface部分の変更量が多くなりました(置換で済むものが多かったので作業的な負荷はそこまで大きくはないものの・・・)。

2)ライブラリの独自contextとの互換性に注意が必要

ginのようなWebフレームワークを使用しているプロジェクトについても注意が必要です。例えばgin.Contextcontext.Context interfaceを実装しているものの、gin.Contextをそのまま渡してもSpanが上手く繋がらない場合がありました(ginのバージョンやotel library側の内部実装によりそうでしたが、深掘りはできていません)。

そのため、レイヤーを跨ぐ際はgin.Context.Request.Context()のように、Requestに含まれるcontextを取り出して各コンポーネントに渡してあげる方針としました。

Logとの紐付け

OpenTelemetry SpecificationではLog仕様もstableになってきましたが、言語別、特にGolangのinstrumentationについては「Not yet implemented」です。そのため、Logの出力方法については既存のままとし、Datadog側で紐付けを行うことにしました。

Datadogでは、収集したログにTraceIdとSpanIdをマッピングすることでTraceとの紐付けを行うことができます(公式ドキュメント)。

利用しているライブラリによりますが、LoggerをラップしてcontextからTraceIdとSpanIdを取得し、ログの属性に追加するような処理を書くことで対応しました(zapなど)。

middlewareのトレーシングとログとの紐付け(nginxなど)

nginxなどmiddlewareのトレーシングについては、middlewareごとにmoduleやextensionを導入する形になります。

例えばnginxであれば下記のようなmoduleが存在します。

OpenTelemetry nginx module

利用バージョンが古く、ビルド済みのバイナリを使えない場合は自前でソースからビルドする必要があるため、苦労が多かったです。また、ログとトレースの紐付けについても、アクセスログ上でTraceIdとSpanIdを出力するようにするための対応が必要なため非常に時間がかかりました。

詳細を語ろうとすると長くなるためここでは割愛しますが、nginxのinstrumentationについては下記のブログ記事にまとめていますので、興味がありましたらご参照ください。

結論、middlewareのトレーシングについては「PHPを使っているサービスがあり、nginxのアクセスログとTraceを紐付けたい!」など、特別な必要性がある場合のみ行うのが良さそうです。

導入してどうだったか

再掲しますが、トレーシング導入前に問題だと感じた課題は下記の通りです。

  • サービスを跨いだパフォーマンス解析が困難

  • サービス(及びコンポーネント)を跨いだエラー調査が困難

結論として、これらの課題はかなり改善されたように思われます。導入から数ヶ月経った今、これらの課題がどのように改善されたかを述べたいと思います。

サービスを跨いだパフォーマンス解析が可能になった

パフォーマンス上のボトルネック解析のため(APM: Application Performance Monitoring用途)のデータとしてはある程度有用なデータが取れるようになったと思います。

特に、様々なサービスを呼び出しているモノリスのパフォーマンスが課題になった際に、ボトルネックを見つけ、その改善によるインパクトについて確度が高い状態で改善施策の優先度を決定し着手できる点は強力に思えます。

また、これまで計測用コードを入れてログで確認していた、外部サービス呼び出し時間なども追加実装無しで取得できるようになったことも良い点かと思います。

ただ、現状の「Makuake」では相互通信するマイクロサービスについてはそこまで多くなく、どちらかというと1サービス内でのストアや外部サービス呼び出しなど、比較的狭い範囲での利用用途に留まっている印象です。

サービス(及びコンポーネント)を跨いだエラー調査が楽になった

サービスを跨いだTrace(&Span)の紐付け、さらにTraceとLogを紐付けることができるようになったため、エラー調査については劇的に楽になったと感じます。

アクセスログからアプリログ、その逆など双方向に問題を深掘りできるため、ログの紐付けの手間が無くなり非常に調査が楽になりました。

画像で示した通り、一つの画面にリクエストの内容やレスポンスやそれに紐付くログ、トレース情報が全て関連付けられた状態で表示されるため、アクセスログから根本原因となるアプリログを引いてくることも可能ですし、逆に、アプリのエラーログから最終的なレスポンス結果(≒ユーザー影響)を確認することもできます。

これが実際に役に立った事例として、最近実施したPHPバージョンアップデートが挙げられます。新旧二つのバージョンが並行稼働していた時期がありましたが、その中で生じたエラーについて、ユーザー影響の確認はもとより、それが「PHPバージョンアップによるものなのか」もしくは「既知のエラーなのか」を見極めるために非常に役に立ちました。

(利用者の声)

今後の課題

先述した通り、一部ではありますが分散トレーシングの仕組みを導入したことで運用上の課題がかなり克服できました。しかし、まだやり切れていないことや改善しなければならないことが多くあります。

非同期メッセージングを介したトレーシング

今回の導入スコープはHTTPやgRPCのような同期的なメッセージングを行うサービスのみとしていましたが、OpenTelemetryのPropagatorの仕組みを使うことでキューなどを用いた非同期メッセージングで連携したサービスのトレースも行うことが可能です。

非同期処理のトレースについては下記の記事でコード付きで紹介していますので、興味がありましたらご参照ください。

「Makuake」のシステムにも一部非同期メッセージングを介して処理を行うサービスが存在しており、そこでもパフォーマンスやエラー調査の課題を見聞きすることが増えているため、そうしたサービスへのトレース導入を目下進めています。

サンプリングやコスト最適化

現状、サンプリングについてはアプリケーション側では行わず、Datadog側で行うようにしています(一部サービスを除く)。また、Datadog側のサンプリングについても予め用意された保持フィルター(インテリジェント保持フィルター)を利用しているサービスが多く、詳細なコントロールは行っていないというのが現状です。

インテリジェント保持フィルターの場合、コストがかからない代わりにサンプリング条件が不明瞭な点や保持されるトレース情報に制限があるなどデメリットも存在するため、サービス毎に収集したいトレースの性質に合わせて独自の保持フィルターを設定する必要が出てきそうです。

また、indexされたトレース以外にも、Datadog側で取り込んだトレースの容量でコストが発生するため、クエリログなどの情報をどこまでトレースに載せるかなど必要に応じて出力側でコントロールすることでコストを最適化する必要があると考えています。

(mysqliのトレースの取り込み容量がとても多くなってしまっている)

最後に

今回の記事では、マクアケにおける運用改善の一環としてOpenTelemetryを用いたトレーシングの導入について、導入背景やその結果について全体的な内容を述べさせて頂きました。まだ導入や活動は始まったばかりですが、有用な面も見えてきましたので今後も活用していきたい技術だと感じました。

この記事が、同じような課題を抱えていたり分散トレーシングについて導入を検討している方々の参考になれば幸いです。

--------------------------------------------------------------------------

現在マクアケではエンジニアの募集をしております。
もっとマクアケについて知りたい方は、社員インタビュー記事や以下リンクからカジュアル面談・エントリーのお申し込みをお願いいたします!

◉エントリーをご希望の方

◉カジュアル面談をご希望の方

◉マクアケの中の人を知りたい方

この記事がおもしろかった!と思っていただけたら、是非「スキ」&「シェア」をしていただけますと嬉しいです。

この記事が気に入ったらサポートをしてみませんか?