好きな技術を布教したい 😗

〜 沼のまわりに餌をまく 〜

【Istio⛵️】Istioによって抽象化されるEnvoyのHTTPSリクエスト処理の仕組み


この記事から得られる知識

この記事を読むと、以下を "完全に理解" できます✌️

  • Istioのサイドカーメッシュを題材にしたEnvoyの設定の抽象化について
  • 様々なサービスメッシュツール (特に、Istio、Consul、Cilium、など) でも流用できるEnvoyの知識について



01. はじめに

どうも、俺 (REMIX) feat. Istioニキ a.k.a いすてぃ男です。

istio-icon


Istioは、Envoyを使用したサービスメッシュを実装します。

IstioがKubernetesリソースやIstioカスタムリソースに基づいてEnvoyの設定を抽象化してくれるため、開発者はEnvoyをより簡単に設定できます。

istio_envoy


Envoyの設定の抽象化は、Envoyを使用したサービスメッシュ (例:Istioサイドカーメッシュ/アンビエントメッシュ、Consul、Istioから得られた学びを土台に登場したCiliumサイドカーフリーメッシュ、など) に共通しています。

つまり、次々に登場するEnvoyによるサービスメッシュツールに振り回されないようにするためには、ツールがどのようにEnvoyを抽象化するのかを理解しておく必要があります。

そこで今回は、IstioサイドカーメッシュがEnvoyのHTTPSリクエストの処理をどのように抽象化するのかを解説します。

また、抽象化されたEnvoyがHTTPSリクエストを処理する仕組みも一緒に解説します。

これらの知識は、様々なサービスメッシュツールで流用できるはずです。

それでは、もりもり布教していきます😗

記事中のこのボックスは、補足情報を記載しています。

飛ばしていただいても大丈夫ですが、読んでもらえるとより理解が深まるはずです👍


02. 様々なリソースによるEnvoy設定の抽象化

まずは、どのようなリソースがHTTPSリクエストの処理に関係しているのかを、HTTPSリクエストの方向に分けて解説していきます。

istio-proxyコンテナやEnvoyについては、次章以降で解説します。


サービスメッシュ外からのHTTPS

サービスメッシュ外から内にHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。

istio_envoy_istio_resource_ingress_mermaid.png


各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. クライアントは、サービスメッシュ外からL7ロードバランサーHTTPSリクエストを送信します。
  2. L7ロードバランサーは、Istio IngressGateway PodにHTTPSリクエストを送信します。
  3. Istio IngressGateway Podは、宛先Podとの間で相互TLS認証を実施します。
  4. Istio IngressGateway Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) に応じて、HTTPSリクエストを宛先PodにL7ロードバランシングします。

istio_envoy_istio_resource_ingress


マイクロサービス間のHTTPS

サービスメッシュ内のPodから別のPodにHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。

istio_envoy_istio_resource_service-to-service_mermaid.png


各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. 送信元Podは、宛先Podとの間で相互TLS認証を実施します。
  2. 送信元Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) の設定に応じて、HTTPSリクエストを宛先PodにL7ロードバランシングします。

istio_envoy_istio_resource_service-to-service


サービスメッシュ外へのHTTPS

サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) にHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。

複数のVirtualServiceとDestinationが登場するため、これらには便宜上 XY をつけています。

istio_envoy_istio_resource_egress_mermaid.png


各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. 送信元Podは、HTTPSリクエストの宛先がServiceEntryでエントリ済みか否かの設定に応じて、HTTPSリクエストの宛先を切り替えます。
    • 宛先がエントリ済みであれば、送信元PodはHTTPSリクエストの宛先にIstio EgressGateway Podを選択します。
    • 宛先が未エントリであれば、送信元PodはHTTPSリクエストの宛先に外のシステムを選択します。
  2. 送信元Podは、Istio EgressGateway Podとの間で相互TLS認証を実施します。
  3. (1) で宛先がエントリ済であったとします。送信元Podは、HTTPSリクエストの向き先をIstio EgressGateway Podに変更します。
  4. 送信元Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) の設定に応じて、Istio EgressGateway PodにL7ロードバランシングします。
  5. Istio EgressGateway Podは、HTTPSリクエストをエントリ済システムにL7ロードバランシングします。

