TerraformでAWS AppMeshを使ってECS Fargateのサービスを統合する(その3 仮想ルーター編)

# はじめに
AWS AppMesh記事第3弾。
[前回の記事](https://qiita.com/neruneruo/items/8177b70f4268c594561d)の続き。

今回は、これまでの記事のバックエンド側サービス(サービスB)に仮想ルータを導入し、Canary なデプロイを実現することを目指す(ELBを使わずサービスディスカバリを使った場合、こういったことをしないとクラウドネイティブなデプロイをするのは難しいと思われる)。

前回に引き続き、本記事を書いている 2021/1/3 時点で AWS AppMesh はまだまだ発展途上なサービスであり、情報が変わる可能性があることはご了承いただきたい。

# 仮想ルーター編で目指す構成
これまでの構成を以下のように変更する。

- これまでの構成
![figure2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/142bc2ce-a683-5402-c12e-b1bfc89eeed7.png)

- 今回の構成
![figure3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/dd055c17-4e90-85b6-5ee9-a4757109b2ee.png)

Canary なデプロイをするということで、もう一つ ECS のサービスを Cloud Map を連動させておく。
これを以降では、サービスBv2として扱う。
サービスBv2は、サービスBと見分けがつきやすいように、返却するコンテンツなり応答なりをサービスBと変えるようにしておこう。

今回は、サービスAについては特に変更することはない。
サービスBより先の部分が変わるため、サービスAとしては意識しなくても良い。

# AppMesh のサービスBに関連した項目の設定
AppMesh に関連したリソースを以下のように変更する。
ここで気を付けなければいけないのは、サービスBの ```aws_appmesh_virtual_service``` の ```name``` は、名前解決可能な DNS なり Cloud Map の名前になっている必要があるということだ。

また、サービスBの名前は、サービスAからの通信先として指定しているため、これを変更するにはサービスA側の設定変更も必要になる点について気を付けなければいけない。おいそれと「v1」みたいな名前をつけてしまうと、ずっとその名前を引きずることになるので気を付けよう。

```HCL
resource "aws_appmesh_virtual_service" "service_b" {
  name      = local.service_b_appmesh_service_name
  mesh_name = aws_appmesh_mesh.trial.id

  spec {
    provider {
      # 仮想ノード向けの通信を仮想ルーター向けに変更する
      # virtual_node {
      #   virtual_node_name = aws_appmesh_virtual_node.service_b.name
      # }
      virtual_router  {
        virtual_router_name = aws_appmesh_virtual_router.service_b.name
      }
    }
  }
}

resource "aws_appmesh_virtual_router" "service_b" {
  name      = local.service_b_appmesh_virtualrouter_name
  mesh_name = aws_appmesh_mesh.trial.id

  spec {
    listener {
      port_mapping {
        port     = 80
        protocol = "http"
      }
    }
  }
}

resource "aws_appmesh_route" "service_b" {
  name                = local.service_b_appmesh_route_name
  mesh_name           = aws_appmesh_mesh.trial.id
  virtual_router_name = aws_appmesh_virtual_router.service_b.name

  spec {
    http_route {
      match {
        prefix = "/"
      }

      action {
        weighted_target {
          virtual_node = aws_appmesh_virtual_node.service_b.name
          weight       = 4
        }
        weighted_target {
          virtual_node = aws_appmesh_virtual_node.service_bv2.name
          weight       = 1
        }
      }
    }
  }
}
```

```aws_appmesh_virtual_router``` については、サービスアプリケーションの待ち受けポートを指定する以外は特に難しいことはない。

これを、```aws_appmesh_route``` に紐付けてやれば良い。
```aws_appmesh_route``` の ```action``` ブロックでは、上記の通り、```weight``` 属性を指定することでトラフィックの振り分けが可能だ。ここは 50, 50 とか書いても良いし、上記のように比率で書いても良い。
上記は、サービスBv2に 20% のトラフィックが流れるようにした例だ。

# 動かしてみる
これで、NLB のパブリックドメインに対して curl で何度かトラフィックを入れてみると、サービスBとサービスBv2が混ざって返却されるようになる。

X-Ray のサービスマップでも、以下のようにサービスBとサービスBv2に振り分けられていることを確認できる。
![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1f31bf9a-8082-ab0c-fee2-ffa05e4d8e9c.png)

なお、最終的にサービスBは廃止してサービスBv2をメインにしていく場合、サービスBの Cloud Map のレコードを削除してしまうとサービスAから通信できなくなってしまう。
なんとかして、元のサービスBのAレコードにサービスBv2を接続するようにして、切り替えていくことが必要になると考えられる。
この辺の使い勝手はイマイチなので、もう少し改善されることを信じたい……