マイクロサービスを跨った処理の追跡機能の実現

Takashi Natsume
nttlabs
Published in
10 min readAug 7, 2019

OpenStackの複数コンポーネント間に跨った処理の追跡機能の実現

皆さん、こんにちは!NTTソフトウェアイノベーションセンタの夏目です。今回はOpenStack®におけるマイクロサービスを跨った処理の追跡機能の実現についての弊社(NTTグループ)の貢献を紹介します。

OpenStackとマイクロサービスアーキテクチャ

OpenStackはマイクロサービスアーキテクチャを取っており、数多くのコンポーネント(コントローラ)が連携してユーザにサービスを提供しています。例えば、仮想サーバの作成を行なう場合は、ユーザはNovaにリクエストを送りますが、NovaはKeystone(認証)、Glance(起動イメージの管理)、Neutron(仮想ネットワーク)、Cinder(仮想ブロックストレージ)等と連携して仮想サーバを作成します。複数のコンポーネントが連携して動作し、あるリクエストに関する処理が複数のコンポーネントで処理されます。

OpenStackの連携方法

OpenStackの各コンポーネントにおいては主に以下の2つの方法で連携を行なっています。

  1. REST API
    異なるコンポーネント間および同一コンポーネント内で用いられる方法です。(図1)
  2. メッセージング・ミドルウェアを介したRPC(リモート・プロシージャ・コール)
    基本的に同一コンポーネント内のみで用いられる方法です。(図2)
図1. REST API
図2. RPC

ログ出力とリクエストID

各コンポーネントにおいて出力されるログについては、リクエストID(Request ID)という識別子で、あるリクエスト(処理要求)に紐づく一連の処理を識別できるようになっています。(図3)

図3. ログに出力されるリクエストID(Ocataにおけるログ)

ただし、それぞれのコンポーネントでリクエストIDを生成しており、あるコンポーネントから別のコンポーネントを呼び出した場合の処理であってもコンポーネントごとに違うIDとなっていました。(図4)

図4. コンポーネントごとに異なるリクエストID(Ocataにおけるログ)

RPCにおいては、ログに出力されるメッセージIDで呼び出し元と呼び出し先の処理を辿ることができます。(図5)

図5. ログに出力されるメッセージID(Ocataにおけるログ)

REST APIにおいては、呼び出し先のリクエストIDは「X-OpenStack-Request-ID」というHTTPレスポンスヘッダにセットされて、呼び出し元に応答が返されます。

ログ解析の課題

Ocataリリースまではログに出力されるリクエストIDが各コンポーネントで異なっており、同一のIDではないので、複数のコンポーネントに跨った処理が追跡しづらいという課題がありました。

解決方法の提案

私たち(NTT)は上述するログ解析の課題を解決するため、コミュニティにおいてまず以下の提案を行ないました。

a) 外部から与えたIDの全てのコンポーネントでの利用
OpenStackの外部で生成したIDを(REST APIにおける)入力として,そのIDを全てのコンポーネントで利用してログに出力する方法です。

しかしながら私たちの提案は、セキュリティの観点から、 OpenStackを利用したシステムや呼び出し先のコンポーネントから見て「外部」からIDが与えられることに懸念があるとの意見がコミュニティで出されました。そのため、他の方法を検討しました。主に以下のような方法です。

b) OpenStackコンポーネントが生成するIDの利用
Keystone(認証機能を持つコンポーネント)が発行するaudit IDを利用して,全コンポーネントで一意のIDを使用する方法です。

c) 呼び出し先から返却されるリクエストIDの利用
HTTPレスポンスヘッダに格納されて返却された呼び出し先のリクエストIDを呼び出し元で(呼び出し元のリクエストIDと一緒に)ログに出力する方法です。

d) プロファイラーの利用
プロファイラー(OSProfiler)を利用して,HTTPレスポンスヘッダを全てログに出力させる方法です。

セキュリティの観点はもちろんですが、セキュリティ以外にも検討すべき観点というものがありました。主に以下の観点です。

i) 性能
この機能を実装して利用した場合に,性能の劣化がない,あるいは少ないかどうか。

ii) ログへの出力量
ログ出力が多いために,ログを人間が確認した場合に確認しにくくなっていないか。

iii) コール・グラフの作成
3つ以上のコンポーネントが連携して処理を行なう場合に,全体のコール・グラフを作成することができるかどうか。

iv) 既存のクライアント・アプリケーションへの影響
コードの修正の発生の有無や変更量が少ないかどうか。互換性があるかどうか。

v) 競合(マルチスレッドでの使用)
マルチスレッドでその機能を使用した場合に,不具合がないように動作可能であるか。