istio_envoy_istio_resource_egress


▶︎ Istio EgressGatewayの必要性について

実は、Istio EgressGatewayを使用しなくとも、ServiceEntryだけでサービスメッシュ外の登録済みシステムにHTTPSリクエストを送信できます。

しかし、Istio EgressGatewayを使わないと、マイクロサービスからistio-proxyコンテナを経由せずに外部システムに直接HTTPSリクエストを送信できるようになってしまい、システムの安全性が低くなります。


03. istio-proxyコンテナによるHTTPS処理

前章では、KubernetesリソースやIstioカスタムリソースによって抽象化されたEnvoyまで言及しませんでした。

本章では、解説をもう少し具体化します。

Istioは、Envoyプロセスを持つistio-proxyコンテナを作成します。

このistio-proxyコンテナを使用してどのようにHTTPSリクエストを処理しているのかを、HTTPSリクエストの方向に分けて解説します。

Envoyの設定については、次章以降で解説します。


Istioコントロールプレーンの仕組み

Envoyの設定を抽象化する責務を担うのは、Istioコントロールプレーン (discoveryコンテナ) です。

Istioコントロールプレーンは異なる責務を担う複数のレイヤーから構成されています。

レイヤー名 責務
Config ingestion
レイヤー
kube-apiserverからKubernetesリソースやIstioカスタムリソースの設定を取得します。
Istioの初期から名前は変わっていません。
Config translation
レイヤー
リソースの設定をEnvoy設定に変換します。
Istioの初期ではConfig Data Modelレイヤーという名前で、執筆時点 (2024/01/16) で名前が変わっています。
Config serving
レイヤー
Envoyの設定や証明書をPod内のistio-proxyコンテナに配布します。
Istioの初期では、Proxy Servingレイヤーという名前で、執筆時点 (2024/01/16) で名前が変わっています。


図中の番号に沿って、Istioコントロールプレーンの仕組みを解説します。

  1. Config ingestionレイヤーにて、 Istioコントロールプレーンはkube-apiserverにHTTPSリクエストを送信します。ここで、KubernetesリソースやIstioカスタムリソースの設定を取得します。
  2. Config translationレイヤーにて、取得したリソースの設定をEnvoyの設定に変換します。
  3. Config servingレイヤーにて、Envoyの設定や証明書をPod内のistio-proxyコンテナに配布します。双方向ストリーミングRPCのため、istio-proxyコンテナがConfig servingレイヤーにリクエストを送信し、これらを取得することもあります。

istio_envoy_istio-proxy_resource_control-plane


▶︎ Config servingレイヤーにあるXDS-APIについて

Config servingレイヤーには、XDS-APIがあります。

このXDS-APIは、Envoyの設定に関するエンドポイント (LDS-API、RDS-APICDS-API、EDS-API、ADS-API、など) や、証明書配布のエンドポイント (例:SDS-API) を持ちます。

istio_sidecar-mesh_architecture

以下の記事で解説していますため、もし気になる方はよろしくどうぞ🙇🏻‍


▶︎ Istioカスタムリソースのコントローラーについて

Istioコントロールプレーンは、前述の責務以外にカスタムコントローラーとしての責務も担います。

以前は、IstioOperatorがカスタムコントローラーの責務を担っていましたが、執筆時点 (2024/01/16) ではIstioOperatorが非推奨となりました。

IstioOperatorの代わりに、Istioコントロールプレーンがこれを担うようになりました👍🏻


サービスメッシュ外からのHTTPS

サービスメッシュ外から内にHTTPSリクエストを送信する場合のistio-proxyコンテナです。

各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内のistio-proxyコンテナに提供します。
  2. クライアントは、サービスメッシュ外からL7ロードバランサーHTTPSリクエストを送信します。
  3. L7ロードバランサーは、Istio IngressGateway PodにHTTPSリクエストを送信します。
  4. Istio IngressGateway Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナに送信します (リダイレクトは不要)。
  5. Istio IngressGateway Pod内のistio-proxyコンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。
  6. Istio IngressGateway Pod内のistio-proxyコンテナは、HTTPSリクエストを宛先PodにL7ロードバランシングします。
  7. 宛先Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナにリダイレクトします。
  8. 宛先Pod内のistio-proxyコンテナは、HTTPSリクエストを宛先マイクロサービスに送信します。

