AppSwitchによる依存関係順序付けの回避

AppSwitchを使用したアプリケーションの起動順序と起動レイテンシの問題への対処。

2019年1月14日 | Dinesh Subhraveti氏 - AppOrbitおよびコロンビア大学

私たちは、アプリケーションの分解と再構成という興味深いサイクルを経験しています。マイクロサービスのパラダイムは、モノリシックアプリケーションを個別のサービスに分割することを推進していますが、サービスメッシュのアプローチは、それらを構造化されたアプリケーションに再結合するのに役立っています。そのため、マイクロサービスは論理的には分離されていますが、独立しているわけではありません。通常は密接に相互依存しており、それらを分割すると、サービス間の相互認証の必要性など、多くの新しい問題が発生します。Istioはこれらの問題の大部分を直接解決します。

依存関係順序付けの問題

アプリケーションの分解によって発生する問題の1つであり、Istioでは対処されていないのが、依存関係の順序付けです。これは、アプリケーション全体が迅速かつ正しく起動することを保証する順序で、アプリケーションの個々のサービスを起動することです。すべてのコンポーネントが組み込まれているモノリシックアプリケーションでは、コンポーネント間の依存関係の順序付けは内部のロックメカニズムによって強制されます。しかし、サービスメッシュでは個々のサービスがクラスタ全体に分散している可能性があるため、サービスを最初に起動するには、それが依存するサービスが稼働して利用可能であることを確認する必要があります。

依存関係の順序付けは、多くの相互に関連する問題を抱えているため、一見単純ではありません。個々のサービスの順序付けには、サービスの依存関係グラフが必要であり、リーフノードからルートノードに戻る順序で起動することができます。このようなグラフを作成し、アプリケーションの動作とともに時間の経過とともに進化する相互依存関係を更新することは容易ではありません。依存関係グラフが何らかの方法で提供されたとしても、順序付け自体を強制することは容易ではありません。指定された順序でサービスを起動するだけでは明らかに不十分です。サービスは起動した可能性がありますが、まだ接続を受け入れる準備ができていない可能性があります。たとえば、docker-composeの`depends-on`タグの問題です。

サービスの起動間で十分に長いスリープを導入することとは別に、よく使用される一般的なパターンは、サービスを開始する前に依存関係の準備状況を確認することです。Kubernetesでは、これはpodのinitコンテナの一部として待機スクリプトを使用して行うことができます。しかし、これは、すべての依存関係がアクティブになるまで、アプリケーション全体が停止することを意味します。アプリケーションによっては、最初の発信接続を行う前に、起動時に数分間初期化に時間がかかる場合があります。サービスの起動をまったく許可しないことで、アプリケーション全体の起動時間にかなりのオーバーヘッドが追加されます。また、initコンテナを待機する戦略は、同じpod内の複数の相互依存サービスの場合には機能しません。

例:IBM WebSphere ND

これらの問題をより詳細に理解するために、広く導入されているアプリケーションミドルウェアであるIBM WebSphere NDを考えてみましょう。これはそれ自体がかなり複雑なフレームワークであり、ノードインスタンスのセットを管理するデプロイメントマネージャー(`dmgr`)と呼ばれる中心的なコンポーネントで構成されています。ノード間のクラスタメンバーシップをネゴシエートするためにUDPを使用し、ノードインスタンスが起動してクラスタに参加する前に、デプロイメントマネージャーが稼働して動作している必要があります。

なぜ最新のクラウドネイティブなコンテキストで従来のアプリケーションについて話しているのでしょうか?KubernetesとIstioプラットフォームで実行できるようにすることで、大きなメリットが得られます。本質的には、従来のアプリとグリーンフィールドアプリを同じ最新のプラットフォーム上で実行できるようにすることで、両者の相互運用性を促進するモダナイゼーションジャーニーの一部です。実際、WebSphere NDは要求の厳しいアプリケーションです。特定のネットワークインターフェース属性などを備えた一貫性のあるネットワーク環境を期待しています。AppSwitchはこれらの要件に対応できます。ただし、このブログの目的では、依存関係の順序付けの要件と、AppSwitchがどのように対処するかについて焦点を当てます。

`dmgr`とノードインスタンスをKubernetesクラスタ上にpodとして単純にデプロイしても機能しません。`dmgr`とノードインスタンスには、数分かかる可能性のある長い初期化プロセスがあります。それらがすべて同時スケジュールされた場合、アプリケーションは通常、奇妙な状態になります。ノードインスタンスが起動して`dmgr`が見つからない場合、代替の起動パスを使用します。代わりに、すぐに終了した場合、Kubernetesのクラッシュループが引き継ぎ、おそらくアプリケーションが起動します。しかし、その場合でも、タイムリーな起動が保証されるわけではありません。

