目次
01. はじめに
3-shake Advent Calender 2022 最終日の記事です🎅🎄
私は普段は 俺の技術ノート に知見を記録しており、はてなブログはデビュー戦となります。
さて今回は、サービスメッシュを実装するIstio⛵️のサービスディスカバリーに関する記事を投稿しました。
Istioの機能の一つである『サービスディスカバリー』の仕組みを、Envoyを交えながら、もりもり布教しようと思います(沼のまわりに餌をまく)。
今回の記事では、先日の 3-shake SRE Tech Talk で発表した内容に加えて、スライドの余白と発表時間の制約で記載できなかったことも記載しました😗
02. サービスディスカバリーについて
マイクロサービスアーキテクチャにおけるサービスディスカバリー
サービスディスカバリーとは
マイクロサービスアーキテクチャでは、マイクロサービスからマイクロサービスにリクエストを送信する場面があります。
サービスディスカバリーとは、宛先マイクロサービスの宛先情報(例:IPアドレス、完全修飾ドメイン名、など)を検出し、送信元マイクロサービスが宛先マイクロサービスにリクエストを継続的に送信できるようにする仕組みのことです。
なぜサービスディスカバリーが必要なのか
そもそも、なぜサービスディスカバリーが必要なのでしょうか。
マイクロサービスアーキテクチャでは、システムの信頼性(定められた条件下で定められた期間にわたり、障害を発生させることなく実行する程度)を担保するために、マイクロサービスのインスタンスの自動スケーリングを採用します。
この時、自動スケーリングのスケールアウトでマイクロサービスが増加するたびに、各インスタンスには新しい宛先情報が割り当てられてしまいます。
また、マイクロサービスが作り直された場合にも、宛先情報は更新されてしまいます。
このように、たとえインスタンスの宛先情報が更新されたとしても、インスタンスへのリクエストに失敗しない仕組みが必要です。
サービスディスカバリーの要素
サービスディスカバリーの仕組みは、次の要素からなります。
名前解決に関しては、DNSベースのサービスディスカバリー(例:CoreDNS + Service + kube-proxyによるサービスディスカバリー)で必要となり、Istioでは使いません。
そのため、本記事では言及しないこととします🙇🏻♂️
要素 | 責務 |
---|---|
送信元マイクロサービス | リクエストを送信する。 |
宛先マイクロサービス | リクエストを受信する。 |
サービスレジストリ | 宛先マイクロサービスの宛先情報を保管する。 |
ロードバランサー | 宛先マイクロサービスのインスタンスにロードバランシングする。 |
名前解決 | 宛先マイクロサービスへのリクエスト送信時に、名前解決できるようにする。 |
サービスディスカバリーのパターン
サービスディスカバリーのパターンとは
サービスディスカバリーの仕組みにはいくつか種類があります。
Istioのサービスディスカバリーは、このうちのサーバーサイドパターンを実装したものになります。
サーバーサイドパターン
送信元マイクロサービスから、問い合わせとロードバランシングの責務が切り離されています。
送信元マイクロサービスは、ロードバランサーにリクエストを送信します。
ロードバランサーは、宛先マイクロサービスの宛先をサービスレジストリに問い合わせ、またリクエストをロードバランシングする責務を担います。
(例)Istio、Linkerd、など
ℹ️ 参考:
クライアントサイドパターン
通信の送信元マイクロサービスは、宛先マイクロサービスの宛先をサービスレジストリに問い合わせ、さらにロードバランシングする責務を担います。
(例)NeflixのEureka、など
ℹ️ 参考:
03. Istioのサービスディスカバリー
Istioのサービスディスカバリーの仕組み
Istioが実装するサービスメッシュには、サイドカープロキシメッシュとアンビエントメッシュがあり、今回はサイドカープロキシメッシュのサービスディスカバリーを取り上げます。
Istioのサービスディスカバリーは、discovery
コンテナとistio-proxy
コンテナが軸となり、サーバーサイドパターンのサービスディスカバリーを実装します。
各コンテナについて詳しく見ていく前に、全体像を解説します。
- kube-apiserverは、Pod等の宛先情報をetcd等に保管する。これは、Kubernetesの通常の仕組みである。
discovery
コンテナは、kube-apiserverからPod等の宛先情報を取得し、自身に保管する。istio-proxy
コンテナは、discovery
コンテナからPod等の宛先情報を双方向ストリーミングRPCで取得する。- 送信元マイクロサービスがリクエストを送信する。Pod内のiptablesがこれをリダイレクトし、
istio-proxy
コンテナが受信する。 istio-proxy
コンテナは、リクエストをロードバランシングし、宛先Podにこれを送信する。
上記では、サーバーサイドパターンでの責務通り、送信元マイクロサービスはロードバランサー(ここではistio-proxy
コンテナ)にリクエストを送信しています。
この時、送信元マイクロサービスがistio-proxy
コンテナに直接的にリクエストを送信しているというよりは、iptablesがistio-proxy
コンテナにリクエストをリダイレクトしてくれています。
またistio-proxy
コンテナは、サービスレジストリへの問い合わせと、ロードバランシングする責務を担っています。
ℹ️ 参考:
discoveryコンテナの仕組み
discovery
コンテナを詳しく見てみましょう。
discovery
コンテナは、別名Istiodと呼ばれています。
XDS-APIというエンドポイントを公開しており、XDS-APIのうち、サービスディスカバリーに関係するAPIは以下の通りです。
APIの種類 | 説明 |
---|---|
LDS-API | Envoyのリスナー値を取得できる。 |
RDS-API | Envoyのルート値を取得できる。 |
CDS-API | Envoyのクラスター値を取得できる。 |
EDS-API | Envoyのエンドポイント値できる。 |
ADS-API | 各XDS-APIから取得できる宛先情報を整理して取得できる。 |
discovery
コンテナは、kube-apiserverからPod等の宛先情報を取得して自身のメモリ上に保管し、各XDS-APIから提供します。
XDS-APIとistio-proxy
コンテナの間では、gRPCの双方向ストリーミングRPCの接続が確立されています。
そのため、istio-proxy
コンテナからのリクエストに応じて宛先情報を返却するだけでなく、リクエストがなくとも、XDS-APIからもistio-proxy
コンテナに対して宛先情報を送信します。
各種XDS-APIから個別に宛先情報を取得できますが、Envoy上で宛先情報のバージョンの不整合が起こる可能性があるため、Istioでは実際にはADS-APIを使用しています。
ℹ️ 参考:Amazon | Istio in Action | Posta, Christian E., Maloku, Rinor | Software Development
istio-proxyコンテナの仕組み
istio-proxy
コンテナを詳しく見てみましょう。
istio-proxy
コンテナでは、pilot-agentとEnvoyが稼働しています。
先ほどistio-proxy
コンテナは、双方向ストリーミングRPCでADS-APIから宛先情報を取得すると説明しました。
厳密にはEnvoyが、pilot-agentを介して、ADS-APIから双方向ストリーミングRPCで宛先情報を取得します。
istio-proxy
コンテナが送信元マイクロサービスからリクエストを受信すると、EnvoyはADS-APIから取得した宛先情報に基づいて、宛先マイクロサービスのインスタンスにロードバランシングします。
ℹ️ 参考:
04. istio-proxyコンテナ内のEnvoyの仕組み
Envoyの処理の流れ
EnvoyがADS-APIから取得した宛先情報を見ていく前に、Envoyの処理の流れを解説します。
istio-proxy
コンテナ内のEnvoyでは、以下の仕組みでリクエストを処理します。
istio-proxy
コンテナは、送信元マイクロサービスからリクエストを受信する。- Envoyは、リクエストの宛先情報(例:宛先IPアドレス、ポート番号、パス、ホスト、など)に応じてリスナー値を選ぶ。
- Envoyは、リスナーに紐づくルート値を選ぶ。
- Envoyは、クラスターに紐づくクラスター値を選ぶ。
- Envoyは、クラスターに紐づくエンドポイント値を選ぶ。
- Envoyは、エンドポイント値に対応するインスタンスにリクエストを送信する。
Envoyで確認した宛先情報を👆に当てはめて見ていくことにしましょう。
ℹ️ 参考:
- Amazon.co.jp: Istio in Action (English Edition) 電子書籍: Posta, Christian E., Maloku, Rinor: 洋書
- Amazon | Istio: Up and Running: Using a Service Mesh to Connect, Secure, Control, and Observe | Calcote, Lee, Butcher, Zack | Design Tools & Techniques
- Architecture Analysis of Istio: The Most Popular Service Mesh Project - Alibaba Cloud Community
EnvoyがADS-APIから取得した宛先情報を見てみよう
config_dumpエンドポイント
実際にEnvoyに登録されている宛先情報は、istio-proxy
コンテナ自体のlocalhost:15000/config_dump
からJSONで取得できます。
ただし、JSONだと見にくいので、yq
コマンドでYAMLに変換すると見やすくなります。
もしお手元にIstioがある場合は、Envoyにどんな宛先情報が登録されているか、Envoyを冒険してみてください👍🏻
$ kubectl exec \ -it foo-pod \ -n foo-namespace \ -c istio-proxy \ -- bash -c "curl http://localhost:15000/config_dump" | yq -P
リスナー値
▼ 確認方法
istio-proxy
コンテナがADS-APIから取得したリスナー値は、/config_dump?resource={dynamic_listeners}
から確認できます。
ここでは、foo-pod内でbar-podのリスナー値を確認したと仮定します。
$ kubectl exec \ -it foo-pod \ -n foo-namespace \ -c istio-proxy \ -- bash -c "curl http://localhost:15000/config_dump?resource={dynamic_listeners}" | yq -P
▼ 結果
以下を確認できました。
- 宛先IPアドレスや宛先ポート番号に応じてリスナー値を選べるようになっており、ここでは
<任意のIPアドレス>:50002
。 - リスナー値に紐づくルート値の名前
configs: - "@type": type.googleapis.com/envoy.admin.v3.ListenersConfigDump.DynamicListener # リスナー名 name: 0.0.0.0_50002 active_state: version_info: 2022-11-24T12:13:05Z/468 listener: "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: 0.0.0.0_50002 address: socket_address: # 受信したパケットのうちで、宛先IPアドレスでフィルタリング address: 0.0.0.0 # 受信したパケットのうちで、宛先ポート番号でフィルタリング port_value: 50002 filter_chains: - filter_chain_match: transport_protocol: raw_buffer application_protocols: - http/1.1 - h2c filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: outbound_0.0.0.0_50001 rds: config_source: ads: {} initial_fetch_timeout: 0s resource_api_version: V3 # 本リスナーに紐づくルート値の名前 route_config_name: 50002 ... - "@type": type.googleapis.com/envoy.admin.v3.ListenersConfigDump.DynamicListener ...
ℹ️ 参考:
ルート値
▼ 確認方法
istio-proxy
コンテナがADS-APIから取得したリスナー値は、/config_dump?resource={dynamic_route_configs}
から確認できます。
ここでは、foo-pod内でbar-podのルート値を確認したと仮定します。
$ 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
▼ 結果
コマンドを実行するとYAMLを取得でき、以下を確認できました。
configs: - "@type": type.googleapis.com/envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig version_info: 2022-11-24T12:13:05Z/468 route_config: "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration # ルート値の名前 name: 50002 virtual_hosts: - name: bar-service.bar-namespace.svc.cluster.local:50002 # ホストベースルーティング domains: - bar-service.bar-namespace.svc.cluster.local - bar-service.bar-namespace.svc.cluster.local:50002 - bar-service - bar-service:50002 - bar-service.bar-namespace.svc - bar-service.bar-namespace.svc:50002 - bar-service.bar-namespace - bar-service.bar-namespace:50002 - 172.16.0.2 - 172.16.0.2:50002 routes: - match: # パスベースルーティング prefix: / route: # 本ルートに紐づくクラスター値の名前 cluster: outbound|50002|v1|bar-service.bar-namespace.svc.cluster.local timeout: 0s retry_policy: retry_on: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes num_retries: 2 retry_host_predicate: - name: envoy.retry_host_predicates.previous_hosts host_selection_retry_max_attempts: "5" retriable_status_codes: - 503 max_stream_duration: max_stream_duration: 0s grpc_timeout_header_max: 0s decorator: operation: bar-service.bar-namespace.svc.cluster.local:50002/* ... - '@type': type.googleapis.com/envoy.admin.v3.RoutesConfigDump.DynamicRouteConfig ...
ℹ️ 参考:
クラスター値
▼ 確認方法
istio-proxy
コンテナがADS-APIから取得したクラスター値は、/config_dump?resource={dynamic_active_clusters}
から確認できます。
ここでは、foo-pod内でbar-podのクラスター値を確認したと仮定します。
$ 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
▼ 結果
コマンドを実行するとYAMLを取得でき、以下を確認できました。
configs: - "@type": type.googleapis.com/envoy.admin.v3.ClustersConfigDump.DynamicCluster version_info: 2022-11-24T12:13:05Z/468 cluster: "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster # クラスター値の名前 name: outbound|50002|v1|bar-service.bar-namespace.svc.cluster.local type: EDS eds_cluster_config: eds_config: ads: {} initial_fetch_timeout: 0s resource_api_version: V3 # 本クラスターに紐づくエンドポイント値の親名 service_name: outbound|50002|v1|bar-service.bar-namespace.svc.cluster.local ... - "@type": type.googleapis.com/envoy.admin.v3.ClustersConfigDump.DynamicCluster ...
ℹ️ 参考:
エンドポイント値
▼ 確認方法
istio-proxy
コンテナがADS-APIから取得したクラスター値は、/config_dump?include_eds
から確認できます。
ここでは、foo-pod内でbar-podのクラスター値を確認したと仮定します。
$ kubectl exec \ -it foo-pod \ -n foo-namespace \ -c istio-proxy \ -- bash -c "curl http://localhost:15000/config_dump?include_eds" | yq -P
▼ 結果
コマンドを実行するとYAMLを取得でき、以下を確認できました。
全てのエンドポイントのload_balancing_weight
キー値が等しい場合、EnvoyはP2Cアルゴリズムに基づいてロードバランシングします。
configs: dynamic_endpoint_configs: - endpoint_config: "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment # エンドポイントの親名 cluster_name: outbound|50002|v1|bar-service.bar-namespace.svc.cluster.local endpoints: - locality: region: ap-northeast-1 zone: ap-northeast-1a lb_endpoints: - endpoint: address: socket_address: # 冗長化されたbar-podのIPアドレス address: 11.0.0.1 # bar-pod内のコンテナが待ち受けているポート番号 port_value: 80 health_check_config: {} health_status: HEALTHY metadata: filter_metadata: istio: workload: bar envoy.transport_socket_match: tlsMode: istio # ロードバランシングアルゴリズムを決める数値 load_balancing_weight: 1 - locality: region: ap-northeast-1 zone: ap-northeast-1d lb_endpoints: - endpoint: address: socket_address: # 冗長化されたbar-podのIPアドレス address: 11.0.0.2 # bar-pod内のコンテナが待ち受けているポート番号 port_value: 80 health_check_config: {} health_status: HEALTHY metadata: filter_metadata: istio: workload: bar envoy.transport_socket_match: tlsMode: istio # ロードバランシングアルゴリズムを決める数値 load_balancing_weight: 1 - locality: region: ap-northeast-1 zone: ap-northeast-1d lb_endpoints: - endpoint: address: socket_address: # 冗長化されたbar-podのIPアドレス address: 11.0.0.3 # bar-pod内のコンテナが待ち受けているポート番号 port_value: 80 health_check_config: {} health_status: HEALTHY metadata: filter_metadata: istio: workload: bar envoy.transport_socket_match: tlsMode: istio # ロードバランシングアルゴリズムを決める数値 load_balancing_weight: 1 policy: overprovisioning_factor: 140 ... - endpoint_config: ...
ℹ️参考:
Envoyの処理の流れのまとめ
確認できた宛先情報を、Envoyの処理の流れに当てはめてみました。
- 送信元マイクロサービスは、宛先マイクロサービス(
<任意のIP>/:50002
)にリクエストを送信し、サイドカーコンテナのistio-proxy
コンテナはこれを受信します。 - Envoyは、リクエストの宛先(IPアドレス、ポート番号、パス)からPodのリスナー値(
0.0.0.0_50002
)を選ぶ。 - Envoyは、リスナーに紐づくPodのルート値(
50002
)を選ぶ。 - Envoyは、クラスターに紐づくPodのクラスター値(
outbound|50002|v1|bar-service.bar-namespace.svc.cluster.local
)を選ぶ。 - Envoyは、クラスターに紐づくPodのインスタンスのエンドポイント値(
11.0.0.X/:80
)を選ぶ。 - Envoyは、エンドポイント値の宛先にPodのリクエストを送信する。
サービスディスカバリーの冒険は以上です。
05. おわりに
今回、Istioの機能の一つである『サービスディスカバリー』の仕組みを、Envoyを交えながら布教しました。
もりもりでしたが、最後までお付き合いいただきありがとうございました。
ここまで見ていただいたそこのあなた、片足が沼に浸かってます😏
謝辞
3-shake SRE Tech Talk での発表前後に、以下の方々に、発表内容について助言をいただきました。
- @ido_kara_deru さん
- @yosshi_ さん
- @yteraoka さん
(アルファベット順)
また、今回の 3-shake Advent Calender 2022 は、以下の方々に企画いただきました。
- @jigyakkuma_ さん
- @nwiizo さん
(アルファベット順)
皆様に感謝申し上げます🙇🏻♂️