istio_envoy_istio_ingress


▶︎ Pod内のiptablesについて

Pod内のiptablesは、リクエストが必ずistio-proxyコンテナを経由するように、istio-proxyコンテナにリクエストをリダイレクトします。

iptablesのルールを書き換えるのはistio-initコンテナです。

Istioは、istio-proxyコンテナと同じタイミングで、istio-initコンテナをPodにインジェクションします (Istio IngressGatewayとIstio EgressGatewayのPodは除きます)。

istio_istio-init
画像引用元:SoByte


istio-initコンテナは、istio-iptablesコマンドを実行し、iptablesのルールを書き換えます。

また、istio-initコンテナはルールを書き換えた後に終了するため、Podの起動後にPod内に残りません👍🏻
$ istio-iptables \
    -p 15001 \
    -z 15006 \
    -u 1337 \
    -m REDIRECT \
    -i * \
    -x \
    -b * \
    -d 15090,15020


▶︎ Istio IngressGateway Pod内のiptablesについて

Istio IngressGateway Podでは、マイクロサービスがないため、istio-proxyコンテナにリクエストをリダイレクトする必要がありません。

そのため、Istioはiptablesのルールを書き換えるistio-initコンテナをIstio IngressGateway Podにインジェクションしません。

つまり、Istio IngressGateway Pod内のiptablesのルールはデフォルトのままになっています👍🏻


マイクロサービス間のHTTPS

サービスメッシュ内のPodから別のPodにHTTPSリクエストを送信する場合のistio-proxyコンテナです。

各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内のistio-proxyコンテナに提供します。
  2. 送信元Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナにリダイレクトします。
  3. 送信元Pod内のistio-proxyコンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。
  4. 送信元Pod内のistio-proxyコンテナは、HTTPSリクエストを宛先PodにL7ロードバランシングします。
  5. 宛先Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナにリダイレクトします。
  6. 宛先Pod内のistio-proxyコンテナは、HTTPSリクエストを宛先マイクロサービスに送信します。

istio_envoy_istio_service-to-service


サービスメッシュ外へのHTTPS

サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) にHTTPSリクエストを送信する場合のistio-proxyコンテナです。

各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。

図中の番号に沿って、通信の仕組みを解説します。

  1. Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内のistio-proxyコンテナに提供します。
  2. 送信元Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナにリダイレクトします。
  3. 送信元Pod内のistio-proxyコンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。この時、ServiceEntryで宛先がエントリ済みか否かに応じて、HTTPSリクエストの宛先を切り替えます。
    • 宛先がエントリ済みであれば、istio-proxyコンテナはHTTPSリクエストの宛先にIstio EgressGateway Podを選択します。
    • 宛先が未エントリであれば、istio-proxyコンテナはHTTPSリクエストの宛先に外のシステムを選択します。
  4. ここでは、宛先がエントリ済であったとします。送信元Pod内のistio-proxyコンテナは、HTTPSリクエストをIstio EgressGateway PodにL7ロードバランシングします。
  5. Istio EgressGateway Pod内のiptablesは、HTTPSリクエストをistio-proxyコンテナに送信します (リダイレクトは不要)。
  6. Istio EgressGateway Pod内のistio-proxyコンテナは、HTTPSリクエストをエントリ済システムにL7ロードバランシングします。

istio_envoy_istio_egress

▶︎ Istio EgressGateway Pod内のiptablesについて

Istio EgressGateway Podでは、マイクロサービスがないため、istio-proxyコンテナにリクエストをリダイレクトする必要がありません。

そのため、Istioはiptablesのルールを書き換えるistio-initコンテナをIstio EgressGateway Podにインジェクションしません。

つまり、Istio EgressGateway Pod内のiptablesのルールはデフォルトのままになっています👍🏻


04. EnvoyによるHTTPS処理

前章では、istio-proxyコンテナ内のEnvoyの設定まで、言及しませんでした。

本章では、もっと具体化します。

EnvoyがHTTPSリクエストを処理する仕組みを解説します。