1つの`dmgr`とそのノードインスタンスは、WebSphere NDの基本的なデプロイメント構成です。本番環境で実行されているWebSphere ND上に構築されたIBM Business Process Managerなどのアプリケーションには、他にも多くのサービスが含まれています。これらの構成では、相互依存関係のチェーンが存在する可能性があります。ノードインスタンスによってホストされるアプリケーションに応じて、それらの間にも順序付けの要件が存在する可能性があります。長いサービスの初期化時間とクラッシュループの再起動により、アプリケーションが妥当な時間内に起動する可能性はほとんどありません。

Istioにおけるサイドカーの依存関係

Istio自体も、依存関係の順序付けの問題の一種の影響を受けます。Istioの下で実行されているサービスへの接続とからの接続は、そのサイドカープロキシを介してリダイレクトされるため、アプリケーションサービスとそのサイドカー間に暗黙的な依存関係が作成されます。サイドカーが完全に動作していない限り、サービスへのすべての要求とからの要求はドロップされます。

AppSwitchを使用した依存関係順序付け

では、これらの問題をどのように解決すればよいでしょうか?1つの方法は、アプリケーションにそれを委譲し、「適切に動作」し、起動順序の問題の影響を受けないようにする適切なロジックを実装することです。しかし、多くのアプリケーション(特に従来のアプリケーション)は、順序が間違っているとタイムアウトするかデッドロックします。新しいアプリケーションの場合でも、各サービスに対して1回限りのロジックを実装することは、回避する方が良い相当な追加の負担となります。サービスメッシュは、これらの問題に関する適切なサポートを提供する必要があります。結局のところ、共通のパターンを基盤となるフレームワークに組み込むことは、サービスメッシュの本質です。

AppSwitchは、依存関係の順序付けを明示的に解決します。クラスタ内のクライアントとサービス間のアプリケーションのネットワークインタラクションの制御パス上に位置し、`connect`呼び出しを行うことでサービスがクライアントになるタイミングと、`listen`呼び出しを行うことで特定のサービスが接続を受け入れる準備が整ったタイミングを正確に認識します。その*サービスルーター*コンポーネントは、これらのイベントに関する情報をクラスタ全体に伝播し、クライアントとサーバー間のインタラクションを仲介します。このようにして、AppSwitchは、ロードバランシングや分離などの機能をシンプルかつ効率的に実装します。アプリケーションのネットワーク制御パスの同じ戦略的な位置を活用することで、依存関係グラフに従って全体のサービスを粗くシーケンスするのではなく、これらのサービスによって行われた`connect`と`listen`の呼び出しをより細かい粒度で整列させることが考えられます。これにより、複数レベルの依存関係の問題が効果的に解決され、アプリケーションの起動が高速化されます。

しかし、それでも依存関係グラフが必要です。サービスの依存関係の発見に役立つ多くの製品とツールが存在します。しかし、それらは通常、ネットワークトラフィックのパッシブモニタリングに基づいており、任意のアプリケーションに対して事前に情報を提供することはできません。暗号化とトンネリングによるネットワークレベルの難読化も、それらを信頼性の低いものにします。依存関係の発見と指定の負担は、最終的にアプリケーションの開発者またはオペレーターに負われます。実際、依存関係の仕様の一貫性チェック自体が非常に複雑であり、依存関係グラフを必要としない方法があれば、最も望ましいでしょう。

依存関係グラフのポイントは、どのクライアントが特定のサービスに依存しているかを知ることです。そうすれば、それらのクライアントをそれぞれのサービスがライブになるまで待機させることができます。しかし、どの特定のクライアントが重要なのでしょうか?最終的に常に成り立つタウモロジーは、サービスのすべてのクライアントがサービスに暗黙的な依存関係を持っているということです。それがAppSwitchが要件を回避するために利用しているものです。実際、それは依存関係の順序付けを完全に回避します。アプリケーションのすべてのサービスは、起動順序に関係なく同時スケジュールできます。それらの間の相互依存関係は、個々の要求と応答の粒度で自動的に解決され、迅速かつ正確なアプリケーションの起動につながります。

AppSwitchモデルと構成要素

