re:Invent 2018 で発表されたAWS App Meshの仕組みを解説します

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2018 の投稿記事です。

こんにちは。re:Invent2018 帰りの大島です。

去年はEKSで大いに盛り上がったre:Inventですが、2018年はApp MeshCloud Mapの発表がコンテナ関連で注目を集めました。本記事では新しく発表されたApp Meshがどのような仕組みなのかを実際に動かしつつ、公式ドキュメントにも掲載されていない情報も含めて紹介します。

App Meshとは

AWS App MeshAWSで利用できるマネージドサービスメッシュです。Envoy proxyを使ってコンテナ化されたマイクロサービスの可視化やトラフィックコントロールを容易に出来るようになります。

AWS App Mesh is a service mesh based on the Envoy proxy that makes it easy to monitor and control containerized microservices. App Mesh standardizes how your microservices communicate, giving you end-to-end visibility and helping to ensure high-availability for your applications.

マイクロサービス化を進めるにつれて、徐々にサービス全体の可視化やサービス間の通信の細かい制御が必要になってきます。『サービスメッシュ』は、そのような問題を解決するためのコンポーネントです。サービスメッシュ自体は様々な方法で構築することはできますが、今回AWSからEnvoy proxyを利用したマネージドなものが出てきたということになります。

以降の章では、AWS公式のApp Meshのサンプルを使って全体像と詳細を深掘りしていきたいと思います。

公式サンプルの内容について

公式サンプルはこちら。基本的にexample配下のREADMEとexample/apps/colorapp配下のREADMEを見ながら進めれば動かせます1)とはいえ、まだPublic Betaなのと、regionも限定されていたり、そのままでは動かない部分などもあります。

構成図

基本的にシェルを実行することでCloud Formationでリソースが作成されていきます。

全体の構成はこのような感じです。VPCがあり、2つのpublic subnet、2つのprivate subnetがあって、ECS ClusterがEC2で構築されます。ここのECS部分にApp Meshを構築します。

ECS部分の各サービスはこのような構成になっています。Color Gatewayというサンプルアプリは内部でColor Tellerというサンプルアプリに対してhttpリクエストをします。そして返ってきた色を基にメモリ内に結果を保存していき、単に返ってきた色と今までの結果をJSONで返すだけという、単純なgolangアプリケーションです。

Color Gatewayにcurlでアクセスするとこのようなjsonが返ってきます。

$ curl -s http://colorgateway.default.svc.cluster.local:9080/color
{"color":"white", "stats": {"white":1}}

ECSのコンソールの様子はこちら。

このサンプルの目的

ECS ServiceとALBやECS Service Discoveryを組み合わせればもちろん内部通信は可能です。ただし、その場合はColor Gatewayアプリの環境変数を書き換えてコンテナを再デプロイしないといけません

そこでApp Meshを使うことでColor Gatewayは環境変数で指定されている同一エンドポイントを指定したまま、コンテナを再デプロイすることなく、Mesh側でトラフィックを別のサービスにルーティングすることができます。さらに、単に向き先を変更するだけでなく、重み付けに応じてトラフィック先を動的に変更することもできます。

どのように上記のことを実現しているのかについて、公式サンプルを使ってより詳細に説明していきます。

公式サンプルを使ったApp Mesh用語の説明

App Meshの用語や概念を理解するために公式サンプルを基に説明します。大きく分けると、MeshsVirtual NodeVirtual RoutersRoutesという要素です。

Meshs

Meshは、『Virtual Node』『Virtual Routers』『Routes』という要素を内包する論理的なネットワークのグループです。今回のサンプルでは、defaultという名前でMeshをcreateしていきます。2018年12月現在、App Meshはプレビュー版なのでAWSコンソールから作ることはできず、CLIからのみになります。

$ aws appmesh create-mesh --mesh-name default

この段階では、単に論理的なグループを作っただけだと思ってください。

Virtual Node

続いてVirtual Nodeを作っていきます。Virtual Nodeは『Listener』『Backends』『Service Discovery』という概念を持っています。

要素 用途
Listener トラフィックを受け付けるためのリスナー
Backends トラフィックをどこに流すかの設定
Service Discvoery Virtual Node自体にアクセスするためのDNS名

例として、Color Gatewayのvirtual nodeの設定はこのようなJSONで表現されます。

{
  "spec": {
    "listeners": [
      {
        "portMapping": {
          "port": 9080,
          "protocol": "http"
        }
      }
    ],
    "serviceDiscovery": {
      "dns": {
        "serviceName": "colorgateway.default.svc.cluster.local"
      }
    },
    "backends": [
      "colorteller.default.svc.cluster.local"
    ]
  },
  "virtualNodeName": "colorgateway-vn"
}