Envoyの設定の種類

HTTPSリクエストを処理する場合、Envoyの設定が以下の順で紐付き、HTTPSリクエストを送信元から宛先まで届けます。

istio_envoy_envoy-flow_mermaid_http.png


各処理がどのような責務を担っているのかをもう少し詳しく見てみましょう。

図中の番号に沿って、EnvoyがHTTPSリクエストを処理する仕組みを解説します。

  1. 送信元からのHTTPSリクエストの宛先ポートで、リスナーを絞り込みます。
  2. 通信の種類 (例:HTTP、HTTPSTCPUDPUnixドメインソケット、など) に応じてフィルターを選び、各フィルターがパケットのヘッダーを処理します。もしHTTPSであれば、送信元との間でTLS接続を確立し、パケットのL7のアプリケーションデータを復号化します。
  3. フィルターを使用して、HTTPSリクエストの宛先ポートで、ルートを絞り込みます。
  4. フィルターを使用して、HTTPSリクエストの宛先ホストやパスで、クラスターを絞り込みます。
  5. 設定した負荷分散方式 (例:ラウンドロビン、など) に応じて、クラスター配下のエンドポイントを選びます。
  6. 宛先との間でTLS接続を確立し、パケットのL7のアプリケーションデータを暗号化します。そして、エンドポイントにL7ロードバランシングします。

istio_envoy_envoy-flow


TCPリクエストを処理する場合について

HTTPリクエストを処理する場合、フィルターに紐づくのはルートですが、TCPリクエストの場合はそうではありません。

TCPリクエストを処理する場合、フィルターにクラスターが紐づきます👍🏻

istio_envoy_envoy-flow_mermaid_tcp


フィルター

フィルターの一覧

Envoyのフィルターは、Envoyの機能を拡張するための設定です。

HTTPSリクエストを処理するためには、リスナーフィルター、ネットワークフィルター、HTTPフィルター、といったフィルターが必要になります。

全ては解説しきれないため、HTTPSリクエストを処理するための代表的なフィルターをいくつか抜粋しました。

ただ、 Istioはこれらのフィルターをデフォルトで有効にしてくれている ため、開発者がEnvoyのフィルターを設定する場面は少ないです。

逆をいえば、Istioを介さずにEnvoyをそのまま使用する場合、開発者がEnvoyのフィルターを自前で設定する必要があります👍🏻

フィルターの種類 HTTPSリクエストの処理に必要なフィルター
(一部抜粋)
説明
リスナー
フィルター
Original Destination istio-proxyコンテナへのリダイレクト前の宛先情報をEnvoyが取得できるようにします。
Pod内のiptablesHTTPSリクエストをistio-proxyコンテナにリダイレクトすると、HTTPSリクエストの宛先がistio-proxyコンテナに変わってしまいます。
ただし、iptablesはリダイレクト前の宛先をカーネル上のSO_ORIGINAL_DSTという定数に格納してくれています。
Envoyは、カーネル上のSO_ORIGINAL_DSTから本来の宛先を取得し、プロキシします。
HTTP Inspector EnvoyがHTTPを検知できるようにします。
TLS Inspector EnvoyがTLSを検知できるようにします。
TLSを検知した場合、EnvoyはTLSに関する処理を実行します。
例えば、DownstreamTlsContextは、リスナーフィルター直後に、送信元との間でTLS接続を確立し、パケットのL7のアプリケーションデータを復号化します。
また、UpstreamTlsContextは、クラスターの処理時に、宛先との間でTLS接続を確立し、L7のアプリケーションデータを暗号化します。
ネットワーク
フィルター
HTTP connection manager Envoyが、L7のアプリケーションデータを読み取り、また後続のHTTPフィルターを制御できるようにします。
HTTP
フィルター
Router Envoyがポート番号でルート、ホストやパスでクラスターを絞り込めるようにします。
gRPC-Web EnvoyがHTTP/1.1で受信したHTTPSリクエストをHTTP/2に変換し、gRPCサーバーにプロキシできるようにします。


▶︎ Istioがデフォルトで有効にするEnvoyの設定について

istio-proxyコンテナは、イメージのビルド時に、あらかじめ用意しておいたEnvoyの設定ファイルを組み込みます。

