Service Meshで解決するマイクロサービス間通信の課題

Service Meshは、名前がKubernetesのServiceと似ているため混同されがちで、「Serviceの上位版」のように受け取られることがあります。実際には別物で、Service Meshはマイクロサービス間の通信そのものを制御する層です。基本の通信を理解した後の発展テーマとして捉えるのが向いています。

この記事は、『KubernetesのService・Ingressの関係とPodへの通信経路』でKubernetesの基本通信を押さえたことを前提に、Service Meshが何を解決するのかを整理します。Istioなどの実装の詳細ではなく、何を解決する仕組みなのかを優先します。

この記事でわかること
  • Service MeshとKubernetesのServiceの違い
  • sidecarプロキシがマイクロサービス間通信に入るイメージ
  • Service Meshが解決する課題(mTLS・トラフィック分割・リトライ・可観測性)
  • Cloud Service MeshとIstioの位置づけ
  • multi-cluster meshという発展構成の位置づけ

基本のKubernetes通信ではService経由でPodへ届ける

まず、Service Meshがない基本のKubernetes通信を確認します。Kubernetesでは、呼び出し元のPodが相手のPodを直接指定するのではなく、Serviceを安定した宛先として使います。

たとえば注文Podがユーザー情報を取りたい場合、個々のユーザーPodを直接指定するのではなく、ユーザーServiceを宛先にします。Serviceが宛先としてまとめるのはNodeではなくPod群です。ただし、呼び出し元Podと宛先Podが別Nodeに載っていれば、通信は結果的にNodeをまたぎます。

flowchart TB
  subgraph Cluster["Cluster"]
    subgraph NodeA["Node"]
      OrderPod["注文Pod(呼び出し元)"]
    end
    UserSvc["ユーザーService(ユーザーPod群の安定した宛先)"]
    subgraph NodeB["Node"]
      UserPod1["ユーザーPod"]
      UserPod2["ユーザーPod"]
    end
  end
  OrderPod --> UserSvc
  UserSvc --> UserPod1
  UserSvc --> UserPod2

この構成だけでも、PodのIPが変わってもService名で相手に到達できます。単純なアプリケーション間通信であれば、まずはこの形で十分です。

基本のKubernetes通信で増えてくる課題

マイクロサービスが増えると、単にKubernetesのServiceへ届けばよいだけでは済まなくなります。ここでいうマイクロサービスは、注文アプリやユーザーアプリのようにPod内で動くアプリケーション側の単位です。そうしたアプリケーション間の通信そのものに、共通の悩みが出てきます。

  • アプリケーション間の通信を暗号化したい
  • 新バージョンにトラフィックを少しずつ流したい
  • 相手が一時的に失敗したときにリトライやタイムアウトを効かせたい
  • どのアプリケーションからどのアプリケーションへ、どれだけ通信が流れているか見たい

これらを各アプリケーションのコードで個別に実装すると、アプリケーションの数だけ同じ処理が散らばり、言語やチームが違えば実装もばらつきます。

Service Meshで通信まわりの共通課題をまとめて引き受ける

Service Meshは、暗号化・リトライ・可観測性のような通信まわりの共通課題を、アプリの外側の層でまとめて引き受ける仕組みです。各アプリケーションのコードに同じ処理を散らばらせず、通信制御の層として切り出すのが狙いです。

sidecarプロキシがアプリケーション間通信に入る

ここで出てくるsidecarプロキシとは、アプリのコンテナと同じPodに並べて置く通信仲介用のコンテナです。アプリ本体ではなく、通信の暗号化・リトライ・監視などを横から引き受ける補助役です。

Service Meshを入れると、既存のClusterやPodが別物になるわけではありません。各Podの中にsidecarプロキシが加わり、アプリケーション間通信がそのsidecarプロキシを通るようになります。Service Meshは、そのsidecarプロキシ群に対して通信ルールを配る制御層として働きます。

次の図は、先ほどの注文PodとユーザーPodの中身を一段詳しく描いたものです。ServiceやPodの関係は同じまま、各Podの中にappとsidecarプロキシが並びます。