http 9080ポートで待ち受け、Cloud Mapによって設定されたcolorgateway.default.svc.cluster.localという名前でService Discoveryを設定し、これまたCloud Mapで設定されたcolorteller.default.svc.cluster.localにリクエストを流すと設定しています。

図に表すと下図の関係性です。

基本的にVirtual NodeはECS Task内にsidecarで立てたenvoyコンテナを指しています。また、それぞれのECS ServiceはCloud Map経由でService DiscoveryされてRoute53が設定され、それらのレコードがVirtual NodeのService DiscoveryとBackendsに指定されています。

ECS Task内のコンテナについて

ECS Task内にproxyinitenvoyappと3つのコンテナを示していますが、appは今回のgolangのサンプルアプリのことです。proxyinitenvoyについても自分でTask Definitionに含めて起動する必要がありますが、イメージはAWSから提供されています。proxyinitの役割についても次の章で後ほど詳しく説明します。

Virtual Routers

Virtual Routerも論理的なリソースを表現しており、Mesh内のTrafficをハンドリングするための箱のようなものです。Virtual Routerには実際のRoutingの設定は書かずに名前を設定するだけです。ApacheやNginxでいうVirtualHostのようなものだと思うと理解しやすいかもしれません。

例として実際のcolor-gatewayからアクセスされる側のcolor-tellerのVirtual RouterのJSONの設定ファイルです。

{
  "spec": {
    "serviceNames": [
      "colorteller.default.svc.cluster.local"
    ]
  },
  "virtualRouterName": "colorteller-vr"
}

Routes

Routeは『どのパスをどのVirtual Nodeにroutingするか』を書く部分です。実際の設定ファイルを見るのが一番理解しやすいかと思います。サンプルの最初のRouteの設定は以下のようになっています。

{
  "routeName": "colorteller-route",
  "spec": {
    "httpRoute": {
      "action": {
        "weightedTargets": [
          {
            "virtualNode": "colorteller-vn",
            "weight": 1
          }
        ]
      },
      "match": {
        "prefix": "/"
      }
    }
  },
  "virtualRouterName": "colorteller-vr"
}

これは全てのトラフィックがcolor-teller(whiteを返すサービス)に流れることを意味しています。

この状態から、routesをaws appmesh update-routeコマンドで以下のblueとredに割り振るルーティングのもので更新してみます。

{
  "routeName": "colorteller-route",
  "spec": {
    "httpRoute": {
      "action": {
        "weightedTargets": [
          {
            "virtualNode": "colorteller-blue-vn",
            "weight": 8
          },
          {
            "virtualNode": "colorteller-red-vn",
            "weight": 2
          }
        ]
      },
      "match": {
        "prefix": "/"
      }
    }
  },
  "virtualRouterName": "colorteller-vr"
}

この設定ファイルでは、colorteller-vrというVirtual RouterにRouteを設定しています。URIのPathに/がmatchしたリクエスト(つまり全てのリクエスト)を、blueに80%、redに20%のトラフィックをroutingするという設定です。

こうすることでcolor-gatewayからcolor-tellerへアクセスするエンドポイントは変更することなく、トラフィック先を柔軟に切り替えることができます。

ここまででApp Meshの各要素について公式サンプルをもとに説明してきました。次節では、Envoyがどのようにトラフィックのルーティングを実現しているのかを見ていきます。

Envoyを使ったサービスメッシュの実現方法

サービス間通信にEnvoyをはさむ

color-gatewayからcolor-tellerへの通信を例にとると、App Meshは全てのECS TaskにsidecarとしてEnvoyを置くことで実現しています。Envoyのドキュメントでは、受信する通信のことをIngress、外に出ていく通信をEgressと呼びますが、全てのサービス間通信に必ずEnvoyを挟むような通信経路をとります。

こうすることで、アプリ側には一切手を入れずに全てのサービス間の通信をトレーシング出来たり、途中で柔軟にトラフィックのルーティングを切り替えたり出来るというわけです。

ECSのTaskでの実現方法

App Meshでサービスメッシュをするためには、自分が動かしたいコンテナとは別にEnvoyともう一つaws-appmesh-proxy-route-managerというDockerイメージも同一Task内で起動する必要があります。実際に起動してるTaskのコンソールのキャプチャ画像はこちら。

proxyinitと表示されているのが該当のコンテナです。このコンテナがしていることは、起動時にiptablesを変更し入ってくる全てのトラフィックをenvoyに、出ていくトラフィックをenvoyに向けるように調整しています。