AppSwitchの高レベルのアプローチについて概念的な理解を得たので、関係する構成要素を見てみましょう。しかしまず、使用方法の概要を説明します。異なるコンテキストのために記述されていますが、このトピックに関する以前のブログを再確認することも役立ちます。完全を期すために、AppSwitchは非ネットワーク依存関係を気にしないことも付け加えておきます。たとえば、2つのサービスがIPCメカニズムまたは共有ファイルシステムを使用して相互作用することが可能です。そのような深い結びつきを持つプロセスは、通常、同じサービスの一部であり、順序付けのためにフレームワークの介入を必要としません。

AppSwitchの中核は、BSDソケットAPIと、ソケットを扱う`fcntl`や`ioctl`などの関連する呼び出しをインストルメントできるメカニズムに基づいています。その実装の詳細がどれほど興味深いものであれ、それは主なトピックから私たちをそらすことになるので、他の実装と区別する重要な特性を要約するにとどめます。(1)高速です。`seccomp`フィルタリングとバイナリインストルメンテーションを組み合わせて使用することで、アプリケーションの通常の処理への介入を積極的に制限します。AppSwitchは、データに実際に触れる必要がないため、サービスメッシュとアプリケーションネットワーキングのユースケースに特に適しています。対照的に、ネットワークレベルのアプローチはパケットごとにコストが発生します。いくつかのパフォーマンス測定については、このブログを参照してください。(2)カーネルサポート、カーネルモジュール、またはパッチは必要なく、標準的なディストロカーネルで動作します。(3)通常のユーザーとして実行できます(root権限不要)。実際、このメカニズムにより、ネットワークコンテナへのroot権限の要件を削除することで、root権限なしでDockerデーモンを実行することも可能になります。(4)アプリケーションに変更を加える必要がなく、WebSphere NDやSAPからカスタムCアプリ、静的にリンクされたGoアプリまで、あらゆるタイプのアプリケーションで動作します。現時点での唯一の要件はLinux / x86です。

サービスと参照のデカップリング

AppSwitchは、アプリケーションをその参照からデカップリングするという基本的な前提に基づいて構築されています。アプリケーションのアイデンティティは、伝統的に、それらが実行されているホストのアイデンティティから派生しています。しかし、アプリケーションとホストは非常に異なるオブジェクトであり、独立して参照する必要があります。このトピックに関する詳細な議論とAppSwitchの概念的基礎については、この研究論文で説明されています。

サービスオブジェクトとそのID間のデカップリングを実現する中心的なAppSwitch構成要素は、サービス参照(短縮して参照)です。AppSwitchは、上記で概説したAPI計装メカニズムに基づいてサービス参照を実装します。サービス参照は、IP:ポートのペア(およびオプションでDNS名)、および参照によって表されるサービスと、この参照が適用されるクライアントを選択するラベルセレクタで構成されます。参照はいくつかの重要なプロパティをサポートします。(1) 参照するオブジェクトの名前とは無関係に名前を付けることができます。つまり、サービスはIPとポートでリスニングしている場合がありますが、参照を使用すると、ユーザーが選択した他のIPとポートでそのサービスにアクセスできます。これは、AppSwitchが、静的なIP構成を持つソース環境から取得された従来のアプリケーションを、ターゲットネットワーク環境に関係なく必要なIPアドレスとポートを提供することで、Kubernetes上で実行できるようにするものです。(2) ターゲットサービスの場所が変更されても、変更されません。参照は、そのラベルセレクタがサービスの新しいインスタンスに解決されるようになったため、自動的にリダイレクトされます。(3) この議論で最も重要なのは、ターゲットサービスが起動中でも参照は有効なままです。

サービス参照を通じてアクセスできるサービスの検出を容易にするために、AppSwitchは自動キュレーションされたサービスレジストリを提供します。レジストリは、AppSwitchが追跡するネットワークAPIに基づいて、クラスタ間でサービスの増減に応じて自動的に最新の状態に維持されます。レジストリの各エントリは、それぞれのサービスがバインドされているIPとポートで構成されます。それに加えて、このサービスが属するアプリケーションを示すラベルのセット、アプリケーションがソケットAPIを介してサービスを作成したときに渡したIPとポート、AppSwitchがアプリケーションに代わって基盤となるホストでサービスを実際にバインドしたIPとポートなどが含まれます。さらに、AppSwitchで作成されたアプリケーションには、アプリケーションを記述するユーザーが渡したラベルのセットと、アプリケーションを作成したユーザーやアプリケーションが実行されているホストなどを示すいくつかのデフォルトのシステムラベルが付属しています。これらのラベルはすべて、サービス参照によって保持されるラベルセレクタで表現できます。レジストリのサービスは、サービス参照を作成することでクライアントからアクセス可能になります。クライアントは、参照の名前(IP:ポート)でサービスにアクセスできるようになります。それでは、AppSwitchがターゲットサービスがまだ起動していない場合でも参照が有効な状態をどのように保証するかを見てみましょう。

