サービスメッシュとAWS App Meshに入門した

こんにちは、ご無沙汰しています。 2020 年も終わろうとしていますが、この度サービスメッシュと AWS App Mesh に入門しました。

サービスメッシュ超入門

AWS App Mesh は、AWS 上のサービスでサービスメッシュを実現するための AWS サービスです。 サービスメッシュやその背景については、Tori さんによる「サービスメッシュは本当に必要なのか、何を解決するのか」の講演が詳しいです(この講演にも最後の 5 分くらいで App Mesh が登場します)。

サービスメッシュは、サービス間の通信のリトライ処理、レートリミット、サーキットブレイカーなどの仕組みを、クライアントやサーバー側で制御するのではなく、プロキシサーバーで解決します。 Envoy Proxyは、サービスメッシュのプロキシサーバーとして、広く利用されています。

Envoy Proxy へのリトライやレートリミットのポリシーの設定は静的ファイルでも設定できますが、 xDS API を使って Envoy Proxy の外で一括して管理できます。 この設定を管理(コントロールプレーン)と Envoy Proxy 本体(データプレーン)を分離する設計によって、Microservices など異なるチームで管理されているサービス間通信を、クライアント、サーバーのどちらにも手を入れずとも制御できるようになります。

Envoy ProxyとxDS

xDS API プロトコルはオープンな仕様なので誰でも実装できます。 AWS App Meshは、Envoy コントロールプレーンのマネージドサービスです。

App Mesh のコンセプト

AWS App Mesh は、サービスメッシュを構成するためのいくつかのリソースがあります。 そのリソースは AWS 上のリソースとして管理されます。 その構成に基づいて Envoy Proxy に設定が適用されて、サービスメッシュを構成します。

AWS App Meshの構成 (AWS公式ドキュメントより) [a]: https://docs.aws.amazon.com/app-mesh/latest/userguide/what-is-app-mesh.html

AWS App Mesh の構成要素はいくつかあります。 その中で代表的な物をいくつか紹介します。

  • メッシュ メッシュとは、サービスメッシュ内のサービス間トラフィックの論理的な集合です。 メッシュ内には、仮想サービス、仮想ノード、仮想ルーターなどを定義してサービス間の通信を定義します。
  • 仮想ノード 仮想ノードとは同一のサービスをまとめたものです。 EKS 上では通常、Deployment リソースと Service リソースに対応します。
  • 仮想ルーター 仮想ルーターは、リクエストの種類や重み付けによって送り先の仮想ノードを決定する、仮想的なロードバランサーです。
  • 仮想サービス 仮想サービスとは、仮想ノードから別のサービスに通信するための、サービスを抽象化したものです。 仮想サービスは仮想ノードまたは仮想ルーターによって提供されます。

これらの構成要素に対応する実体となるサービスが、Kubernetes 上にデプロイされるわけではありません。 これらの設定は各コンテナにサイドカーとしてデプロイされる Envoy Proxy の設定として適用されます。 たとえばサービスメッシュの管理者が、仮想ノードや仮想ルーターを追加したり設定を変更すると、その構成に応じた設定が Envoy Proxy に反映されます。

ネットワーク構成

App Mesh 内にデプロイしたサービスは全てサイドカーにデプロイされている Envoy Proxy を経由して通信します。 クライアント側はサイドカーの Envoy Proxy を経由してリクエストを送信し、それを相手のサービスの Envoy Proxy が受け取り、実際のサービスにリクエストを渡します。 この、クライアント、サーバー両方の Envoy Proxy を経由することで、App Mesh の複雑な構成や、リトライ処理、サーキットブレイカー、分散トレーシングなどを実現しています。

たとえば、あるサービスをカナリアリリースするために、v1 サービス/v2 サービスを、それぞれ 90%/10%で重み付きロードバランシングする例を考えます。 素朴な構成で重み付きロードバランシングするときは、v1/v2 サービスの前段にロードバランサーを配置すると思います。 App Mesh の場合はロードバランサーを、クライアント側の Envoy Proxy で実装されます。

重み付きロードバランシングの例

カスタムコントローラー

App Mesh を Kubernetes 上にデプロイするときは、カスタムコントローラ (appmesh-controller) を事前にデプロイする必要があります。 このカスタムコントローラの役割は主に 2 つです。

  1. カスタムリソースから App Mesh リソースを作成
  2. Envoy サイドカーの注入

カスタムリソースから App Mesh リソースを作成