ECSのawsvpcモードにおけるタスクネットワークは、新しいENIをアタッチし別のLinuxのネットワーク名前空間を作成します。このネットワーク名前空間はデフォルトの名前空間とは別なので、iptablesを変更することでこのTaskへの通信を全てEnvoyを通すようにすることを実現しつつ他のTaskには影響しないようになっています。

proxyinitの一部のTask Definitionのパラメータはこのようになっています。iptablesを変更するため、NET_ADMINが追加されているのと、iptablesを設定する際の各種変数などを環境変数に設定していますが、このあたりもおそらくGAになる頃にはCNI Plugin経由で行われるようになるのでユーザーは意識しなくてよくなるとは思っています。

        - Name: "proxyinit"
          Image: { Ref: SideCarRouterManagerImage }
          Essential: false
          ...
          LinuxParameters:
            Capabilities:
              Add:
                - "NET_ADMIN"
          Environment:
            - Name: "APPMESH_START_ENABLED"
              Value: "1"
            - Name: "APPMESH_IGNORE_UID"
              Value: "1337"
            - Name: "APPMESH_ENVOY_INGRESS_PORT"
              Value: "15000"
            - Name: "APPMESH_ENVOY_EGRESS_PORT"
              Value: "15001"
            - Name: "APPMESH_APP_PORTS"
              Value: "9080"
            - Name: "APPMESH_EGRESS_IGNORED_IP"
              Value: { Ref: AppMeshEgressIgnoredIpCsv }

なお、AWSのECSのタスクネットワークについては、こちらのブログが非常に参考になりました。

EnvoyとControl Plane部分の通信

App Meshの要素としてはVirtual NodeやRoutesなどで表現されていましたが、実際はこれらの設定はService MeshにおけるControl Planeと呼ばれる部分に対して登録されています。このControl Planeと各TaskにいるEnvoy(ここをData Planeと呼ぶ)が情報をやりとりすることで、サービスメッシュは成り立っています。

App MeshはEnvoy Proxyを利用しており、『Control Plane <-> Data Plane』間の通信はgRPCを利用してAggregated Discovery Services (ADS)で設定を取得しています。

コンテナインスタンスにsshして実際のenvoyの設定ファイルを取得したものは下記のようになっていました。

admin:
  access_log_path: /tmp/envoy_admin_access.log
  # Provides access to: http://<envoy hostname>:9901/config_dump
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }
node:
    id: mesh/default/virtualNode/colorteller-red-vn
    cluster: mesh/default/virtualNode/colorteller-red-vn
dynamic_resources:
  # Configure Envoy to get listeners and clusters via GRPC ADS
  ads_config:
    api_type: GRPC
    grpc_services:
      google_grpc:
        target_uri: appmesh-envoy-management.us-west-2.amazonaws.com:443
        stat_prefix: ads
        channel_credentials:
          ssl_credentials:
            root_certs:
              filename: /etc/pki/tls/cert.pem
        credentials_factory_name: envoy.grpc_credentials.aws_iam
        call_credentials:
          from_plugin:
            name: envoy.grpc_credentials.aws_iam
            config:
              region: us-west-2
              service_name: appmesh
  lds_config: {ads: {}}
  cds_config: {ads: {}}

上記の図でいうManagement Serverがtarget_uriの部分ということですね。node.idには、virtual nodeの名前が指定されていることが分かると思います。
この辺りの設定を利用して識別しているのだとは思いますが、EnvoyのxDSプロトコル部分についてはまだ理解が浅く分からないことが多いので、ADSがなんなのかをツイート等でコメントいただけると嬉しいです。

まとめ

公式サンプルをもとにApp Meshについて理解を深めてきました。Envoyやネットワーキングについても説明してきたので難しそうという印象を持たれたかもしれません。しかしApp Meshを使うだけであれば、JSONでroutingの設定をするだけなので、マネージドサービスのメリットを享受できるはずです。

一方、(2018年12月時点で)Public β Ver.なので、routesをupdateすると500エラーが返ってきたり反映されなかったりといった事象が起きることも確認しています。まだまだこれから発展途上という感想を持ちました。執筆時点ではトラフィックルーティングしか実装されていませんが、内部にEnvoyを使っている以上、Envoyでできること(retry、circuit breakingなど)は実装されさえすれば、必ず出来るようになると言えます。

機能面で言えば、Istioのような有名どころが持っている機能はまだまだありませんが、ロードマップを見る限りこれからもりもり開発されていくと期待できるプロダクトです!

脚注

脚注
1 とはいえ、まだPublic Betaなのと、regionも限定されていたり、そのままでは動かない部分などもあります。
関連職種の採用情報
詳しくはこちら