この記事から得られる知識
この記事を読むと、以下を "完全に理解" できます✌️
- Istioのサイドカーメッシュを題材にしたEnvoyの設定の抽象化について
- 様々なサービスメッシュツール (特に、Istio、Consul、Ciliumなど) でも流用できるEnvoyの知識について
- この記事から得られる知識
- 01. はじめに
- 02. 様々なリソースによるEnvoy設定の抽象化
- 03. istio-proxyコンテナによるHTTPS処理
- 04. EnvoyによるHTTPS処理
- 05. リソースの設定からEnvoy設定への翻訳
- 06. 翻訳されたEnvoy設定値を見てみる
- 07. おわりに
- 謝辞
- 記事関連のおすすめ書籍
01. はじめに
どうも、俺 (REMIX) feat. Istioニキ a.k.a. いすてぃ男です。
Istioは、Envoyを使用したサービスメッシュを実装します。
IstioがKubernetesリソースやIstioカスタムリソースに基づいてEnvoyの設定を抽象化してくれるため、開発者はEnvoyをより簡単に設定できます。
Envoyの設定の抽象化は、Envoyを使用したサービスメッシュ (例:Istioサイドカーメッシュ/アンビエントメッシュ、Consul、Istioから得られた学びを土台に登場したCiliumサイドカーフリーメッシュなど) に共通しています。
つまり、次々に登場するEnvoyによるサービスメッシュツールに振り回されないようにするためには、ツールがどのようにEnvoyを抽象化するのかを理解しておく必要があります。
そこで今回は、IstioサイドカーメッシュがEnvoyのHTTPSリクエストの処理をどのように抽象化するのかを解説します。
また、抽象化されたEnvoyがHTTPSリクエストを処理する仕組みも一緒に解説します。
これらの知識は、様々なサービスメッシュツールで流用できるはずです。
それでは、もりもり布教していきます😗
飛ばしていただいても大丈夫ですが、読んでもらえるとより理解が深まるはずです👍
02. 様々なリソースによるEnvoy設定の抽象化
まずは、どのようなリソースがHTTPSリクエストの処理に関係しているのかを、HTTPSリクエストの方向に分けて解説していきます。
istio-proxy
コンテナやEnvoyについては、次章以降で解説します。
サービスメッシュ外からのHTTPS
サービスメッシュ外から内にHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。
flowchart TD 送信元 -.->|HTTPS| Gateway Gateway([⛵️ Gateway]) -.-> VirtualService VirtualService([⛵️ VirtualService]) -.-> DestinationRule DestinationRule([⛵️ DestinationRule]) -.-> Service Service([☸️ Service]) -.-> Endpoints Endpoints([☸️ Endpoints]) -.->|HTTPS| 宛先 classDef sly fill: #CCFFFF, stroke: black; class 送信元 sly classDef yellow fill: #FFFF88, stroke: black; class 宛先 yellow classDef blue fill: #326CE5, color: white, stroke: black; class Gateway,VirtualService,DestinationRule,Service,Endpoints blue
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- クライアントは、サービスメッシュ外からL7ロードバランサーにHTTPSリクエストを送信します。
- L7ロードバランサーは、Istio IngressGateway PodにHTTPSリクエストを送信します。もちろん、クラスター外からIstio IngressGateway PodにHTTPリクエストを送信するために、Service (例:NodePort Service) が必要です。
- Istio IngressGateway Podは、宛先Podとの間で相互TLS認証を実施します。
- Istio IngressGateway Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) に応じて、HTTPSリクエストを宛先PodにL7ロードバランシングします。
マイクロサービス間のHTTPS
サービスメッシュ内のPodから別のPodにHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。
flowchart TD 送信元 -.->|HTTPS| VirtualService VirtualService([⛵️ VirtualService]) -.-> DestinationRule DestinationRule([⛵️ DestinationRule]) -.-> Service Service([☸️ Service]) -.-> Endpoints Endpoints([☸️ Endpoints]) -.->|HTTPS| 宛先 classDef sly fill: #CCFFFF, stroke: black; class 送信元 sly classDef yellow fill: #FFFF88, stroke: black; class 宛先 yellow classDef blue fill: #326CE5, color: white, stroke: black; class VirtualService,DestinationRule,Service,Endpoints blue
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- 送信元Podは、宛先Podとの間で相互TLS認証を実施します。
- 送信元Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) の設定に応じて、HTTPSリクエストを宛先PodにL7ロードバランシングします。
実は、サービスメッシュ内のPod間通信では、kube-proxyは使用しない。
istio-init
コンテナは、istio-iptables
コマンドを実行し、iptablesのルールを書き換えます (本記事3章参照) 。
これにより、送信元Podから宛先Podに直接通信できるようになります。
サービスメッシュ外へのHTTPS
サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) にHTTPSリクエストを送信する場合、リソースが以下の順で紐付き、Envoyの設定を抽象化します。
複数のVirtualServiceとDestinationが登場するため、これらには便宜上 X
と Y
をつけています。
flowchart TD 送信元 -.->|HTTPS| VirtualServiceX VirtualServiceX([⛵️ VirtualService X]) -.-> DestinationRuleX DestinationRuleX([⛵️ DestinationRule X]) -.-> Service Service([☸️ Service]) -.-> Endpoints Endpoints([☸️ Endpoints]) -.-> Gateway Gateway([⛵️ Gateway]) -.-> VirtualServiceY VirtualServiceY([⛵️ VirtualService Y]) -.-> DestinationRuleY DestinationRuleY([⛵️ DestinationRule Y]) -.-> ServiceEntry ServiceEntry([⛵️ ServiceEntry]) -.->|HTTPS| 宛先 classDef sly fill: #CCFFFF, stroke: black; class 送信元 sly classDef yellow fill: #FFFF88, stroke: black; class 宛先 yellow classDef blue fill: #326CE5, color: white, stroke: black; class Gateway,VirtualServiceX,VirtualServiceY,DestinationRuleX,DestinationRuleY,Service,Endpoints,ServiceEntry blue
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- 送信元Podは、HTTPSリクエストの宛先がServiceEntryでエントリ済みか否かの設定に応じて、HTTPSリクエストの宛先を切り替えます。
- 宛先がエントリ済みであれば、送信元PodはHTTPSリクエストの宛先にIstio EgressGateway Podを選択します。
- 宛先が未エントリであれば、送信元PodはHTTPSリクエストの宛先に外のシステムを選択します。
- 送信元Podは、Istio EgressGateway Podとの間で相互TLS認証を実施します。
- (1) で宛先がエントリ済であったとします。送信元Podは、HTTPSリクエストの向き先をIstio EgressGateway Podに変更します。
- 送信元Podは、Kubernetesリソース (Service、Endpoints) やIstioカスタムリソース (VirtualService、DestinationRule) の設定に応じて、Istio EgressGateway PodにL7ロードバランシングします。
- Istio EgressGateway Podは、HTTPSリクエストをエントリ済システムにL7ロードバランシングします。
実は、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コントロールプレーンは異なる責務を担う複数のレイヤーから構成されています。
レイヤー名 | 責務 |
---|---|
レイヤー |
kube-apiserverからKubernetesリソースやIstioカスタムリソースの設定を取得します。 Istioの初期から名前は変わっていません。 |
レイヤー |
リソースの設定をEnvoy設定に変換します。 Istioの初期ではConfig Data Modelレイヤーという名前で、執筆時点 (2024/01/16) で名前が変わっています。 |
レイヤー |
Envoyの設定や証明書をPod内のistio-proxyコンテナに配布します。 Istioの初期では、Proxy Servingレイヤーという名前で、執筆時点 (2024/01/16) で名前が変わっています。 |
図中の番号に沿って、Istioコントロールプレーンの仕組みを解説します。
- Config ingestionレイヤーにて、 Istioコントロールプレーンはkube-apiserverにHTTPSリクエストを送信します。ここで、KubernetesリソースやIstioカスタムリソースの設定を取得します。
- Config translationレイヤーにて、取得したリソースの設定をEnvoyの設定に変換します。
- Config servingレイヤーにて、Envoyの設定や証明書をPod内の
istio-proxy
コンテナに配布します。双方向ストリーミングRPCのため、istio-proxy
コンテナがConfig servingレイヤーにリクエストを送信し、これらを取得することもあります。
Config servingレイヤーには、XDS-APIがあります。
このXDS-APIは、Envoyの設定に関するエンドポイント (LDS-API、RDS-API、CDS-API、EDS-API、ADS-APIなど) や、証明書配布のエンドポイント (例:SDS-API) を持ちます。
以下の記事で解説していますため、もし気になる方はよろしくどうぞ🙇🏻
Istioコントロールプレーンは、前述の責務以外にカスタムコントローラーとしての責務も担います。
以前は、IstioOperatorがカスタムコントローラーの責務を担っていましたが、執筆時点 (2024/01/16) ではIstioOperatorが非推奨となりました。
IstioOperatorの代わりに、Istioコントロールプレーンがこれを担うようになりました👍🏻
サービスメッシュ外からのHTTPS
サービスメッシュ外から内にHTTPSリクエストを送信する場合のistio-proxy
コンテナです。
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内の
istio-proxy
コンテナに提供します。 - クライアントは、サービスメッシュ外からL7ロードバランサーにHTTPSリクエストを送信します。
- L7ロードバランサーは、Istio IngressGateway PodにHTTPSリクエストを送信します。もちろん、クラスター外からIstio IngressGateway PodにHTTPリクエストを送信するために、Service (例:NodePort Service) が必要です。
- Istio IngressGateway Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナに送信します (リダイレクトは不要)。 - Istio IngressGateway Pod内の
istio-proxy
コンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。 - Istio IngressGateway Pod内の
istio-proxy
コンテナは、HTTPSリクエストを宛先PodにL7ロードバランシングします。 - 宛先Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナにリダイレクトします。 - 宛先Pod内の
istio-proxy
コンテナは、HTTPSリクエストを宛先マイクロサービスに送信します。
Pod内のiptablesは、リクエストが必ず
istio-proxy
コンテナを経由するように、istio-proxy
コンテナにリクエストをリダイレクトします。
iptablesのルールを書き換えるのは
istio-init
コンテナです。
Istioは、
istio-proxy
コンテナと同じタイミングで、istio-init
コンテナをPodにインジェクションします (Istio IngressGatewayとIstio EgressGatewayのPodは除きます)。
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では、マイクロサービスがないため、
istio-proxy
コンテナにリクエストをリダイレクトする必要がありません。
そのため、Istioはiptablesのルールを書き換える
istio-init
コンテナをIstio IngressGateway Podにインジェクションしません。
つまり、Istio IngressGateway Pod内のiptablesのルールはデフォルトのままになっています👍🏻
マイクロサービス間のHTTPS
サービスメッシュ内のPodから別のPodにHTTPSリクエストを送信する場合のistio-proxy
コンテナです。
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内の
istio-proxy
コンテナに提供します。 - 送信元Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナにリダイレクトします。 - 送信元Pod内の
istio-proxy
コンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。 - 送信元Pod内の
istio-proxy
コンテナは、HTTPSリクエストを宛先PodにL7ロードバランシングします。 - 宛先Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナにリダイレクトします。 - 宛先Pod内の
istio-proxy
コンテナは、HTTPSリクエストを宛先マイクロサービスに送信します。
サービスメッシュ外へのHTTPS
サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) にHTTPSリクエストを送信する場合のistio-proxy
コンテナです。
各リソースは、以下の仕組みで、HTTPSリクエストを送信元から宛先まで届けます。
図中の番号に沿って、通信の仕組みを解説します。
- Istioコントロールプレーンは、翻訳されたEnvoyの設定をPod内の
istio-proxy
コンテナに提供します。 - 送信元Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナにリダイレクトします。 - 送信元Pod内の
istio-proxy
コンテナは、宛先Podを決定し、またこのPodに対して相互TLS認証を実施します。この時、ServiceEntryで宛先がエントリ済みか否かに応じて、HTTPSリクエストの宛先を切り替えます。- 宛先がエントリ済みであれば、
istio-proxy
コンテナはHTTPSリクエストの宛先にIstio EgressGateway Podを選択します。 - 宛先が未エントリであれば、
istio-proxy
コンテナはHTTPSリクエストの宛先に外のシステムを選択します。
- 宛先がエントリ済みであれば、
- ここでは、宛先がエントリ済であったとします。送信元Pod内の
istio-proxy
コンテナは、HTTPSリクエストをIstio EgressGateway PodにL7ロードバランシングします。 - Istio EgressGateway Pod内のiptablesは、HTTPSリクエストを
istio-proxy
コンテナに送信します (リダイレクトは不要)。 - Istio EgressGateway Pod内の
istio-proxy
コンテナは、HTTPSリクエストをエントリ済システムにL7ロードバランシングします。
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リクエストを送信元から宛先まで届けます。
flowchart TD 送信元 -.->|HTTPS| リスナー リスナー(リスナー) -.-> リスナーフィルター subgraph "" リスナーフィルター(リスナーフィルター) -.-> ネットワークフィルター ネットワークフィルター(ネットワークフィルター) -.-> HTTPフィルター end HTTPフィルター(HTTPフィルター) -.-> ルート ルート(ルート) -.-> クラスター クラスター(クラスター) -.-> エンドポイント エンドポイント(エンドポイント) -.->|HTTPS| 宛先 classDef sly fill: #CCFFFF, stroke: black; class 送信元 sly classDef yellow fill: #FFFF88, stroke: black; class 宛先 yellow classDef red fill: #EA6B66, font-weight :bold, stroke: black; class リスナー,リスナーフィルター,ネットワークフィルター,HTTPフィルター,ルート,クラスター,エンドポイント red
各処理がどのような責務を担っているのかをもう少し詳しく見てみましょう。
図中の番号に沿って、EnvoyがHTTPSリクエストを処理する仕組みを解説します。
- 送信元からのHTTPSリクエストの宛先ポートで、リスナーを絞り込みます。
- 通信の種類 (例:HTTP、HTTPS、TCP、UDP、Unixドメインソケットなど) に応じてフィルターを選び、各フィルターがパケットのヘッダーを処理します。もしHTTPSであれば、送信元との間でTLS接続を確立し、パケットのL7のアプリケーションデータを復号化します。
- フィルターを使用して、HTTPSリクエストの宛先ポートで、ルートを絞り込みます。
- フィルターを使用して、HTTPSリクエストの宛先ホストやパスで、クラスターを絞り込みます。
- 設定した負荷分散方式 (例:ラウンドロビンなど) に応じて、クラスター配下のエンドポイントを選びます。
- 宛先との間でTLS接続を確立し、パケットのL7のアプリケーションデータを暗号化します。そして、エンドポイントにL7ロードバランシングします。
HTTPリクエストを処理する場合、フィルターに紐づくのはルートですが、TCPリクエストの場合はそうではありません。
TCPリクエストを処理する場合、フィルターにクラスターが紐づきます👍🏻
flowchart TD 送信元 -.->|TCP| リスナー リスナー(リスナー) -.-> リスナーフィルター subgraph "" リスナーフィルター(リスナーフィルター) -.-> ネットワークフィルター end ネットワークフィルター(ネットワークフィルター) -.-> クラスター クラスター(クラスター) -.-> エンドポイント エンドポイント(エンドポイント) -.->|TCP| 宛先 classDef sly fill: #CCFFFF, stroke: black; class 送信元 sly classDef yellow fill: #FFFF88, stroke: black; class 宛先 yellow classDef red fill: #EA6B66, font-weight :bold, stroke: black; class リスナー,リスナーフィルター,ネットワークフィルター,クラスター,エンドポイント red
フィルター
フィルターの一覧
Envoyのフィルターは、Envoyの機能を拡張するための設定です。
HTTPSリクエストを処理するためには、リスナーフィルター、ネットワークフィルター、HTTPフィルター、といったフィルターが必要になります。
全ては解説しきれないため、HTTPSリクエストを処理するための代表的なフィルターをいくつか抜粋しました。
ただ、 Istioはこれらのフィルターをデフォルトで有効にしてくれている ため、開発者がEnvoyのフィルターを設定する場面は少ないです。
逆をいえば、Istioを介さずにEnvoyをそのまま使用する場合、開発者がEnvoyのフィルターを自前で設定する必要があります👍🏻
(一部抜粋) |
説明 | |
---|---|---|
リスナー フィルター |
Original Destination | istio-proxy コンテナへのリダイレクト前の宛先情報をEnvoyが取得できるようにします。Pod内のiptablesがHTTPSリクエストを 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-proxy
コンテナは、イメージのビルド時に、あらかじめ用意しておいたEnvoyの設定ファイルを組み込みます。
そのため、
istio-proxy
コンテナ内のEnvoyは、多くの設定をデフォルトで有効にできます。
Istioを利用する開発者が、EnvoyがHTTPSリクエストを処理するために必要なフィルターを有効にしなくてよいのも、Istioのおかげです。
Istioほんまにありがとな🙏🙏🙏
フィルターチェーンの仕組み
Envoyは、複数のフィルターからなるフィルターチェーンを実行し、HTTPSを処理します。
図中の番号に沿って、Envoyのフィルターチェーンの仕組みを解説します。
各フィルターの機能は、前述したフィルターの一覧を参考にしてください🙇🏻
- リスナーフィルター (Original Destination、HTTP Inspector、TLS Inspectorなど) を実行します。
- (1) でTLS InspectorがTLSを検知した場合、DownstreamTlsContextで宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
- ネットワークフィルター (HTTP connection managerなど) を実行します。
- HTTPフィルター (Router、gRPC-Webなど) を実行します。
HTTPフィルターはHTTP/HTTPSリクエストを処理する場合にのみ使用します。
それ以外の通信の種類 (例:TCP、UDP、Unixドメインソケットなど) の場合は、HTTPフィルターを使用しません。
例えば、TCPリクエストの場合、ネットワークフィルターのTCP proxyフィルターを使用します👍🏻
05. リソースの設定からEnvoy設定への翻訳
いよいよです🔥
Istioが各リソースをいずれのEnvoyの設定に翻訳しているのかを解説します。
表で対応関係の一覧を示した後、istio-proxy
コンテナ内のEnvoyに当てはめました。
各リソースとEnvoyの設定の関係一覧
Istioコントロールプレーンは、KubernetesリソースやIstioカスタムリソースの設定をEnvoyの設定に翻訳し、処理の流れに当てはめます。
以下の通り、各リソースがいずれのEnvoyの設定を抽象化するのかを整理しました。
リソースによっては、Envoyの複数の設定を抽象化します。
なお、Istioの用意したEnvoyのフィルターのデフォルト値を変更するユースケースが少ないため、これを抽象化するEnvoyFilterについては言及しません。
Kubernetes ☸️ リソース |
Istio ⛵️ カスタムリソース |
||||||
---|---|---|---|---|---|---|---|
Service | Endpoints | Gateway | Virtual Service |
Destination Rule |
Service Entry |
Peer Authentication |
|
✅ | ✅ | ✅ | ✅ | ||||
✅ | ✅ | ||||||
✅ | ✅ | ✅ | ✅ | ||||
✅ | ✅ | ✅ |
サービスメッシュ外からのHTTPS
Envoyの設定を抽象化するリソース一覧
サービスメッシュ外からのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。
Gatewayは、Istio IngressGatewayの一部として使用します。
ServiceEntryは、使用しないリソースのため、×
としています。
Kubernetes ☸️ リソース |
Istio ⛵️ カスタムリソース |
||||||
---|---|---|---|---|---|---|---|
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 |
|
送信元 | リスナー | ✅ | ✅ | ✅ | ✅ | ||
ルート | ✅ | ✅ | |||||
クラスター | ✅ | ✅ | ✅ | ||||
エンドポイント | ✅ | ✅ | |||||
宛先 | リスナー | ✅ | ✅ | ✅ | |||
ルート | ✅ | ✅ | |||||
クラスター | ✅ | ✅ | ✅ | ||||
エンドポイント | ✅ | ✅ |
今回、送信元のIstio IngressGatewayと宛先は同じNamespaceにあると仮定しています。
しかし、もししっかり設計するのであれば、Istio IngressGatewayは専用のNamespace (例:
istio-ingress
) においた方が良いです。
マイクロサービスとは異なるNamespaceにIstio IngressGatewayを置くことで、Istio IngressGatewayをアップグレードしやすくなったり、他から障害の影響を受けにくくなります🙆🏻♂️
istio-proxy
コンテナ内のEnvoyに当てはめる
この表を、HTTPSリクエストの仕組みの中に当てはめると、以下になります。
引用した前述の解説のイメージが掴めるかと思います。
送信元または宛先Envoyでほとんど同じリソースが登場しますが、 Gatewayは送信元Envoyだけで登場します。
リソースの種類だけに着目すると、以下になります。
Gatewayが送信元Envoyだけで登場することがわかりやすくなりました。
マイクロサービス間のHTTPS
Envoyの設定を抽象化するリソース一覧
サービスメッシュ内のPodから別のPodへのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。
GatewayとServiceEntryは、使用しないリソースのため、×
としています。
Kubernetes ☸️ リソース |
Istio ⛵️ カスタムリソース |
||||||
---|---|---|---|---|---|---|---|
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で、同じリソースが登場します。
リソースの種類だけに着目すると、以下になります。
送信元または宛先Envoyで同じリソースが登場することがわかりやすくなりました。
サービスメッシュ外へのHTTPS
Envoyの設定を抽象化するリソース一覧
サービスメッシュ内のPodから外のシステム (例:データベース、ドメインレイヤー委譲先の外部API) へのHTTPSリクエストを処理する場合に関係するリソースを抜粋しました。
Gatewayは、Istio EgressGatewayの一部として使用します。
Kubernetes ☸️ リソース |
Istio ⛵️ カスタムリソース |
||||||
---|---|---|---|---|---|---|---|
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 |
|
✅ | ✅ | ✅ | ||||||||
✅ | ✅ | |||||||||
✅ | ✅ | ✅ | ||||||||
✅ | ✅ | |||||||||
✅ | ✅ | ✅ | ||||||||
✅ | ||||||||||
✅ | ✅ | ✅ | ||||||||
✅ | ✅ |
今回、送信元と宛先のIstio EgressGatewayは同じNamespaceにあると仮定しています。
しかし、もししっかり設計するのであれば、Istio EgressGatewayは専用のNamespace (例:
istio-egress
) においた方が良いです。
マイクロサービスとは異なるNamespaceにIstio EgressGatewayを置くことで、Istio EgressGatewayをアップグレードしやすくなったり、他から障害の影響を受けにくくなります🙆🏻♂️
istio-proxy
コンテナ内のEnvoyに当てはめる
この表を、HTTPSリクエストの仕組みの中に当てはめると、以下になります。
PeerAuthenticationだけは、話を簡単にするために送信元と宛先が同じNamespaceであると仮定しているので、同じリソースが抽象化します。
引用した前述の解説のイメージが掴めるかと思います。
送信元または宛先Envoyで同じリソースが登場しません 。
リソースの種類だけに着目すると、以下になります。
送信元または宛先Envoyで同じリソースが登場しないことがわかりやすくなりました。
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リクエストを処理するとします。
図中の番号に沿って、通信の仕組みを解説します。
送信元Pod側のistio-proxy
コンテナ
- 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:
50000
) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50000
) 。 - HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
- HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:
50000
) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:50000
) 。 - HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:
foo-service.foo-namespace.svc.cluster.local
) やパス (例:/
) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートやホストで管理しています (例:outbound|50010|foo-service.foo-namespace.svc.cluster.local
) 。 - 設定した負荷分散方式 (例:ラウンドロビンなど) に応じて、Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:
<PodのIPアドレス>:50000
) 。 - 宛先との間で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さんの解説が鬼わかりやすかったです🙏🙏🙏
マイクロサービス間のHTTPS
ここでは、istio-proxy
コンテナはHTTPSリクエストを処理するとします。
図中の番号に沿って、通信の仕組みを解説します。
送信元Pod側のistio-proxy
コンテナ
- 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:
50010
) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_50010
) 。 - HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
- HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:
50010
) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:50010
) 。 - HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:
foo-service.foo-namespace.svc.cluster.local
) やパス (例:/
) で、クラスターを絞り込みます。Envoyは、クラスターを宛先ポートやホストで管理しています (例:outbound|50010|foo-service.foo-namespace.svc.cluster.local
) 。 - 設定した負荷分散方式 (例:ラウンドロビンなど) に応じて、Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:
<PodのIPアドレス>:50010
) 。 - 宛先との間で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リクエストを処理するとします。
図中の番号に沿って、通信の仕組みを解説します。
送信元Pod側のistio-proxy
コンテナ
- 送信元マイクロサービスからのHTTPSリクエストの宛先ポート (例:
443
) で、リスナーを絞り込みます。Envoyは、リスナーを宛先ポートで管理しています (例:0.0.0.0_443
) 。 - HTTPSリクエストを処理するための各種フィルターを選びます。また、宛先とTLSハンドシェイクを実行し、パケットのL7のアプリケーションデータを復号化します。
- HTTPフィルターにより、HTTPSリクエストの宛先ポート (例:
443
) で、ルートを絞り込みます。Envoyは、ルートを宛先ポートで管理しています (例:443
) 。 - HTTPフィルターにより、HTTPSリクエストの宛先ホスト (例:
istio-egressgateway-service.foo-namespace.svc.cluster.local
) やパス (例:/
) で、クラスターを絞り込みます。Envoyは、クラスターをIstio EgressGateway 宛先ポートやホストで管理しています (例:outbound|443|istio-egressgateway-service.foo-namespace.svc.cluster.local
) 。 - 設定した負荷分散方式 (例:ラウンドロビンなど) に応じて、Istio EgressGateway Service配下のPodを選びます。Envoyは、エンドポイントをPodのIPアドレスや宛先ポートで管理しています (例:
<PodのIPアドレス>:443
) 。 - 宛先との間で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のネットワークを調査するにあたり、以下の方に知見をご教授いただきました。
@ken5owata
さん
この場で感謝申し上げます🙇🏻