Next.jsをproduction環境で使うために外観を掴んでおきたいと思い、Next.jsアプリケーションを動かすAWS環境をaws-cdkを使って構築するサンプルを作ってみた。だいぶ荒削りだけど、参考になる例にはなったと思う。
https://github.com/shibayu36/nextjs-on-ecs
利用した技術
今回作ったアーキテクチャ
- 全てのリクエストをCloudFrontに通すフルCDNアーキテクチャ
- next buildで生成した静的ファイルはS3から配信する
- Next.jsのSSR用サーバはコンテナ化して、Fargateで動かす
- 簡単のため全てpublic subnetに配置した
図を書くとこんな感じ
初期構築方法と継続的なデプロイ方法
README.md に書いた。
初期構築は以下のようにして行う。誰でも使えるようにECR_BASEがあるせいで複雑になっているが、通常はECRのレポジトリURLはハードコードしたら良さそう。
$ yarn install $ yarn workspace cdk run cdk bootstrap $ yarn workspace cdk run cdk deploy VpcStack SecurityGroupStack RepositoryStack $ ECR_BASE=<your ecr base> ./build-image-and-push.sh $ yarn workspace cdk run cdk deploy --context application-version=$(git rev-parse --short HEAD) NextServerStack StaticStack $ ECR_BASE=<your ecr base> ./deploy.sh
初期構築した後に、継続的にデプロイするのは以下のコマンド一発でできる。
ECR_BASE=<your ecr base> ./deploy.sh
技術的なポイント
今回構築のメインのポイントは以下の通り。
- Next.jsアプリケーションのコンテナイメージを作る
- Next.jsのnext buildで生成した静的ファイルをどう配信するか
- aws cdkを使って、CloudFront + S3 + ECSの構成を作る
- どうやってECSへ継続的にデプロイするか
Next.jsアプリケーションのコンテナイメージを作る
Next.jsアプリケーション用のDockerイメージを作る - $shibayu36->blog; に書いてあるとおり。基本はデプロイするコンテナにはpackage.json、yarn.lock、.next/を入れておき、yarn installでproductionに必要なものにみ入れておくと良い。またnext.config.jsを使っている場合はそれもコンテナに入れておくと良さそうだった。
Next.jsのnext buildで生成した静的ファイルをどう配信するか
ユーザーがブラウザでアクセスした時にダウンロードする静的ファイルはCloudFront + S3で配信したい。next buildで作った成果物の中には、SSR用サーバのためのファイル群と、ユーザーがブラウザからアクセスする際に使われる静的ファイル群が存在するので、後者をS3に配置すれば良い。
ブラウザでアクセスした時には/_next/static/*
のURLで静的ファイルを取得していて(例: _next/static/8VBMV8AYTxMJOTzvzTfZj/pages/index.js
)、これは.next/static/
以下のファイルに対応するので
aws s3 sync ./.next/static s3://nextjs-on-ecs-static-bucket/_next/static
のようにして、S3に静的ファイルをアップロードしておく- CloudFrontの設定で、/_next/static/*のpathは、S3のnextjs-on-ecs-static-bucketへ転送する
のようにしたら、CloudFront + S3配信が出来た。
aws cdkを使って、CloudFront + S3 + ECSの構成を作る
これはコードを見るのが早そう。
- Next.jsアプリケーションが動くECSの構築
- 今回は勉強のためかなり手作りしたが、aws-ecs-patternsのApplicationLoadBalancedFargateServiceを使うでも良さそう
- S3とCloudFrontの構築
- S3とCloudFrontは責務の分離のためにstackを分けたかった。しかし、S3にアクセス権限を付与する部分(CloudFrontのオリジンアクセスアイデンティティを介してS3にアクセス権限を付与)と、CloudFrot -> S3へのルーティングを作る部分で循環参照を起こしてしまい、どうやったらスタック分割出来るか分からなかった。そこで諦めて一つのstackとした
どうやってECSへ継続的にデプロイするか
ecspressoなどのECSへのデプロイツールを別に用意する必要もあるが、今回はaws-cdkをデプロイにも用いることにした。デプロイ時はaws-cdkのcontext機能を使って、どのイメージタグをデプロイするか決定する。
流れとしては以下のようになる。deploy.sh一発で全て動くようにしている。
- gitのrevisionをイメージタグとして、build & ECRへpushする
- buildしたイメージから.next/ディレクトリを取り出し、静的ファイル部分をS3にアップロードする
- aws-cdkのcontextのapplication-versionにビルドしたイメージタグを渡して、NextServerStackを更新
- cdk.context.jsonにapplication-versionを記録しておきたいので、sedを使って置換する
- 今はcdk.context.jsonをいい感じに上書きする手法がなさそう?
#!/bin/bash set -ex export DOCKER_BUILDKIT=1 : ${REVISION:="$(git rev-parse --short HEAD)"} : ${ECR_BASE:=648803025740.dkr.ecr.ap-northeast-1.amazonaws.com} docker build --file Dockerfile.server --target nextjs-on-ecs-server --tag "nextjs-on-ecs-server:$REVISION" --progress plain . $(aws ecr get-login --no-include-email --region ap-northeast-1) docker tag "nextjs-on-ecs-server:$REVISION" "$ECR_BASE/nextjs-on-ecs-server:$REVISION" docker push "$ECR_BASE/nextjs-on-ecs-server:$REVISION" echo "$ECR_BASE/nextjs-on-ecs-server:$REVISION"
#!/bin/bash set -ex : ${REVISION:="$(git rev-parse --short HEAD)"} export REVISION export ECR_BASE ./build-image-and-push.sh # Next.jsで作った静的ファイルをS3に上げる rm -rf ./dist/ mkdir ./dist/ CONTAINER_ID="$(docker create "nextjs-on-ecs-server:$REVISION")" docker cp ${CONTAINER_ID}:/app/.next ./dist/ docker rm -v ${CONTAINER_ID} aws s3 sync ./dist/.next/static s3://nextjs-on-ecs-static-bucket/_next/static # Nextのサーバが動くECSを更新 yarn workspace cdk run cdk deploy --context application-version=$REVISION NextServerStack # cdk.context.jsonをデプロイしたrevisionに更新しておく sed -i '' -E "s/\"application-version\": \"[^\"]+\"/\"application-version\": \"$REVISION\"/" ./cdk/cdk.context.json
まとめ
今回はNext.jsアプリケーションを動かすAWS環境をaws-cdkを使って構築するサンプルを作ってみた。Next.jsのデプロイ、AWSの要素技術、Dockerのマルチステージビルドやキャッシュの仕組み、aws-cdkによる構築の勉強になってかなり良かった。
感想としては
- aws cdkによる構築はだいぶ体験が良い
- CloudFormationで構築するのかなり辛かったので...
- ECSのデプロイ方法は悩む。ベストプラクティスが知りたい
- DockerのBuildKitによるキャッシュの仕組みは便利
- Nextのデプロイに関するドキュメントがあんまりないので、勘でやってる...
参考
- https://github.com/shibayu36/nextjs-on-ecs
- フルCDNアーキテクチャ実験 / Minami Aoyama Night #1 - Speaker Deck
- フルCDNアーキテクチャでサービス設計した話 - Speaker Deck
- Next.jsアプリケーション用のDockerイメージを作る - $shibayu36->blog;
- Amazon ECS入門 〜公式のDockerイメージを使って10分で構築してみる〜 | DevelopersIO
- AWS ECSでDockerコンテナ管理入門(基本的な使い方、Blue/Green Deployment、AutoScalingなどいろいろ試してみた) - Qiita
- AWS Cloud Development Kit (AWS CDK)でECS環境を構築してみた | DevelopersIO
- AWS CDKを使って ほぼ一撃で静的サイトを構築する - Qiita
- [AWS] Amazon CloudFrontのキャッシュ無効化(TTL設定) | DevelopersIO
- CloudFrontでマルチオリジンとCache Behavior設定してみた | DevelopersIO
- [CloudFront + S3]特定バケットに特定ディストリビューションのみからアクセスできるよう設定する | DevelopersIO