🕌

AWS ESC(Fargate)でNext.jsプロジェクトを自動ビルド&デプロイする方法

2022/02/26に公開

概要

AWS ESC(Fargate)で Next.js プロジェクトを、コンテナ環境に自動ビルド及び自動デプロイを行うポイントについて記載します。

前提

  • 同一 Git リポジトリ内で Dockerfile と Next.js のソースを管理している。
  • node_modules は.gitignore で git 管理対象外となっている。(package.json で管理)
  • ECS(Fargate)の環境自体は準備できていて、CodePipeline での自動デプロイはできる。

参考
https://zenn.dev/ttani/articles/aws-ecs-setup
https://zenn.dev/ttani/articles/aws-ecs-autodeploy
https://zenn.dev/ttani/articles/aws-fargate-setup

Dockerfile の準備

次のような構成で Dockerfile を配置します。
ポイントとしては、開発時に使用する Dockerfile をプロジェクトルートディレクトリに配置し、自動デプロイ用の Dockerfile は別フォルダで管理するという点です。
ローカル開発時には、docker-commpose.yml の設定を参照して、ローカルのボリュームをマウントし、dev モードで動作させていますが、検証/本番環境時ではビルドイメージの中に、ソースファイルを入れ、build と start を行っています。
Next.js のプロジェクトディレクトリは、/srcに格納されている前提とします。

プロジェクト構成

.
├ docker
│ └prod
│   └Dockerfile :本番/検証環境用Dockerfile
├ src :Nextプロジェクトソース用フォルダ
├ Dockerfile :ローカル開発用Dockerfile
├ docker-compose.yml :ローカル開発用docker-composeファイル
└ buildspec.yml : CodeBuild用設定ファイル
ローカル開発用Dockerfile
FROM node:17-alpine
WORKDIR /usr/app
docker-compose.yml
version: '3'

services:
  app:
    build: ./
    volumes:
      - ./src:/usr/app
    command: sh -c "yarn dev"
    ports:
      - "3000:3000"
本番/検証環境用Dockerfile
FROM node:17-alpine
WORKDIR /usr/app
COPY ./src /usr/app/
RUN yarn install && yarn build
EXPOSE 3000

CMD ["yarn", "start"]
  • COPY ./src /usr/app/でプロジェクトソースをコンテナ内に配置します。
  • RUN yarn installpackage.jsonに記載のライブラリを取得します。
  • yarn buildでプロダクト用ビルドを行います。
  • EXPOSE 3000で 3000 番ポートを公開します。
  • CMD ["yarn", "start"]でコンテナ起動時に、自動で Web サーバが立ち上がり Listen になるようにします。

buildspec.yml の準備

buildspec.yml で、プロジェクトルートディレクトリの Dockerfile は使用せずに、docker/prodディレクトリ配下のファイルを使用しています。(cp ./docker/prod/Dockerfile ./Dockerfileで起き変えています)

もし、環境ごとに使用するファイルを切り替える場合は、パス内のprodの部分についてCodeBuildプロジェクトの設定で、環境変数を定義して、それを参照するとよいでしょう。(環境変数を定義したうえで${project_env}のような形で参照)
今回は、本番と検証で Dockerfile 内容は共通である前提(そのほうがいいと思いますが)としています。

buildspec.ymlファイル内容
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com
      - REPOSITORY_URI=<aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/samplerepo
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - cp ./docker/prod/Dockerfile ./Dockerfile
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"container01","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json

<aws_account_id>部分には AWS アカウント ID を設定します。リージョンが違う場合はそこも変更します。
REPOSITORY_URIは ECR のリポジトリの URI を設定します。
container01の部分は、Docker イメージを参照するサービスのタスク定義のコンテナ名とします。

参考情報
buildspec.ymlcommandsのところで、リポジトリ URI を、REPOSITORY_URI=<aws_account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/samplerepoで直接指定していますが、コミットされたブランチによって、デプロイ先環境を切り替えるような Pipeline を組む場合、本記載を行ごと削除して、CodePipeline 側の環境変数(CodeDeploy 側の環境変数ではない)でREPOSITORY_URIを定義することで、Pipeline 単位で使用するリポジトリを切り替えることができます。
またimagedefinitions.jsonに対して受け渡しているコンテナ名"name":"container01"についても、printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.jsonのように書き換え、CodePipeline 側の環境変数CONTAINER_NAMEを定義して引き渡すようにすることで、動的に切り替えができます。

注意
ビルドプロセスの中で、docker Hub からイメージを PULL している場合、IP アドレス単位で時間当たりの PULL 数が制限されており、AWS の CodeBuild の IP アドレスが共有であるために、Limit に引っかかり PULL できない場合があります。その場合、CodeBuild で認証するプロセスを組み込む必要があります。
以下に対応方法を記載しましたので、参考にしてください。

https://zenn.dev/ttani/articles/aws-codebuild-docker-limit

タスク定義の作成

タスク定義を作成する際に、コンテナの定義ポートマッピング3000を指定します。
 ※EC2 タイプの場合ホストポートとコンテナポートのマッピングを指定できますが、Fargate の場合、ホストポートとコンテナポートは同一のポート番号しか指定できません。

サービスの作成

ターゲットグループに設定するセキュリティグループでの TCP3000 番ポートの許可

ECS のサービスを作成する際に、ターゲットグループに対してセキュリティグループを設定しますが、その際に使用するセキュリティグループにインバウンドで3000番のポートを許可するように設定します。

ロードバランス用のコンテナの 3000 番ポートとプロダクションリスナーポートをマッピング

ロードバランス用のコンテナに、タスク定義で作成した3000番ポートを公開しているコンテナを指定します。
また、その際にプロダクションリスナーポート80:HTTP(ALB 側でリスナーにするポート)を指定します。

あとは、CodePileline で Git リポジトリからソースを取得し、CodeBuild を行い、対象のサービスに対してデプロイするように設定すれば OK です。

まとめ

  • プロジェクトのルートディレクトリ直下の Dockerfile で、プロダクションビルドが走るように設定する。
  • コンテナは 3000 番ポートで Listen するようにする。
  • コンテナのターゲットグループでインバウンド 3000 番ポートを許可する。
  • サービス作成時に、ALB のリスナーポート(80:HTTP/443:HTTPS)とコンテナのポート(3000)をマッピングする。

Discussion