flowchart TB
  SM["Service Mesh(通信ルールを管理)"]
  subgraph Cluster["Cluster"]
    subgraph NodeA["Node"]
      subgraph OrderPod["注文Pod(呼び出し元)"]
        OrderApp["app"]
        OrderSidecar["sidecar proxy"]
      end
    end
    UserSvc["ユーザーService(ユーザーPod群の安定した宛先)"]
    subgraph NodeB["Node"]
      subgraph UserPod1["ユーザーPod"]
        UserSidecar1["sidecar proxy"]
        UserApp1["app"]
      end
      subgraph UserPod2["ユーザーPod"]
        UserSidecar2["sidecar proxy"]
        UserApp2["app"]
      end
    end
    OrderApp --> OrderSidecar
    OrderSidecar --> UserSvc
    UserSvc --> UserSidecar1
    UserSvc --> UserSidecar2
    UserSidecar1 --> UserApp1
    UserSidecar2 --> UserApp2
  end
  SM -. "設定を配る" .-> OrderSidecar
  SM -. "設定を配る" .-> UserSidecar1
  SM -. "設定を配る" .-> UserSidecar2

app-a から app-b への通信の流れ

Service Meshの中心にあるのが、各Podに入ったsidecarプロキシです。

Service Meshを入れると、アプリケーション間の通信はアプリ同士が直接やりとりするのではなく、それぞれのsidecarを経由するようになります。

flowchart LR
  subgraph PodA["Pod(app-a)"]
    A["app-a"]
    PA["sidecar"]
  end
  subgraph PodB["Pod(app-b)"]
    PB["sidecar"]
    B["app-b"]
  end
  A --> PA
  PA -->|"制御された通信"| PB
  PB --> B

app-aはまず自分のsidecarに送り、sidecar同士が通信し、受け取り側のsidecarがapp-bに渡します。暗号化やリトライといった制御は、このsidecarの層で行われます。アプリのコードは通信相手を意識するだけでよく、通信の作り込みをアプリから追い出せるのが利点です。

このsidecarたちをまとめて設定・管理する仕組み全体がService Meshで、「どのアプリケーション間をどう通信させるか」を一元的に宣言できます。

Service Meshが解決する課題

sidecarの層に通信制御を寄せることで、次の課題をアプリのコードを変えずに解決できます。

mTLSでアプリケーション間通信を暗号化する

mTLS(相互TLS)は、通信する双方が互いに証明書で相手を確認しつつ暗号化する方式です。TLSとは通信を暗号化する標準的な仕組みで、mTLSはそれをアプリケーション間で双方向に行います。Service Meshは、sidecar同士の通信をmTLSにすることで、アプリケーション間通信の暗号化と相手の確認を、アプリに手を入れずに実現します。

トラフィック分割

Service Meshは、あるアプリケーションへの通信を複数のバージョンに割合で振り分けられます。新バージョンにまず一部だけを流し、問題なければ徐々に増やす、といった段階的なリリース(カナリアリリース)が可能になります。

リトライとタイムアウト

相手アプリケーションが一時的に失敗したときのリトライ(再試行)や、応答が遅いときに待つ上限を切るタイムアウトを、sidecarの層で共通に設定できます。各アプリケーションが個別に実装しなくても、通信の失敗への振る舞いを揃えられます。

可観測性

sidecarはすべての通信を通過させるため、どのアプリケーション間にどれだけの通信が流れ、どこで失敗や遅延が起きているかを、通信を眺める位置から収集できます。マイクロサービスで見えにくくなる「1つのリクエストが複数アプリケーションをどう横断したか」を追いやすくなります。

Service Meshを実現する代表的な実装

Service Meshは考え方の名前で、それを実現する実装がいくつかあります。

  • Istio
    • 代表的なオープンソースのService Mesh実装。sidecarによる通信制御の仕組みを提供する
  • Cloud Service Mesh
    • GCPが提供するマネージドなService Mesh。Istioの仕組みをベースに、GKEなどのGCP環境で運用の手間を抑えて使えるようにしたもの