そのため、istio-proxyコンテナ内のEnvoyは、多くの設定をデフォルトで有効にできます。

Istioを利用する開発者が、EnvoyがHTTPSリクエストを処理するために必要なフィルターを有効にしなくてよいのも、Istioのおかげです。

Istioほんまにありがとな🙏🙏🙏


フィルターチェーンの仕組み

Envoyは、複数のフィルターからなるフィルターチェーンを実行し、HTTPSを処理します。

図中の番号に沿って、Envoyのフィルターチェーンの仕組みを解説します。

各フィルターの機能は、前述したフィルターの一覧を参考にしてください🙇🏻

  1. リスナーフィルター (Original Destination、HTTP Inspector、TLS Inspector、など) を実行します。
  2. (1) でTLS InspectorがTLSを検知した場合、DownstreamTlsContextで宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
  3. ネットワークフィルター (HTTP connection manager、など) を実行します。
  4. HTTPフィルター (Router、gRPC-Web、など) を実行します。

istio_envoy_envoy-filter


TCPリクエストを処理する場合について

HTTPフィルターはHTTP/HTTPSリクエストを処理する場合にのみ使用します。

それ以外の通信の種類 (例:TCPUDPUnixドメインソケット、など) の場合は、HTTPフィルターを使用しません。

例えば、TCPリクエストの場合、ネットワークフィルターのTCP proxyフィルターを使用します👍🏻


05. リソースの設定からEnvoy設定への翻訳

いよいよです🔥

Istioが各リソースをいずれのEnvoyの設定に翻訳しているのかを解説します。

表で対応関係の一覧を示した後、istio-proxyコンテナ内のEnvoyに当てはめました。


各リソースとEnvoyの設定の関係一覧

Istioコントロールプレーンは、KubernetesリソースやIstioカスタムリソースの設定をEnvoyの設定に翻訳し、処理の流れに当てはめます。

以下の通り、各リソースがいずれのEnvoyの設定を抽象化するのかを整理しました。

リソースによっては、Envoyの複数の設定を抽象化します。

なお、Istioの用意したEnvoyのフィルターのデフォルト値を変更するユースケースが少ないため、これを抽象化するEnvoyFilterについては言及しません。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
Destination
Rule
Service
Entry
Peer
Authentication
リスナー
ルート
クラスタ
エンドポイント


サービスメッシュ外からのHTTPS

Envoyの設定を抽象化するリソース一覧

サービスメッシュ外からのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。

Gatewayは、Istio IngressGatewayの一部として使用します。

ServiceEntryは、使用しないリソースのため、×としています。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
Destination
Rule
Service
Entry
Peer
Authentication
リスナー ×
ルート ×
クラスタ ×
エンドポイント ×


リソースとEnvoyの設定の対応関係

送信元または宛先Envoyに分けると、各リソースは以下のようにEnvoyの設定を抽象化します。

話を簡単にするために、送信元と宛先は同じNamespaceにあると仮定します。

送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、同じリソースが抽象化します。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
Destination
Rule
Peer
Authentication
送信元 リスナー
ルート
クラスタ
エンドポイント
宛先 リスナー
ルート
クラスタ
エンドポイント


▶︎ 送信元と宛先のNamespaceについて

今回、送信元のIstio IngressGatewayと宛先は同じNamespaceにあると仮定しています。

しかし、もししっかり設計するのであれば、Istio IngressGatewayは専用のNamespace (例:istio-ingress) においた方が良いです。

マイクロサービスとは異なるNamespaceにIstio IngressGatewayを置くことで、Istio IngressGatewayをアップグレードしやすくなったり、他から障害の影響を受けにくくなります🙆🏻‍♂️

istio-proxyコンテナ内のEnvoyに当てはめる

この表を、HTTPSリクエストの仕組みの中に当てはめると、以下になります。

送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、同じリソースが抽象化します。


引用した前述の解説のイメージが掴めるかと思います。

送信元または宛先Envoyでほとんど同じリソースが登場しますが、 Gatewayは送信元Envoyだけで登場します。

istio_envoy_envoy-flow_resource_ingress