Kubernetes 上で App Mesh を利用する場合は、AWS の App Mesh のリソースをそのまま作成・更新するのではなく、Kubernetes 上にカスタムリソースを定義します。 App Mesh 上の仮想ノード、仮想ルーターなどは、それぞれvirtualrouter.appmesh.k8s.awsvirtualnode.appmesh.k8s.awsなどのカスタムリソースとして定義します。 サービスメッシュ管理者は、サービスメッシュの構成を Kubernetes マニフェストとして定義します。

作られたリソースをカスタムコントローラがバリデーションなどをして、AWS 上のリソースとして反映します。 そして AWS 上のリソースが作成、更新されると、Envoy Proxy は xDS API 経由で設定を更新します。

Envoy Proxy の注入

もう 1 つ重要なのが、デプロイされる Pod に対しての Envoy Proxy の注入です。

ユーザーが既存のサービスから App Mesh に移行するとき、既存のコードに手を入れる必要はありません。 appmesh.k8s.aws/sidecarInjectorWebhook: enabled ラベルが付けられている Namespace に Pod がデプロイされるとき、カスタムコントローラ自動で Envoy Proxy をサイドカーとして追加します。 そのため既存の Deployment は手を加えること無く App Mesh 上でも利用できます。

これは Kubernetes のAdmission Containerという仕組みで動作します。 この記事では詳しく述べませんが、Admission Container とはリソースが作成される前に、リソースの設定値を検証したり、中身を書き換えることができます。

さて、既存のコードに手を加えることなく App Mesh が利用できると書きました。 しかし上記のネットワーク構成で説明したとおり、クライアントは自身のサイドカーにデプロイされている Envoy Proxy と通信します。 これを実現するために、カスタムコントローラは Init コンテナで、サービス起動前に Pod 内のネットワーク設定を書き換えます。 iptables を使って、Pod 内の通信を全て Envoy Proxy に転送するように設定します。

トラブルシューティング

App Mesh の背景にあるのは Envoy Proxy とマネージドなコントロールプレーンです。 Envoy Proxy の動作が透けて見えるようになったら、App Mesh も怖くありません。 ここでは自分が実際に App Mesh を触ってみたときの、トラブルシューティングのいくつかを紹介します。

構成がよくわからない

App Mesh は既存のサービスを元に透過的にサービスメッシュを利用できる反面、構成がわからないとまともに運用はできません。 まずは App Mesh の構成を知るところから始めましょう。

手始めにデプロイした Pod の情報を見てみましょう。 自分が管理していない Init コンテナやサイドカーコンテナがデプロイされているはずです。

$ kubectl describe pod my-service-a-xxxxxxxxxx-xxxxx

App Mesh の構成情報は全て、は Envoy Proxy の設定となって現れます。 つまり Envoy Proxy の設定を見れば、現在のサービスメッシュの構成が全て分かります。 Envoy Proxy は 9901 ポートに、管理用サーバーが立っています。 それを手元に port-forward することで、現在の設定を確認することができます。

$ kubectl port-forward my-service-a-xxxxxxxxxx-xxxxx 9901:9901

Web ブラウザで http://127.0.0.1:9901 で開くと、Envoy Proxy の現在の設定や統計情報が確認できます。

Connection reset by peer

これは HTTP 接続をしようとして、Envoy Proxy がコネクションをリセットした時に発生します。 この原因は、Envoy Proxy の設定と実際のサービスの設定が食い違っている時に表示されることが多いです。

App Mesh 利用をしていない素の Kubernetes では、ポート名が間違っていると TCP 接続すらできません。 一方で App Mesh を利用していると、一度全ての通信はサイドカーの Envoy Proxy が受け取るため、TCP 接続自体は確立できているのです。

接続先が正しいかどうかは、Envoy Proxy コンテナからの接続を試みてください。 Envoy Proxy コンテナからの通信は、クライアント側の Envoy Proxy を経由せずに相手の Pod に接続します。

kubectl exec my-service-b-xxxxxxxxx-xxxxx -cenvoy -- curl -sS -I my-service-a:8000

404 Not Found

これも Envoy Proxy の設定がうまくできてないときに発生します。 ステータスコード 404 を誰が返しているかは、レスポンスヘッダをみると分かります。 Envoy Proxy が返すレスポンスには、ヘッダーに server: envoy が乗っています。

またアクセスログを見ると、誰が 404 を返しているかも分かります。 クライアント側の Envoy Proxy のみが 404 のログが現れているときは、たとえば仮想ノードのバックエンドが正しく設定できていないかもわかりません。


Profile picture

Shin'ya Ueoka

B2B向けSaaSを提供する会社の、元Webエンジニア。今はエンジニアリング組織のマネジメントをしている。