ここで大事なのは実装の細部ではなく、どちらも「マイクロサービス間通信の共通の関心事をsidecarの層で引き受ける」という同じ目的を持つ点です。まずは何を解決する仕組みかを押さえ、具体的な設定は必要になったときに実装ごとのドキュメントで確認すれば十分です。

参考: 複数クラスタ構成でのService Mesh

ここまでの説明は、基本的に1つのKubernetes Cluster内のアプリケーション間通信を前提にしています。

マイクロサービスで注文アプリ、決済アプリ、ユーザーアプリを分ける場合、基本は1つのCluster内でDeployment、Pod、Serviceを分けます。NodeはPodを動かす実行場所なので、マイクロサービスを分ける主な単位ではありません。必要に応じてNamespace、NetworkPolicy、Node Poolなどで運用や配置を分けます。

そのため、注文アプリと決済アプリを分けたいだけなら、同じCluster内に注文Podと決済Podを置けば足ります。Clusterを複数にする理由は、アプリの役割分担ではなく、Clusterそのものを分けたい場合です。たとえば、region障害に備えたい、決済など一部の領域だけセキュリティ境界を強くしたい、チームやシステムごとに運用責任を分けたい、障害範囲やアップグレード影響を分離したい、といった理由です。

このようにClusterを複数にすると、各Clusterの中に同じようなアプリケーション間通信が生まれます。そのうえで、複数Clusterをまとめて同じ通信ポリシーで管理したい、Clusterをまたいだ通信が必要になったときも同じ仕組みで扱いたい、という論点が出てきます。

Service Meshは、アプリケーション間通信を制御する仕組み全体を指します。その対象が1つのCluster内に閉じることもあれば、複数Clusterにまたがることもあります。このように複数ClusterをService Meshの対象にする構成を、Cloud Service Meshのドキュメントではmulti-cluster meshと呼びます。multi-cluster meshはService Meshとは別物の名前ではなく、Service Meshの対象範囲を複数Clusterに広げた構成です。この記事でも、複数Clusterを1つのService Meshとして扱う構成を指すときはmulti-cluster meshと呼びます。

次の2つは、Clusterを複数に分ける理由の例です。まず一般的な構成として説明し、そのあとにGCPでCloud Service Meshを使う場合の見方を補足します。

パターン1 可用性目的で同じ構成を複数regionに置く

可用性を高める目的で複数regionへGKE Clusterを配置する場合は、基本的には同じようなアプリ構成を別regionのClusterにも用意します。region-aのClusterにも注文アプリ、決済アプリ、ユーザーアプリがあり、region-bのClusterにも同じ構成がある、という考え方です。

この場合、外部Load Balancerで複数regionのClusterへ振り分けます。図ではNodeの内訳ではなく、Clusterごとに同じ構成が複製されている点を見ます。

flowchart TB
  LB["外部Load Balancer"]
  Mesh["Service Mesh(meshを管理)"]
  LB -->|"region-aへ振り分け"| ClusterA
  LB -->|"region-bへ振り分け"| ClusterB
  subgraph ClusterA["Cluster A(region-a)"]
    AppsA["同じアプリ構成\n注文Pod / 決済Pod / ユーザーPod"]
    SidecarsA["各Podのsidecar proxy"]
  end
  subgraph ClusterB["Cluster B(region-b)"]
    AppsB["同じアプリ構成\n注文Pod / 決済Pod / ユーザーPod"]
    SidecarsB["各Podのsidecar proxy"]
  end
  AppsA --> SidecarsA
  AppsB --> SidecarsB
  Mesh -. "設定を配る" .-> SidecarsA
  Mesh -. "設定を配る" .-> SidecarsB

GCPで設計する場合

GCPでCloud Service Meshを使う場合、複数のGKE Clusterを同じFleetに登録してmulti-cluster meshを構成します。Fleetは、GKE ClusterなどをGoogle Cloud上で論理的にグループ化し、Fleet対応機能を使うための管理単位です。単一ClusterでもFleetは登場しますが、複数Clusterでは共通のセキュリティ、設定、サービス管理をそろえる土台として特に重要になります。