リソースの種類だけに着目すると、以下になります。

Gatewayが送信元Envoyだけで登場することがわかりやすくなりました。

istio_envoy_istio-proxy_resource_ingress


マイクロサービス間のHTTPS

Envoyの設定を抽象化するリソース一覧

サービスメッシュ内のPodから別のPodへのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。

GatewayとServiceEntryは、使用しないリソースのため、×としています。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
Destination
Rule
Service
Entry
Peer
Authentication
リスナー × ×
ルート × ×
クラスタ × ×
エンドポイント × ×


リソースとEnvoyの設定の対応関係

送信元または宛先Envoyに分けると、各リソースは以下のようにEnvoyの設定を抽象化します。

話を簡単にするために、送信元と宛先は同じNamespaceにあると仮定します。

送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、同じリソースが抽象化します。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Virtual
Service
Destination
Rule
Peer
Authentication
送信元 リスナー
ルート
クラスタ
エンドポイント
宛先 リスナー
ルート
クラスタ
エンドポイント

istio-proxyコンテナ内のEnvoyに当てはめる

この表を、HTTPSリクエストの仕組みの中に当てはめると、以下になります。

送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、同じリソースが抽象化します。


引用した前述の解説のイメージが掴めるかと思います。

送信元または宛先Envoyで、同じリソースが登場します。

istio_envoy_envoy-flow_resource_service-to-service


リソースの種類だけに着目すると、以下になります。

送信元または宛先Envoyで同じリソースが登場することがわかりやすくなりました。

istio_envoy_istio-proxy_resource_service-to-service


サービスメッシュ外へのHTTPS

Envoyの設定を抽象化するリソース一覧

サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) へのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。

Gatewayは、Istio EgressGatewayの一部として使用します。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
Destination
Rule
Service
Entry
Peer
Authentication
リスナー
ルート
クラスタ
エンドポイント

リソースとEnvoyの設定の対応関係

送信元または宛先Envoyに分けると、各リソースは以下のようにEnvoyの設定を抽象化します。

話を簡単にするために、送信元と宛先は同じNamespaceにあると仮定します。

他の場合とは異なり、送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、異なるリソースが抽象化します。

PeerAuthenticationだけは、話を簡単にするために送信元と宛先が同じNamespaceであると仮定しているので、同じリソースが抽象化します。

送信元Envoyの設定の抽象化で登場するリソースが宛先では登場せず、逆も然りです。

Kubernetes ☸️
リソース
Istio ⛵️
カスタムリソース
Envoyの設定 Service Endpoints Gateway Virtual
Service
X

Y
Destination
Rule
X

Y
Service
Entry
Peer
Authentication
送信元 リスナー
ルート
クラスタ
エンドポイント
宛先 リスナー
ルート
クラスタ
エンドポイント


▶︎ 送信元と宛先のNamespaceについて

今回、送信元と宛先のIstio EgressGatewayは同じNamespaceにあると仮定しています。

しかし、もししっかり設計するのであれば、Istio EgressGatewayは専用のNamespace (例:istio-egress) においた方が良いです。

マイクロサービスとは異なるNamespaceにIstio EgressGatewayを置くことで、Istio EgressGatewayをアップグレードしやすくなったり、他から障害の影響を受けにくくなります🙆🏻‍♂️

istio-proxyコンテナ内のEnvoyに当てはめる

この表を、HTTPSリクエストの仕組みの中に当てはめると、以下になります。

送信元EnvoyでHTTPSリクエストの宛先を決める設定、または宛先EnvoyでHTTPSリクエストを受信する設定を、異なるリソースが抽象化します。

PeerAuthenticationだけは、話を簡単にするために送信元と宛先が同じNamespaceであると仮定しているので、同じリソースが抽象化します。


引用した前述の解説のイメージが掴めるかと思います。

送信元または宛先Envoyで同じリソースが登場しません 。

istio_envoy_envoy-flow_resource_egress


リソースの種類だけに着目すると、以下になります。

送信元または宛先Envoyで同じリソースが登場しないことがわかりやすくなりました。

istio_envoy_istio-proxy_resource_egress


06. 翻訳されたEnvoy設定値を見てみる

