AWS 東京と大阪のDR設計時の工夫

# AWS 東京と大阪のDR設計時の工夫
東京リージョンから大阪リージョンへのフェイルオーバーを実現するDR設計を行いました。
そこまで複雑なAWSアーキテクチャではなかったですが、それでも色々苦労した点がありました。
ここではDR設計時の工夫点を記載しております。

## DRの要件
1. RTO/RPOは2時間
1. 多数の外部システムからアクセスを受けるので、できる限り外部システムに変更を加えることなく、DRを実現する
1. 極力SAMおよびCloudfomrationを利用して構築する。ただし、妥当な理由があれば手作業も可とする

## AWS 構成図
API Gateway、Cognito、Lambda、DynamoDB、S3を利用した一般的なサーバーレスアーキテクチャを採用したシステムです。
図には表現しておりませんが、かなりの数のリソースをプロビジョニングしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519657/0a29b2fa-40a7-9f44-96e8-563c00fd7e1a.png)

## API Gatewayの設計
パブリックなREST APIのAPI Gatewayを使用しました。この機能は両方のリージョンで利用できます。
DRで問題となったのは、APIキーでした。
**Cloudforamtionを利用してAPI Gatewayを作成するとAPIキーにランダムな文字列が自動で割り当てられて、任意の文字列を指定することはできません。**
なので、単純に東京リージョンで作成したCloudforamtionを大阪リージョンで実行すると、APIキーが一致せず、外部システムからリクエストを受け付けることができません。
解決策として、API Gateway作成後に手作業でAPIキーの値を修正することにしました。Lambdaのカスタムリソースを作成すればCloudforamtionで完結させることができますが、今回のシステムではこの後API Gatewayを追加する予定がなかったため、手作業で十分と判断しました。

### 補足
今回のアーキテクチャでは使用していませんが、HTTP APIは大阪リージョンでは使用できません。
また、REST APIプライベートは、以前は大阪リージョンで使用できませんでしたが、現在は使用できるようになっています。
大阪リージョンでどのサービスが使用できるのかを調べるには、以下の公式サイトを参照します。ただし、あくまでサービス単位での記載なので、機能単位(API GatewayでいうとHTTP APIなど)ではないので、きちんとPoCを実施して機能単位まで確認しておく必要があります。

https://aws.amazon.com/jp/about-aws/global-infrastructure/regional-product-services/

## Cognitoの設計
2023年9月にCognitoが大阪リージョンで利用可能になりました。
今回はClient Credentials Grantを利用しています。
リージョンを問わず、**クライアントIDとクライアントシークレットはCognitoから自動的に発行され、変更することができません。**
つまり、API GatewayのAPIキーと異なり、手作業で編集することはできません。

そこで今回は、大阪リージョンにのみCognitoの間に代理Lambdaを挟み込み、アクセストークンを取得を代理Lambdaで行わせるようにしました。
Secrets Managerには東京リージョンのクライアントIDとクライアントシークレットと同じ値を保管し、代理Lambdaは外部システムから届いたリクエストに含まれているクライアントIDとクライアントシークレットが、Secrets Managerに保管されている値と同じか確認します。
同じであれば、代理LambdaがCognitoにアクセスしてアクセストークンを取得し、リクエスト元に返却するよう実装しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519657/772b54b1-63f8-2dbc-8eaf-8624c7d23596.png)
1. ユーザーは東京リージョンのクライアントIDとクライアントシークレットと同じ値で代理Lambda用API Gatewayにアクセスする
1. Secrets Managerには東京リージョンのクライアントIDとクライアントシークレットの値が保管されている。代理LambdaはSecrets Managerの値とリクエストからもらった値が同じか検証する
1. ②で同じ値と検証されたら、代理LambdaはCognitoにアクセスし、トークンを取得する。そしてリクエスト元にトークンを返却する
1. ユーザーは取得したトークンで、API Gatewayにアクセスする
1. API GatewayはオーソライザーとしてCognitoを利用しており、トークンをCognitoで検証する
1. トークンに問題がなければ、リクエストを受け付け、後続のLambdaが起動する

### 補足(Cognito User Profiles Export リファレンスアーキテクチャ)
ユーザープールをリージョン間で同期させたい場合、以下の"Cognito User Profiles Export リファレンスアーキテクチャ"が参考になります。ただし、今回は以下の理由で使用を見送っています。
* 外部システムの数が変動する予定は今の所ない
* Client Credentials Grantでは使用できなかった

https://aws.amazon.com/jp/solutions/implementations/cognito-user-profiles-export-reference-architecture/

## Lambdaの設計
SAMで実装しています。Lambda自体にはDR時に課題はなかったです。
Lambdaに限った話ではありませんが、大阪リージョンでのみ使用するAWSリソースがあります。
なので、大阪リージョンでのみ作成するリソース用のGitリポジトリ、およびCI/CDパイプライン(CodePipeline)を分けて作成する必要がありました。

### 補足 CI/CDパイプライン(CodePipeline)
* 2023年11月に大阪リージョンでも使用できるようになりました

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-codepipeline-seven-regions/

* Lambdaのコードはモノリポジトリで管理していたので、以下のAWS Prescriptive Guidanceを参考に実現しました

https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/automatically-detect-changes-and-initiate-different-codepipeline-pipelines-for-a-monorepo-in-codecommit.html

* 現在ではCodePipelineのトリガーフィルターを利用すると、モノリポジトリでも簡単にCodePipelineを実行することができます。ただし、大阪リージョンでは現時点で未対応のため、DRの設計に組み込むことはできません

https://aws.amazon.com/jp/about-aws/whats-new/2024/02/codepipeline-trigger-filters-execution-modes/

## DynamoDBの設計
DynamoDBはRPOが2時間であったため、当初はAWS Backupを利用してバックアップとリストアを行おうとしていました。
しかし、AWS Backupはテーブルごとにバックアップが取得されるため、全テーブルの断面を揃えることができませんでした。
なので、RTO/RPO観点からは少々過剰なDyanmoDBグローバルテーブルを利用することにしました。

## IAMロール
東京リージョンのLambdaは東京リージョンのリソースにしか触れず、大阪リージョンのLambdaは大阪リージョンのリソースにしか触れないように権限制御を行おうとしておりました。つまり、東京と大阪でIAMロールは別々にする手筈でした。
しかし、IAMロールのロール名にリージョン名を入れておらず、大阪リージョンにてCloudfomrationを実行した時に、すでに同名のロールがあるとエラーになりました。
S3のバケット名は意識していましたが、グローバルリソースであるIAMロールも意識していないといけませんでした。

## まとめ
CognitoやCodePipelineを大阪リージョンでも使用できるようになり、東京リージョンと同じ構成を取りやすくなってきました。ただ、大阪リージョンで使用できない機能も存在するため、事前の調査をしっかり行う必要があります。大阪リージョンで利用できない機能は明日にも使用できる可能性があるため、代替機能の作り込みをどこまで行うのかの意識合わせを、ステークホルダー間で行っておくべきだと思いました。