「b)OpenStackコンポーネントが生成するIDの利用」については、「iii)コール・グラフの作成」の観点から、コールグラフの作成が難しいケースがあるということ、「d) プロファイラーの利用」については、「i) 性能」と「ii) ログへの出力量」の観点から商用環境への適用は難しいという結論になり、「c) 呼び出し先から返却されるリクエストIDの利用」の方法を採用することとなりました。

解決方法の実装

実装方法ですが、上述した「iv) 既存のクライアント・アプリケーションへの影響」の観点から、コンポーネントの呼び出しを行なうクライアント・ライブラリに実装を行なうこととし、呼び出し先のリクエストIDを呼び出し元のリクエストIDとともに一行でログに出力する機能(図6)を追加しました。

図6. 呼び出し先のリクエストIDのログ出力機能

例えば、python-novaclientでの実装は以下のパッチで行ないました(筆者が実装を行いました)。

https://review.opendev.org/#/c/322664/ (2016年6月)

この機能の実装により、呼び出し元のリクエストIDと呼び出し先のリクエストIDを紐づけることができるようになりました。

また、私たちはpython-cinderclient、python-glanceclient、python-neutronclient等でも実装を行いました。

OpenStack SDKの対応

主要な各クライアント・ライブラリで機能の実装を行いましたが、コミュニティの動向として、各クライアント・ライブラリ(CLIおよびPython API bindings)のOpenStack ClientおよびOpenStack SDKへの移行が進められており(2019年8月現在も進行中です)、将来的にもこの機能の利用を行なうためには、それらにおける対応も必要となります。そこで私たちはOpenStack SDKでの対応を行ないました。OpenStack SDKはKeystoneauthを利用しており、そのKeystoneauthに機能を実装することにより、呼び出し元と呼び出し先のリクエストIDの両方のログの出力を実現しました。

実装したパッチは以下のパッチです。実装したのは私たちのチームの一員であったNTT DATA, Inc.(当時)の Abhishek Kekaneさん(現在GlanceプロジェクトのCore Reviewer)です。

https://review.opendev.org/#/c/392442/ (2017年1月)

機能実装後のログ出力の例

以下の図7は機能実装後のログを抜粋したものになります。図では3行になっていますが、実際にはその3行が1行としてログに出力されます。赤い文字列が呼出し元のリクエストID(req-d7452eb4-dd88–43e8–918a-0a75f93e4b8b)であり、青色が呼出し先のリクエストID(req-94038f1c-bcae-494d-a31c-0d50ccd101ad)になります。これにより、呼出し元の処理に紐付く呼出し先のリクエストIDが分かりますので、その呼出し先のリクエストIDを利用して呼出し先のログの該当行を見つけ、ログ解析を行なうことができます。

図7. 機能実装後のログ

グローバルリクエストID(Global Request ID)

Pikeリリース(2017年8月)からはグローバルリクエストID(Global Request ID)という機能(図7)が追加されています。どのような機能かというと、REST APIのHTTPリクエストヘッダの「X-Openstack-Request-Id」に呼び出し元のリクエストIDをセットして呼び出すと、そのリクエストIDがグローバルリクエストIDとして扱われて、呼び出し先のログに出力されるというものです。

図7. nova-cinder連携におけるグローバルリクエストIDのログへの出力

この機能は、我々が当初提案していた「外部」で生成されたIDを同一ID(同一文字列)として複数のコンポーネント(複数のログ)で用いる方法です。Elastic Searchにおいて同一のID(文字列)で複数のログに対して検索を掛けることができたら解析作業を行なううえで便利であるとの理由で機能が追加されました。

なお、私たちが提案した時に出されたセキュリティ上の懸念については、この機能において無効な形式のリクエストID(文字列)を入力したら、その場合はグローバルリクエストIDとしては用いないということになっています(有効な形式の文字列のみグローバルリクエストIDとして設定する)。また、「iii) コール・グラフの作成」については、残念ながら同一ID(グローバルリクエストID)だけを用いた場合には、コールグラフの作成が難しいケースがあります。そのため、私たちが実装した機能が残されており、グローバルリクエストIDの機能と共存するかたちとなっています。なお、各コンポーネントごとに生成されるリクエストIDはローカルリクエストID(Local Request ID)と呼ばれています。

おわりに

さて、今回はOpenStackにおけるマイクロサービスを跨った処理の追跡機能の実現について私たち(NTT)の貢献を紹介しました。今後機会があれば、私たちの他の貢献についても紹介していきたいと思います。それでは、またお会いしましょう!

--

--