ノンブロッキングリクエスト

AppSwitchはBSDソケットAPIのセマンティクスを活用して、対応するサービスが起動する際に、クライアントの視点からサービス参照が有効に見えるようにします。クライアントがまだ起動していない別のサービスへのブロッキング接続呼び出しを行うと、AppSwitchはターゲットサービスがライブになるまで待機して一定時間呼び出しをブロックします。ターゲットサービスはアプリケーションの一部であり、すぐに起動することが予想されることがわかっているため、ECONNREFUSEDなどのエラーを返すのではなくクライアントをブロックすることで、アプリケーションの起動失敗を防ぎます。サービスが時間内に起動しない場合は、Kubernetesクラッシュループなどのフレームワークレベルのメカニズムが作動するように、アプリケーションにエラーが返されます。

クライアントリクエストがノンブロッキングとしてマークされている場合、AppSwitchはEAGAINを返してアプリケーションに再試行するように通知し、諦めさせません。これもソケットAPIのセマンティクスに沿っており、起動時の競合による障害を防ぎます。AppSwitchは、BSDソケットAPIをサポートするためにアプリケーションに既に組み込まれている再試行ロジックを、依存関係の順序付けのために透過的に再利用できるようにします。

アプリケーションタイムアウト

アプリケーションが独自の内部タイマーに基づいてタイムアウトした場合はどうなりますか?正直なところ、AppSwitchは必要であればアプリケーションの時間の認識を偽装することもできますが、それは行き過ぎであり、実際には不必要です。アプリケーションは、どのくらい待つべきかを決定し、それを最もよく知っています。AppSwitchがそれに干渉するのは適切ではありません。アプリケーションのタイムアウトは保守的に長く設定されており、ターゲットサービスが時間内に起動しない場合、それは依存関係の順序付けの問題ではない可能性が高いです。隠すべきではない何か他の問題が発生しているはずです。

サイドカー依存関係のためのワイルドカードサービス参照

サービス参照を使用して、前に述べたIstioサイドカー依存関係の問題に対処できます。AppSwitchでは、サービス参照の一部として指定されたIP:ポートをワイルドカードにすることができます。つまり、サービス参照のIPアドレスは、キャプチャするIPアドレス範囲を示すネットマスクにすることができます。サービス参照のラベルセレクタがサイドカーサービスを指している場合、このサービス参照が適用されるアプリケーションのすべての発信接続は、透過的にサイドカーにリダイレクトされます。そしてもちろん、サイドカーがまだ起動中で競合が解消されている間も、サービス参照は有効なままです。

サイドカー依存関係の順序付けにサービス参照を使用すると、iptablesとそれに伴う特権の問題を必要とせずに、アプリケーションの接続をサイドカーに暗黙的にリダイレクトします。本質的には、アプリケーションがターゲットの宛先ではなくサイドカーに直接接続しているかのように機能し、サイドカーが何をすべきかを担当します。AppSwitchは、サイドカーが接続をアプリケーションに渡す前にデコードできるプロキシプロトコルを使用して、元の宛先などのメタデータを接続のデータストリームに挿入します。これらの詳細の一部についてはこちらで説明しました。これは発信接続に対応しますが、着信接続はどうでしょうか?AppSwitchで実行されているすべてのサービスとそのサイドカーでは、リモートノードから来た着信接続は、それぞれのリモートサイドカーにリダイレクトされます。したがって、着信接続については特別なことは何もする必要はありません。

概要

依存関係の順序付けは厄介な問題です。これは主に、サービス間の相互作用に関する細かいアプリケーションレベルのイベントにアクセスできないことが原因です。この問題に対処するには、通常、アプリケーションが独自の内部ロジックを実装する必要があります。しかし、AppSwitchは、アプリケーションの変更を必要とせずに、これらの内部アプリケーションイベントを計装できるようにします。その後、AppSwitchは、BSDソケットAPIのユビキタスなサポートを活用して、依存関係の順序付けの必要性を回避します。

謝辞

IBM WebSphereおよびBPM製品をKubernetesプラットフォームに最新化することにあたり、Eric Herness氏とチームの洞察とサポートに感謝いたします。また、このブログの初期ドラフトのレビューをしていただいたMandar Jog氏、Martin Taillefer氏、およびShriram Rajagopalan氏にも感謝いたします。

この投稿を共有