こんにちは、ご無沙汰しています。 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公式ドキュメントより)
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つです。
- カスタムリソースからApp Meshリソースを作成
- Envoyサイドカーの注入
カスタムリソースからApp Meshリソースを作成
Kubernetes上でApp Meshを利用する場合は、AWSのApp Meshのリソースをそのまま作成・更新するのではなく、Kubernetes上にカスタムリソースを定義します。
App Mesh上の仮想ノード、仮想ルーターなどは、それぞれvirtualrouter.appmesh.k8s.aws
やvirtualnode.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のログが現れているときは、たとえば仮想ノードのバックエンドが正しく設定できていないかもわかりません。