API Gateway + Lambdaのマルチリージョン構成をServerless Frameworkで作る

# 概要
Amazon API Gateway + AWS Lambda で作るサーバーレスなアプリケーションを、マルチリージョン構成にする。
普段から使っているServerless Frameworkで構築してみた。


# 参考
以下のAWSブログの記事が参考になる。記事自体は古いが構成は変わらない。

[Building a Multi-region Serverless Application with Amazon API Gateway and AWS Lambda](
https://aws.amazon.com/jp/blogs/compute/building-a-multi-region-serverless-application-with-amazon-api-gateway-and-aws-lambda/)

# 構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241369/d688f2de-348f-0872-2c08-4275efa9e15f.png)


- プライマリリージョンを `ap-northeast-1` , セカンダリリージョンを `us-west-2` とする。
- ルーティングポリシーは、フェールオーバーとする。
    - プライマリとセカンダリに対して定期的にヘルスチェックをし、プライマリのヘルスチェックが失敗したらセカンダリにルーティングする。


# 開発環境
言語: Python 3.8.3
デプロイ: Lambdaのデプロイには[Serverless Framework](https://www.serverless.com/)を使う

# 実装

## 事前準備
ドメインとSSL証明書を作成する。

- ドメインの作成とDNSサーバーは「Route 53」で用意する。
    - 今回のサンプルでは `example.com` とする。
- SSL証明書は「AWS Certificate Manager (ACM)」で作成する
    - リージョンごとに作成する

## プロジェクト作成
テンプレートで生成

```bash
sls create --template aws-python3
```

## serverless-multi-region-plugin プラグインをインストール

Serverless Frameworkの以下のプラグインを利用することで、マルチリージョン構成なサーバーレスアプリを構築することができる。

[serverless-multi-region-plugin](https://www.npmjs.com/package/serverless-multi-region-plugin/v/1.3.3)

バージョン固定しない場合は現時点でLatestである1.2.5-dns-3がインストールされるが、テンプレートに必要なリソースが足りない状態である。今回はバージョン1.3.3を利用する。

プラグインをインストールする。

```bash
sls plugin install --name serverless-multi-region-plugin@1.3.3
```

## serverless.yml編集

custom属性にプラグインの設定を記述する。そのほかは通常通り。

```serverless.yml
service: multiregion-example

provider:
  name: aws
  runtime: python3.8
  stage: dev

custom:
  # 作成されるAPI GATEWAY Methodの論理id
  gatewayMethodDependency: ApiGatewayMethodHelloGet

  dns:
    # 利用するドメインのRoute 53 ホストゾーンのid
    hostedZoneId: XXXXXXXXXXXXXXXXXXXXX
    # エンドポイントとなるサブドメイン。上記ホストゾーンにレコードが作成される
    domainName: ${self:service}.example.com
    regionalDomainName: ${self:custom.dns.domainName}
    healthCheckResourcePath: /${self:provider.stage}/healthcheck

    # リージョンごとの設定
    ap-northeast-1:
      # ACMで作成したSSL証明書のARN
      acmCertificateArn: arn:aws:acm:ap-northeast-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      failover: PRIMARY
    us-west-2:
      # ACMで作成したSSL証明書のARN
      acmCertificateArn: arn:aws:acm:us-west-2:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      failover: SECONDARY

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
  healthcheck:
    handler: handler.hello
    events:
      - http:
          path: healthcheck
          method: get

plugins:
  - serverless-multi-region-plugin
```

## プライマリリージョンへデプロイ

```bash
sls deploy --region ap-northeast-1
```

対象のAPI GatewayとLambdaがデプロイされ、Route 53のホストゾーンにフェールオーバールーティングポリシーを持ったPRIMARYのAレコードが追加されている。また、プライマリリージョンのAPI Gatewayのエンドポイントへのヘルスチェックも追加されている。

## セカンダリリージョンへデプロイ

```bash
sls deploy --region us-west-2
```

対象のAPI GatewayとLambdaがデプロイされ、Route 53のホストゾーンにフェールオーバールーティングポリシーを持ったSECONDARYのAレコードが追加されている。また、セカンダリリージョンのAPI Gatewayのエンドポイントへのヘルスチェックも追加されている。

## 確認
デプロイ後は、エンドポイントはプライマリリージョンのAPI Gatewayに向いている。

フェールオーバーの確認として、プライマリリージョンのAPI Gateway、またはヘルスチェック用のLambdaをスロットリングさせる。

しばらくすると、プライマリへのヘルスチェックが異常状態となり、セカンダリに切り替わる。エンドポイントへリクエストを送ってもエラーとならないことが確認できる。

# まとめ
Serverless Frameworkを使えば簡単にマルチリージョン構成を作ることができる。

serverless-multi-region-pluginの設定をさらに追加すれば、前段にCloudFrontを追加することもできる。
また、フェールオーバーの他にレイテンシーベースのルーティングポリシーを指定できる。 ただし、レコードセットの更新の際は一度スタック削除してから作り直す必要がある。

詳しくは[serverless-multi-region-plugin](https://www.npmjs.com/package/serverless-multi-region-plugin/v/1.3.3)参照

他のプラグインを使っている場合は、相性の悪いもの (例えば serverless-aws-alias) もあるようなので注意が必要。