前章では、Envoyの具体的な設定値まで、言及しませんでした。

本章では、さらに具体化します。

各リソースの設定の翻訳によって、Envoyの具体的にどのような設定値になっているのかを解説します。


Envoyの現在の設定を出力する

Envoyは、現在の設定を確認するためのエンドポイント (/config_dump) を公開しています。

これにHTTPSリクエストを送信し、具体的な設定値を出力してみましょう👍🏻

リスナーを出力する

/config_dumpのクエリストリングにresource={dynamic_listeners}をつけると、Envoyのリスナーを出力できます。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://localhost:15000/config_dump?resource={dynamic_listeners}" | yq -P


▶ 宛先情報を見やすくするyqコマンドについて

Envoyは、JSON形式で設定を出力します。

JSON形式だと見にくいため、yqコマンドでYAMLに変換すると見やすくなります👍

ルートを出力する

/config_dumpのクエリストリングにresource={dynamic_route_configs}をつけると、Envoyのルートを出力できます。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://localhost:15000/config_dump?resource={dynamic_route_configs}" | yq -P

クラスターを出力する

/config_dumpのクエリストリングにresource={dynamic_active_clusters}をつけると、Envoyのクラスターを出力できます。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://localhost:15000/config_dump?resource={dynamic_active_clusters}" | yq -P

エンドポイントを出力する

/config_dumpのクエリストリングにinclude_edsをつけると、Envoyのエンドポイントを出力できます。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://localhost:15000/config_dump?include_eds" | yq -P

証明書を出力する

/config_dumpのクエリストリングにresource={dynamic_active_secrets}をつけると、証明書を出力できます。

$ kubectl exec \
    -it foo-pod \
    -n foo-namespace \
    -c istio-proxy \
    -- bash -c "curl http://localhost:15000/config_dump?resource={dynamic_active_secrets}" | yq -P


サービスメッシュ外からのHTTPS

ここでは、istio-proxyコンテナはHTTPSリクエストを処理するとします。

図中の番号に沿って、通信の仕組みを解説します。

istio_envoy_envoy-flow_ingress

送信元Pod側のistio-proxyコンテナ

  1. 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:50000) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50000) 。
  2. HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
  3. HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:50000) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:50000) 。
  4. HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:foo-service.foo-namespace.svc.cluster.local) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートやホストで管理しています (例:outbound|50010|foo-service.foo-namespace.svc.cluster.local) 。
  5. 設定した負荷分散方式 (例:ラウンドロビン、など) に応じて、Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:<PodのIPアドレス>:50000) 。
  6. 宛先との間でTLS接続を確立し、パケットのL7のアプリケーションデータを暗号化します。そして、HTTPSリクエストを宛先PodにL7ロードバランシングします。

宛先Pod側のistio-proxyコンテナ

  • L7ロードバランシングされたHTTPSリクエストの宛先ポート (例:50000) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50000)
  • HTTPSリクエストを処理するための各種フィルターを選びます。
  • HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:50000) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:inbound|50000||) 。
  • HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:example.com) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートで管理しています (例:inbound|50000||)
  • エンドポイントを選びます。Envoyは、エンドポイントをローカルホストや宛先ポートで管理しています (例:127.0.0.6:50000) 。
  • ローカルホストにHTTPSリクエストを送信します。結果的に、宛先マイクロサービスにHTTPSリクエストが届きます。


▶︎ istio-proxyコンテナのプロキシ先のIPアドレスについて

istio-proxyコンテナは、ローカルホストを127.0.0.6とし、HTTPSリクエストをマイクロサービスに送信します。

これは、127.0.0.1を指定してしまうと、istio-proxyコンテナからマイクロサービスへの通信がiptables上でループしてしまうためです。

istio-proxyコンテナからマイクロサービスへの通信では、正しくはiptables上でISTIO_OUTPUTからPOSTROUTINGに通信を渡します。

一方で、もしローカルホストが127.0.0.1であると、ISTIO_OUTPUTからISTIO_IN_REDIRECTに通信を渡すことになり、istio-proxyコンテナに再びリダイレクトしてしまいます。

hatappi1225さんの解説が鬼わかりやすかったです🙏🙏🙏
127-0-0-1_iptables_loop
画像引用元:mercari engineering


