システムのサービスが数十個になると、同じネットワーク処理のコードがすべてのサービスに現れます。再試行、タイムアウト、サービス間の TLS、呼び出しごとのメトリクス、リリースのためのトラフィック分割です。サービスメッシュはこのコードをサービスから取り出してネットワークの経路に移し、1 つのチームが全員のために設定できるようにします。
この記事では、メッシュの構成、コントロールプレーンとデータプレーンそれぞれの役割、自動 mTLS の仕組み、そして多くのチームにとってメッシュが誤った選択になる理由であるコストを説明します。図は古典的なサイドカー型の設計を示しています。
データプレーンとコントロールプレーン
データプレーン は、サービス間のすべてのリクエストの経路上に置かれるプロキシの集まりです。サイドカーモデルでは、各 Pod が専用のプロキシコンテナを持ちます。アプリはローカルのプロキシと平文の HTTP で話し、ネットワーク上の処理はすべてプロキシが担います。暗号化、再試行、ロードバランシング、メトリクスです。Istio はサイドカーに Envoy を使い、Linkerd は Rust で書かれた専用の独自プロキシを使います。
コントロールプレーン は、リクエストのトラフィックに一切触れません。クラスター内のサービスとエンドポイントを監視し、ポリシーをプロキシの設定に変換して、その設定をすべてのプロキシに配布し、プロキシが自身を証明するための証明書を発行します。Istio では istiod がこれにあたります。Istio のアーキテクチャのページ を参照してください。Linkerd の アーキテクチャリファレンス には、対応する構成要素が説明されています。
自動 mTLS とワークロード ID
すべてのサービスの組み合わせの間で相互 TLS を使えることが、多くのチームにとっての決め手です。各プロキシは、ワークロードを名前で示す短命の証明書をコントロールプレーンから受け取ります。通常は Kubernetes の名前空間とサービスアカウントから導かれる SPIFFE ID で、たとえば spiffe://cluster.local/ns/shop/sa/orders のような形です。2 つのプロキシが接続すると、双方が証明書を提示し、双方が検証して、接続が暗号化されます。
重要な帰結が 2 つあります。
- ID がネットワーク上の位置に取って代わります。 ポリシーで「payments は orders のサービスアカウントからの呼び出しだけを受け付ける」と書けます。これは「このサブネットから」よりも強力です。
- ローテーションは自動です。 証明書の寿命は数時間で、更新のためにチケットを切る人はいません。
すべてがメッシュに入ったら strict モードを有効にし、平文の接続を黙って受け入れるのではなく拒否するようにします。
メッシュを通る 1 つのリクエスト
orders から payments への呼び出しを追ってみます。
- orders アプリが payments のサービス名に平文の HTTP リクエストを送ります。トラフィックのリダイレクトによって、リクエストはネットワークではなくローカルのサイドカーに送られます。
- orders のサイドカーは、コントロールプレーンが配布した設定から健全な payments のエンドポイントを探し、ロードバランシングのアルゴリズムで 1 つを選び、このルートのタイムアウトを適用します。
- payments のサイドカーへの mTLS 接続を開くか再利用し、orders の証明書を提示します。
- payments のサイドカーは証明書を検証し、認可ポリシー(orders は payments を呼び出してよいか)を確認して、localhost 経由で payments アプリにリクエストを転送します。
- 両方のサイドカーがリクエスト、そのステータス、レイテンシーを記録します。再試行可能なステータスで呼び出しが失敗した場合、orders のサイドカーは予算の範囲内で再試行できます。
どちらのアプリも、こうしたことが起きたことを知りません。それこそが狙いであり、同時にリスクでもあります。コードから見えない振る舞いは、図のような別の場所で見えるようにしておく必要があります。
メッシュの導入
段階的に導入します。コントロールプレーンをインストールし、平文も引き続き使えるように permissive モードで名前空間ごとにサービスを追加し、メトリクスを見ながら、各名前空間を strict な mTLS に切り替えます。認可ポリシーは最後に、機密データを持つサービスから追加します。
トラフィックポリシー
すべての呼び出しがプロキシを通るので、メッシュは同じルールをどこでも適用できます。
- タイムアウトと再試行: 障害時に再試行が膨れ上がらないよう、予算と組み合わせます。
- サーキットブレーカーと外れ値検出: 失敗し続けるインスタンスへのトラフィック送信を止めます。
- トラフィック分割: 重みやヘッダーで分割します。メッシュではこれでカナリアリリースを実装します。
- フォールトインジェクション: テストのために、一定割合の呼び出しに遅延やエラーを加えます。
再試行を 2 か所で行うことには注意が必要です。アプリが再試行し、サイドカーも再試行すると、1 つの失敗した呼び出しが多数の呼び出しになります。再試行の担当は 1 か所に決め、それを書き残しておきましょう。
(ほぼ)無料で手に入るオブザーバビリティ
プロキシは、アプリを変更することなく、送信元と宛先ごとのリクエスト数、エラー率、レイテンシーを報告します。トレースのスパンを開始して伝播させることもできますが、受信したリクエストのトレースヘッダーを送信するリクエストへ引き継ぐのは引き続きアプリの役目です。そうしないと、トレースはサービスごとに途切れてしまいます。
サイドカーとアンビエントモード
サイドカーには実際のコストがあります。すべての Pod にあるプロキシが CPU とメモリーを使い、ホップごとにレイテンシーを加え、Pod の起動やアップグレードを複雑にします。Istio の アンビエントモード は Pod ごとのサイドカーをなくします。ztunnel と呼ばれるノードごとのプロキシが mTLS と L4 のポリシーを担い、オプションの waypoint プロキシが、必要なサービスに対してだけ L7 の機能を提供します。その代わりに運用モデルが異なり、本番での実績もサイドカーより浅くなります。
アーキテクチャ図
ダイアグラムの内容
- Ingress gateway
- orders pod
- Sidecar proxy
- orders app
- payments pod
- payments app
- inventory pod
- inventory app
- Control plane
- Metrics and traces
- mTLS
- localhost
- config + certificates
図の読み方は次のとおりです。
- 左: イングレスゲートウェイが外部からのトラフィックを受け取り、orders Pod のサイドカーへの mTLS 接続を開きます。
- 中央: 各 Pod の中では、サイドカーが localhost 経由でアプリに転送します。orders のサイドカーは payments と inventory のサイドカーを mTLS で呼び出します。アプリが証明書を扱うことはありません。
- 下: コントロールプレーンがプロキシに設定と証明書を配布します(破線のエッジ)。リクエストの経路には入っていません。
- 右: サイドカーがメトリクスとトレースを報告します(点線)。Markdown ブロックは payments のポリシーを示しています。呼び出してよい相手、タイムアウトと再試行、外れ値の除外、strict な mTLS です。
メッシュを使うべきでない場合
- サービスが数個しかない場合や、モノリスに少数の衛星サービスがある程度の場合。ライブラリや API ゲートウェイで、わずかなコストで必要を満たせます。
- 責任を持つチームがいない場合。メッシュは、アップグレード、デバッグのスキル、オンコールの知識が必要なインフラです。
- 主な目的が暗号化だけの場合。多くのクラウドや CNI プラグインは、完全なメッシュなしで Pod 間のトラフィックを暗号化できます。
メッシュの図に描くべきもの
- 両方のプレーン: はっきり分け、コントロールプレーンはリクエストの経路の外に置きます。
- TLS の始点と終点: プロキシからプロキシまで、そして localhost 上は平文の HTTP です。
- ID: 少なくともアクセスポリシーのあるサービスについて描きます。
- 振る舞いを変えるポリシー: タイムアウト、再試行、サーキットブレーカー。
メッシュが動くクラスターについては Kubernetes のアーキテクチャ図 で、ID に基づくポリシーが重要な理由は ゼロトラストのアーキテクチャ図 で、サービスそのものは マイクロサービスのアーキテクチャ図 で扱っています。
Archboard で作る
- Google または GitHub でサインインし、図を作成します。
- C4 モデルパックから、イングレスゲートウェイ用に コンテナ を追加します。
- 各 Pod について、基本パックから フレーム を追加し、その中にサイドカー用の コンポーネント とアプリ用の コンテナ を置きます。
- コントロールプレーン用の コンテナ と、DevOps パックの メトリクス を追加します。
- サイドカー間に mTLS のエッジ、コントロールプレーンから破線のエッジ、テレメトリーの点線のエッジを描き、1 つのサービスのポリシーを書いた基本パックの Markdown ブロックを追加します。
この例で使うダイアグラムノードは 13 個で、Free プランの範囲に収まります。
よくある質問
サービスメッシュと API ゲートウェイの違いは何ですか? API ゲートウェイは、外部からシステムに入ってくるトラフィックを管理します。サービスメッシュは、システム内部のサービス間のトラフィックを管理します。多くのシステムは両方を使います。
サービスメッシュはレイテンシーを増やしますか? はい。プロキシのホップごとに少し増えます。通常はホップあたり数ミリ秒を大きく下回りますが、プロキシの CPU とメモリーも必要です。自分のトラフィックで測定しましょう。
メッシュのためにアプリケーションのコードを変更する必要はありますか? ほとんどありません。例外はトレーシングです。サービスをまたいでトレースをつなげるには、アプリがトレースヘッダーを引き継ぐ必要があります。
アンビエントモードとは何ですか? サイドカーを使わない Istio のデータプレーンです。ノードごとのプロキシが mTLS と L4 を担い、オプションの waypoint プロキシが必要な場所で L7 の機能を提供します。
メッシュは誰が担当すべきですか? プラットフォームチームです。サービスチームはポリシーを通じてその機能を使いますが、誰かがアップグレード、監視、デバッグを担う必要があります。