FleetはGoogle Cloudの概念なので、一般的なService Meshの説明で必ず出てくる用語ではありません。Istioなどの一般的なService Meshの考え方としては「複数Clusterを1つのmeshに参加させる」と捉え、GCPでCloud Service Meshを使う場合はFleetがその管理単位として登場する、と分けて理解すると混ざりにくくなります。

Fleetを含むGKEの複数Cluster向け機能の関係は『GKE Fleet・マルチクラスタGKE・MCSの関係まとめ』で整理しています。

flowchart TB
  LB["Cloud Load Balancing"]
  CSM["Cloud Service Mesh"]
  LB -->|"region-aへ振り分け"| ClusterA
  LB -->|"region-bへ振り分け"| ClusterB
  subgraph Fleet["Fleet(Google Cloud上の管理単位)"]
    subgraph ClusterA["GKE Cluster A(region-a)"]
      SidecarsA["各Podのsidecar proxy"]
    end
    subgraph ClusterB["GKE Cluster B(region-b)"]
      SidecarsB["各Podのsidecar proxy"]
    end
  end
  CSM -. "設定を配る" .-> SidecarsA
  CSM -. "設定を配る" .-> SidecarsB

パターン2 境界分離目的で特定領域を別Clusterにする

セキュリティや運用責任の境界を強く分けたい場合は、特定領域だけ別Clusterにすることもあります。たとえば、通常アプリClusterに注文アプリとユーザーアプリを置き、決済アプリだけ決済専用Clusterに分ける構成です。

これは「決済が別マイクロサービスだからClusterを分ける」という意味ではありません。決済だけセキュリティ境界、監査、運用責任、変更影響を強く分けたい、というCluster分離の理由がある場合の構成です。

flowchart TB
  Mesh["Service Mesh(meshを管理)"]
  subgraph AppCluster["通常アプリCluster"]
    AppPods["注文Pod / ユーザーPod"]
    AppSidecars["各Podのsidecar proxy"]
  end
  subgraph PaymentCluster["決済専用Cluster"]
    PaymentPods["決済Pod"]
    PaymentSidecars["決済Podのsidecar proxy"]
  end
  AppPods --> AppSidecars
  PaymentPods --> PaymentSidecars
  AppSidecars -. "必要な通信だけ許可" .-> PaymentSidecars
  Mesh -. "設定を配る" .-> AppSidecars
  Mesh -. "設定を配る" .-> PaymentSidecars

GCPで設計する場合

GCPでは、境界を分けたい単位ごとにGKE Clusterを分け、それらを同じFleetに登録してCloud Service Meshの対象にできます。Fleetを使う理由はパターン1と同じで、複数ClusterをCloud Service Meshの管理対象としてそろえるためです。

flowchart TB
  CSM["Cloud Service Mesh"]
  subgraph Fleet["Fleet(Google Cloud上の管理単位)"]
    subgraph AppCluster["通常アプリGKE Cluster"]
      AppSidecars["各Podのsidecar proxy"]
    end
    subgraph PaymentCluster["決済専用GKE Cluster"]
      PaymentSidecars["決済Podのsidecar proxy"]
    end
  end
  AppSidecars -. "必要な通信だけ許可" .-> PaymentSidecars
  CSM -. "設定を配る" .-> AppSidecars
  CSM -. "設定を配る" .-> PaymentSidecars

まとめ

Service Meshは、マイクロサービス間の通信まわりの共通課題を、アプリの外側の層でまとめて解決する仕組みです。要点は以下です。

  • KubernetesのServiceとは別物で、通信そのものを制御する発展テーマ
  • 各Podにsidecarプロキシを添え、マイクロサービス間通信をsidecar経由にする
  • mTLSによる暗号化、トラフィック分割、リトライ・タイムアウト、可観測性を、アプリのコードを変えずに実現する
  • IstioやCloud Service Meshは実装で、目的は共通。まずは何を解決するかを押さえる
  • multi-cluster meshは、複数ClusterをまたぐService Mesh構成の発展テーマ

参考

タグ: マイクロサービス, システム設計