マイクロサービス間のHTTPS

ここでは、istio-proxyコンテナはHTTPSリクエストを処理するとします。

図中の番号に沿って、通信の仕組みを解説します。

istio_envoy_envoy-flow_service-to-service

送信元Pod側のistio-proxyコンテナ

  1. 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:50010) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50010) 。
  2. HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
  3. HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:50010) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:50010) 。
  4. HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:foo-service.foo-namespace.svc.cluster.local) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートやホストで管理しています (例:outbound|50010|foo-service.foo-namespace.svc.cluster.local) 。
  5. 設定した負荷分散方式 (例:ラウンドロビン、など) に応じて、Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:<PodのIPアドレス>:50010) 。
  6. 宛先との間でTLS接続を確立し、パケットのL7のアプリケーションデータを暗号化します。そして、HTTPSリクエストを宛先PodにL7ロードバランシングします。

宛先Pod側のistio-proxyコンテナ

  • L7ロードバランシングされたHTTPSリクエストの宛先ポート (例:50010) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50010)
  • HTTPSリクエストを処理するための各種フィルターを選びます。
  • HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:50010) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:inbound|50010||) 。
  • HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:example.com) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートで管理しています (例:inbound|50010||)
  • エンドポイントを選びます。Envoyは、エンドポイントをローカルホストや宛先ポートで管理しています (例:127.0.0.6:50010) 。
  • ローカルホストにHTTPSリクエストを送信します。結果的に、宛先マイクロサービスにHTTPSリクエストが届きます。


サービスメッシュ外へのHTTPS

ここでは、istio-proxyコンテナはHTTPSリクエストを処理するとします。

図中の番号に沿って、通信の仕組みを解説します。

istio_envoy_envoy-flow_egress

送信元Pod側のistio-proxyコンテナ

  1. 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:443) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_443) 。
  2. HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
  3. HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:443) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:443) 。
  4. HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:istio-egressgateway-service.foo-namespace.svc.cluster.local) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターをIstio EgressGateway 宛先ポートやホストで管理しています (例:outbound|443|istio-egressgateway-service.foo-namespace.svc.cluster.local) 。
  5. 設定した負荷分散方式 (例:ラウンドロビン、など) に応じて、Istio EgressGateway Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:<PodのIPアドレス>:443) 。
  6. 宛先との間でTLS接続を確立し、パケットのL7のアプリケーションデータを暗号化します。そして、Istio EgressGateway PodにL7ロードバランシングします。

宛先Pod (Istio EgressGateway Pod) 側のistio-proxyコンテナ

  • L7ロードバランシングされたHTTPSリクエストの宛先ポート (例:443) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_443)
  • HTTPSリクエストを処理するための各種フィルターを選びます。
  • HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:443) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:inbound|50010||) 。
  • HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:external.com) やパス (例:/) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートやホストで管理しています (例:outbound|443|external.com) 。
  • エンドポイントを選びます。Envoyは、エンドポイントをエントリ済システムのIPアドレスや宛先ポートで管理しています (例:<エントリ済システムのIPアドレス>:50010) 。エントリ済システムのIPアドレスは、開発者が設定する必要はなく、EnvoyがDNSから動的に取得します。
  • エントリ済システムにHTTPSリクエストを送信します。


07. おわりに

IstioサイドカーメッシュがEnvoyのHTTPSリクエストの処理をどのように抽象化するのか、またEnvoyがどのようにHTTPSリクエストを処理するのかを解説しました。

次々とサービスメッシュツールが登場したとしても、それがEnvoyを使用したサービスメッシュである限り、最終的にはEnvoyの設定値に行き着きます。

そのため、抽象化されたEnvoyがどのように通信を扱うのかを一度でも理解すれば、様々なサービスメッシュツールで知識を流用できると思います。

Istioはもちろん、他のEnvoyによるサービスメッシュツール (Consul、Cilium、など) を使っている方の参考にもなれば幸いです👍🏻


謝辞

今回、Kubernetesのネットワークを調査するにあたり、以下の方に知見をご教授いただきました。

この場で感謝申し上げます🙇🏻‍


記事関連のおすすめ書籍