はじめに
技術本部 Platform Engineering Unitで1カ月間インターンをしていた竜 鶴吉(X)です。*1
Platform Engineering UnitではOrbitと呼ばれる、GKEをベースとしたシングルクラスタのアプリケーション開発基盤を運用しています。Orbitは開発者の認知負荷を低減させることを目的とし、複数のアプリケーションが稼働しています。今後、より多くのアプリケーションを乗せる予定で現在成長の過渡期と言えます。
その中で、運用しているアプリケーションの可観測性を向上することはチームのミッションの1つでした。そこで、今回私はOrbitにOpenTelemetryを導入し、Orbit上で稼働する名刺メーカーというサービスに計装の処理を実装しました。これにより、トレーシングによるリクエストフローの可視化と、トレースとログの紐付けを実現しました。
1カ月のインターンを通じて技術選定や設計・実装を任せて頂きました。その時に経験した技術選定のプロセスや、実装をするに当たり得られた知見について、Sansanの開発文化と併せて紹介できればと思います。
背景と目的
Orbit上で稼働しているアプリケーションにおいて、従来はメトリクスとログを個別に監視していたため、問題発生時に根本原因の特定が困難になるという課題がありました。
このような背景から、リクエストフローの可視化や、メトリクス・トレース・ログの関連付けによって可観測性を高めたいというニーズが開発者から寄せられていました。
そこで、OpenTelemetryを導入することでオブザーバビリティの3本柱である、メトリクス・トレース・ログを一元的に収集し、分散トレーシングを行い可観測性を向上させることを目的としました。
トレース収集・送信基盤の技術選定と設計について
Orbitは、複数アプリケーションが1つのGKEクラスタ上で稼働し、各アプリケーションはNamespaceで区切られた構成であることが特徴的です。
この前提の下で、トレーシングを可能にし可観測性を向上するにあたって最適な技術選定や設計をするために複数の観点を考慮しました。その際の意思決定の内容や理由について紹介します。
なぜOpenTelemetryなのか
Orbit上では複数のアプリケーションが稼働しており、当然ながらそれぞれについて個別の開発チームを有しています。
現在はメトリクスやログをGoogle Cloudに送信していますが、将来的に利用したいバックエンドが変化する可能性は十分に考えられ、アプリケーションごとに利用するバックエンドが異なるといった状態も想定されます。
そのため、特定のバックエンドに依存せず、設定の変更を柔軟に行うことができるといった点からOpenTelemetryはOrbitにとって魅力的な選択肢でした。
OpenTelemetryを用いた設計案
「OpenTelemetryを利用しトレースをバックエンドに送信する」ことは、さまざまな手段で実現できます。アプリケーションから直接送信することが出来ますし、OpenTelemetry Collectorに一度収集し、その後送信するといった方法も考えられます。さらに、OpenTelemetry Collectorを利用する場合でもSidecarパターンや、Deploymentといった個別のリソースとしてデプロイするなど多くの選択肢があります。
後者の場合、Namespaceごとに別々のアプリケーションが稼働している現在の構成において単一のNamespaceにCollectorリソースをデプロイすべきか、各アプリケーションのNamespaceにCollectorリソースをデプロイすべきかなどについても考慮する必要がありました。
また、「どのバックエンドへ送信するか」についても考える必要がありました。OrbitのGoogle Cloudプロジェクトは、アプリケーションごとのリソースへの権限管理をシンプルにするために各サービスのプロジェクトとは分離されています。
なお、Orbitのアーキテクチャ図は次のようになっています。(コンテナ基盤Orbitの紹介 より引用)
そこで、それぞれのGoogle Cloudプロジェクトにトレースデータを送信するという方針以外にも、Orbit上で稼働するアプリケーションのトレースデータを一元的にOrbitのプロジェクトに送信し、トレーススコープを用いて別のプロジェクトからOrbitプロジェクトにある自アプリケーションのトレースデータをCloud Traceで閲覧する方針も挙げられます。全アプリケーションからのトレースデータが同一プロジェクトのCloud Traceに集められた場合でもフィルタリングを行うことで自アプリケーションのデータのみの閲覧が可能なためです。
採用した構成について
上記の各選択肢についてメリットやデメリットを議論し、周辺技術について調査しながら現在のシステムに最適な構成を決定していきました。結論、次のような構成を取ることに決定しました。
- OpenTelemetry Collectorを採用する
- すべてのトレースデータを、何もせずにバックエンドへ送信するといったことは通常行いません。データのフィルタリングや加工など、バックエンドへ送信する前に各アプリケーションでトレースデータの前処理を行いたい場合がほとんどです。これらの処理をアプリケーション側ではなくCollectorで行うことにより責務を分割し開発者側の負担を軽減できます。加えて、設定を変更したい場合に逐一アプリケーションを変更し反映せずに済むことも大きなメリットの1つでした。
- Collectorを、Deploymentとしてデプロイする
- SidecarとしてCollectorをデプロイする場合、アプリケーション側でトレースデータの送信情報を変更しなくて良いというメリットがあったり、アプリケーションとSidecarが同様にスケールする点でスパイク時にCollectorリソースだけが飽和する可能性は少ないというメリットもあります。しかし、Sidecarとして導入する場合は本番環境で稼働中のpodの設定を変更する必要があり、既存環境への影響を最小限に抑えつつCollectorリソースを定義するメリットが大きいと考え、Deploymentとして定義することにしました。
- Collectorを各アプリケーションのNamespaceにデプロイする
- 運用時には、アプリケーションごとにCollectorの内部設定は異なることが想定されます。そのため、単一のNamespaceで共通の設定によりCollectorを管理することは適していませんでした。開発者が意識する必要のあるNamespaceの数が増えることは認知負荷の増大にもつながるため、既存の各アプリケーションのNamespaceにCollectorリソースを構築することにしました。
- トレースデータを各アプリケーションのGoogle Cloudプロジェクトに送信する
- トレースデータを単一のプロジェクトに収集する場合、どのアプリケーションがどの程度のトレースデータを送信しているのかの把握ができずコスト負担の按分が難しくなるというデメリットが挙げられます。各プロジェクトにトレースデータを送信することで、アプリケーションごとに発生しているデータ量に応じた料金が発生するため公平と言えます。
最終的なアーキテクチャとしては次の図のようになります。
レビュー文化について
このような技術選定や設計に関してチームメンバー全員を交えてトレードオフなどを議論し意思決定を行いました。その際に、なぜ今のOrbitの構成だとこのパターンを採用するのが適しているのか?といったことをさまざまな観点から考える必要がありました。そのため、必然的に周辺の技術に関する調査や導入したい構成のメリットだけでなくデメリットについても理解するようになり、自分自身の解像度が上がるようになりました。
また、自チームでは新たな技術を導入する際に、行った意思決定の理由や背景についてGitHub上で管理されているドキュメントにproposalとして用意し、チームメンバー全員の承認を得る必要がありました。このような仕組みは、メンバー間で認識の齟齬を防ぐために効果的であったと感じています。
実装について
Orbit上で稼働している名刺メーカーに対して、OpenTelemetryによる自動計装の処理に関する実装も行いました。具体的には、フロントエンドとバックエンドにそれぞれ自動計装を実装し、コンテキストを伝播させ一貫したトレーシングができる状態にした上でログとの紐付けも行いました。それぞれ、ユーザーテレメトリーとパフォーマンステレメトリーの収集を目的としています。
コンテキストの伝播
フロントエンドのトレースIDや親スパンIDを、OpenTelemetryの対応する標準規格であるW3C Trace Context形式に埋め込みました。(参考)
次のようなtraceparent HTTPヘッダをフロントエンドからバックエンドに伝播させます。
traceparent: <version>-<trace-id>-<parent id>-<trace-flags>
OpenTelemetryのPropagatorを利用し、トレースIDやスパンIDの埋め込み、及び取り出しを行いました。また、アプリケーションでログを出力する際、トレースID及びスパンIDを取得しGoogle Cloud Loggingが自動的に解釈できるフィールド名にセットするようにしたことで、トレースとログの紐付けを可能にしました。
次のような設定を既存の構造化ログに追加しました。(参考)
"logging.googleapis.com/spanId":"000000000000004a", "logging.googleapis.com/trace":"projects/my-projectid/traces/06796866738c859f2f19b7cfb3214824", "logging.googleapis.com/trace_sampled":false
トレースとコード変更の関連付け
OpenTelemetryには、テレメトリーが計装されたアプリケーションの名前やバージョンを表す属性である、service という概念があります。そして、アプリケーション側の実装ではservice.versionにコミットハッシュを含めるようにしています。エラーやパフォーマンス低下といった問題が発生したとして、それをCloud Traceで発見した場合に原因となった可能性のあるコードの変更を直接結びつけることができるようになるためです。
つまり、service.versionにコミットハッシュを含めることで、「特定のコミットが追加されてから問題が発生するようになった」といった形での原因の切り分けが容易になります。
コミットハッシュの利用は障害調査を容易にするための手段の1つであり、バージョンごとにリリースを管理している場合はservice.versionにリリースタグを含めるといった運用の仕方も考えられます。今回私の携わったアプリケーションではリリースタグによるデプロイを実施していなかったため、コミットハッシュを利用しました。
まとめと今後の展望
今回、1カ月というインターン期間において、Orbit上で稼働するアプリケーションにトレーシングを実現するための技術選定や設計・OpenTelemetryによる計装の実装を行いました。分散トレーシングは機能するようになっており、ログとトレースが紐付いた形で可観測性を向上させるというミッションについて初めのステップを踏み出せたと考えています。
今後は、メトリクスベースのSLOアラートをトレース・ログと紐付けるようにすることで障害発生時の調査を容易にしたり、トレースをメトリクスといった別のシグナルに変換することでさらなる可観測性の向上を図っていければ理想的だと考えています。
また、自動計装だけではサービス固有のビジネスロジックやメタデータについて知ることはできないため、必要に応じて手動計装による拡張を行い、サービス固有のロジックやメタデータの追加をする必要があります。
終わりに
技術選定や設計をする際にはさまざまな観点について調査しながらチームメンバーと議論したことが印象的でした。また、チームメンバー全員がレビューをしてくれるような文化があり、自分自身レビューでの指摘を通じて日々新たな知識を得ることができ、とても充実した1カ月間を過ごすことができました。
最後になりますが、インターン期間中はメンターの方をはじめ多くの人にお世話になりました。ありがとうございました。
*1:本記事は、執筆者本人の了承を得て、代理で投稿しています。