Webアプリケーションの一部をNuxt化するときのインフラのお話(CloudFront,ALB,S3)

# はじめに
この記事は[ペライチアドベントカレンダーの20日目](https://qiita.com/advent-calendar/2021/peraichi)の記事です。

ペライチでは一部マイクロサービスでサービス開発しており、一つのドメインに複数のアプリケーションがぶら下がる構成になっています。
サービス運営を長いこと続けているとどうしても環境の陳腐化に悩まされていたので、その解決のために一部づつモダンな環境に切り替えようと、あるアプリケーションのフロント部分をNuxt.jsで開発することとなりました。
そんな環境を実現するためのインフラ構築のお話です。


# 前提
- 旧環境ではCakePHP、Railsで構築された複数のアプリケーションが存在している。
- この内のRailsアプリケーションのフロント部分をNuxt.jsにリプレースする。
- CakePHP,Rails,Nuxt.jsのアプリケーションはそれぞれ同じドメインでサービス提供する。(ログイン状態を共有したい)
- Nuxt.jsはSSGで構築する。(今思えばSSRで作ってもよかった...。)

# 構成
赤字が今回新設した箇所になります。
![スクリーンショット 2021-12-08 20.43.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/5cb7ac52-4a55-5a5b-b8d6-50f0226d190b.png)

# やったこと
## 1. URLごとのルーティング追加
CloudFrontにNuxt.jsが配置されたS3へのルーティングと、Nuxtから実行するRails用EC2へのルーティングを追加します。
(旧環境ではRails用EC2は内部APIサーバとして、Cakeのアプリケーションから実行される構成でした。)

### 1_1. Orginの追加
NuxtとOriginを2つ追加します。
詳細な設定は以下を参考に設定をしました。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html
https://blog.serverworks.co.jp/tech/2020/05/11/post-84652/

ポイントとしてはカスタムヘッダーを付与するように設定をします。
![スクリーンショット 2021-12-08 16.24.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/545622f0-3d23-83ce-08fb-058e810f78bc.png)
ここの設定内容をもとに、S3やWAFの設定で指定のヘッダーを持っているときのみアクセスを許可する設定(後述)をすればCloudFront以外から直接ALBやS3にアクセスがあったときにアクセスを遮断することができます。

### 1_2. Behaviorの追加
※この設定を行うとルーティングが有効になるので、移行作業の最後に行うようにしてください。
構成図上[/hoge][/huga]としていますが、任意のURLに対してのBehaviorの設定を追加します。
![スクリーンショット 2021-12-08 16.35.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/fe3f6f63-0e2d-6569-8f5a-1ab8f4bdae14.png)
#### 注意点
1. Nuxtのアプリケーションはnuxt_assetsというディレクトリにjsを吐き出しますのでアクセスさせたいURLとは別に/nuxt_assetsというパスへのビヘイビアも追加が必要になります。
今回の構成では以下3つのビヘイビアを定義しました。
  - Nuxtアプリケーションの任意のURL
  - Nuxtのjsにアクセスさせるための/nuxt_assets
  - Rails用アプリケーションの任意のURL
1. アプリケーションでCookieやクエリパラメータを使いたい場合は各パラメータを付与する設定が必要です。今回はLegacy cache settingsを使いました。
![スクリーンショット 2021-12-08 16.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/a1e5b816-156f-8ae5-281b-61f2e94b2d56.png)
どのパラメータを渡すか等は個々の環境で検討すれば良いと思います。
参考: https://aws.amazon.com/jp/premiumsupport/knowledge-center/configure-cloudfront-to-forward-headers/

## 2. WAFでCloudFront以外から直接API実行をできないように制御
「Orginの追加」で追加したカスタムヘッダーをチェックするように指定します。
CloudFrontで設定したヘッダー名と値を間違えないように注意してください。
詳細な手順はこちらを参照いただくと良いと思います。
https://dev.classmethod.jp/articles/restrict-elb-origin-awswaf/

## 3. CloudFront以外から直接S3にアクセスできないように制御
「Orginの追加」で追加したカスタムヘッダーをチェックするように指定します。(S3バケット作成の手順は省略します。)
対象のS3バケットのバケットポリシーに以下のようなルールを入れてください。

```
{
    "Version": "2012-10-17",
    "Id": "xxxxxxxx",
    "Statement": [
        {
            "Sid": "xxxxxxxxxxxxx.",
            "Effect": "Allow",
            "Principal": "xxxxxxxxxxxxx",
            "Action": [
                "xxxxxxxxxxxxx",
                "xxxxxxxxxxxxx"
            ],
            "Resource": "arn:aws:s3:::バケット名を指定/*",
            "Condition": {
                "StringLike": {
                    "aws:CloudFrontで指定したHeader": "CloudFrontで指定した値"
                }
            }
        }
    ]
}
```
xxxxの箇所は以下を参考に適宜必要なルールを指定してもらえればと思います。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-use-case-4

CloudFrontからS3へのアクセス制御の方法としてOAIという方式があるのですが、
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

今回S3で静的ウェブサイトホスティングを利用しており、その場合だとOAIが使えないようで、下記の「アクセスが Referer ヘッダーで制限されたオリジンとして、ウェブサイトのエンドポイントを使用する」という方式で実装をしました。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudfront-serve-static-website/


## 4. 静的ホスティングの設定(fallback設定)
S3は静的ホスティングに設定してjsを配置しています。
![スクリーンショット 2021-12-08 20.38.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/189d063b-5ebf-8fab-f721-3d95ba49b0c6.png)

ここが最後にハマったポイントなのですが、Nuxtで動的ルーティングしたい場合は、fallback用のhtmlを返して上げる必要があります。
https://zenn.dev/crayfisher_zari/articles/ed0a69e45f1057

これをS3の静的ウェブサイトホスティング設定のエラードキュメントに指定してあげる必要がありました。
![スクリーンショット 2021-12-08 20.37.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71592/c008f456-4194-3881-fef6-88d54d42c53e.png)

※cloudfrontのカスタムエラーでも同様のfallback用htmlを返却することは可能ですが、cloudfrontでやってしまうとcloudfrontでルーティングされたあとの特定のパスだけではなく、全体のエラー発生時の挙動が変わってしまうので注意してください。(最後にこの問題にぶち当たり積んだと思いましたが、S3で設定できてよかった..。)

# 最後に
といった設定をして、同じドメインで一部パスをNuxtアプリケーションとして稼働させることができました。
レガシーなシステムをリプレースする場合、一気にリプレースすることは難しく徐々に移行していくケースが多いと思いますが、そのような環境作りの一助になれば幸いです。