複数のサービスで構成されたシステムにサービスメッシュを採用するモチベーションについて説明します。
「サービスメッシュ必読ガイド - 第2版: 次世代のマイクロサービス開発」によると、サービスメッシュが解決しようとする課題は次のとおりです。
- サービスディスカバリー、ルーティング、およびアプリケーションレベル (レイヤー 7) の非機能通信要件を処理するために、言語固有の通信ライブラリを個々のサービスにコンパイルする必要がなくなります。
- 外部サービスのネットワークロケーション、セキュリティクレデンシャル、サービス品質ターゲットなど、サービス通信構成の外部化。
- 他のサービスのパッシブおよびアクティブな監視を提供します。
- 分散システム全体にポリシーの適用を分散化する。
- 可観測性にデフォルトを提供し、関連データの収集を標準化します。
- リクエストロギングの有効化
- 分散トレーシングの構成
- メトリクスの収集
非機能通信要件の処理
3つのサービス (Service A、Service B、Service C) で構成されたシステムを例にします。
図はサービスメッシュを利用しない構成です。
最初に Service A の実装を考えてみましょう。
Service A が Service B の API (/list) を呼び出す場合、URL は http://service-b/list
となります。Service C の API (/items) を呼び出す場合の URL は http://service-c/items
となります。
Service A と同様に Service B、Service C もそれぞれ依存するサービスの URL を生成します。
次にサービスメッシュ (Service Mesh) を利用する場合のトポロジーは次の図のとおりです。
proxy の代表例は Envoy Proxy です。
各サービスと proxy は同一の Pod に同梱して運用します。
ここで、先の例 Service A の実装を考えてみます。
Service A が Service B の API (/list) を呼び出す場合の URL は http://localhost/service-b/list
となります。Service C の API (/items) を呼び出す場合の URL は http://localhost/service-c/items
となります。
ここで、proxy はサービスディスカバリーにより、Service B、Service C のアドレスを理解してルーティングします (ルーティングの設定は、サービスメッシュにより自動的に反映されます)。
Service A と同様に Service B、Service C もそれぞれ依存するサービスへのルーティングを proxy が実行します。
これだけだと、URL のホスト名を localhost にかえて、サービス名をパスのプレフィックスにしただけで、メリットよりも proxy があることによる性能面での劣化の方が気になるでしょう。
さて、ここで Service C の API を REST API から gRPC を使う HTTP/2 に切り替えてパフォーマンスを向上したくなったと仮定してみてください。
サービスメッシュを利用していない最初の例の場合、Service C に依存する他のサービスは、Service C の API を呼び出すための改修が必要になります。そして、Service C の新バージョンのデプロイに合わせて、依存するすべてのサービスのデプロイも実施する必要があります。
サービスメッシュを利用している場合はどうなるでしょうか。
サービスメッシュの proxy の代表例 Envoy Proxy について「Istio in Action」の中で次のように書かれています。
Envoy can accept HTTP/1.1 connections and proxy to HTTP/2—or vice versa—or proxy incoming HTTP/2 to upstream HTTP/2 clusters. gRPC is an RPC protocol using Google Protocol Buffers (Protobuf) that lives on top of HTTP/2 and is also natively supported by Envoy.
Envoy は、HTTP/1.1 を HTTP/2 に (またはその逆にも) プロキシします、また受信した HTTP/2 を上流の HTTP/2 クラスタにプロキシすることができます。gRPC は、Google の Protocol Buffers (Protobuf) を使用する RPC プロトコルで HTTP/2 を使用しますが Envoy によってネイティブにサポートされています。
サービスメッシュの proxy を使用すると図のようになります。
他のサービスのパッシブおよびアクティブな監視
再び Service A について考えてみます。
Service A が、Service B の API を呼び出す必要があった時に Service B が停止していた場合、タイムアウトするまで処理がブロックされたくはないでしょう。
このためには、他のサービスの状態を監視し、停止しているサービスへのルーティングを行わないようにするサーキットブレーカーと呼ばれる機能によって、ブロックを防ぐことが一般的です。
サービスメッシュを利用しない場合は、各サービスでこのような機能を実装する必要がありますが、proxy はサービスの死活監視などパッシブとアクティブな監視により、このような機能が実現されます。
分散システム全体にポリシーの適用を分散化
サービスメッシュの proxy はポリシーを適用する機能が組み込まれています。
これを適用することで、ゼロトラストネットワークに必要な、認証 (JWT認証、mTLS 等)、認可 (OPA等) によるネットワークセキュリティの実行が可能です。
例えば、NIST (米国国立標準技術研究所) の「Zero Trust Architecture (NIST SP 800-207)」を参考にあてはめると次のようになります。
可観測性と関連データの収集の標準化
可観測性とは、「マイクロサービスアーキテクチャ 第2版」によれば、
システムの可観測性とは、外部出力からシステムの内部状態をどの程度把握できるかです。
サービスメッシュの proxy はアクセスログ、サービスのログ、メトリクス、分散トレーシングデータ等を収集し配信できます。
各サービスへのプロダクト固有のエージェントの組み込み等を回避できます。
最後に
サービスをコンテナ化する大きな利点は、サイズの小さいイメージを作成することで、起動が俊敏となり、ミリ秒単位でスケールイン/アウトできることにあると考えています。
このためには、コンテナで実装するサービスは、可能な限り少ない責務のドメインに特化したコードのみとし、外部化が可能な実装はサービスメッシュでも実現されているように、サイドカーやコントロールプレーンとデータプレーンの分離等のコンセプトを取り入れることが必要でしょう。
さらに、サービスメッシュは、Feature Flags (Feature Toggles) や Blue/Green デプロイ、カナリアリリース等を実現できる機能もあります。
参考