😺

ECS自動デプロイ構成でNext.jsに環境変数を渡して使用する

2022/03/07に公開

概要

環境ごとに接続先情報や Secret など、使用したい値が異なる場合、ECS 環境でサービスごとなどで、環境変数をアプリケーション側に受け渡して使用します。
なお、セキュリティの関係でソースリポジトリ(Git)上にそれらの情報は持たないものとします。

前提構成

  • Next.js
  • ECS(Fargate)
  • GitHub
  • CodePipeline
  • CodeBuild

パターン検討

ECS のタスク定義内のコンテナ定義の中で、環境変数を定義するパターンと、コンテナのビルド時にコンテナ側に環境変数を渡しておく方法が考えられます。

この時、注意点があり、Next.js のサーバサイド処理内で使用する環境変数は、タスク定義で環境変数を渡すことができますが、クライアント側(クライアントで動作する JS)内で使用できるNEXT_PUBLIC_から開始される環境変数については、ビルド時に設定されるものなので、コンテナ起動後に使用されるタスク定義で環境変数を設定しても使用できません。

そのようなNEXT_PUBLIC_変数に関しては、ビルド時に、.env.productionファイルを生成して、その際に環境変数を渡すといった考慮が必要になります。
※なお、Dockerfile のENV設定でNEXT_PUBLIC_変数を渡しても、サーバサイドでは使用できますが、クライアント側では使用できません。

タスク定義に設定してサーバサイド処理に環境変数を渡す

  1. AWS コンソールよりECSを選択し、タスク定義で設定するタスクを選択します。
  2. タスク定義-コンテナ定義環境変数に対象の値を設定します。

以下のような方法で、サーバサイド処理内から呼び出せます。

環境変数 TEST を呼び出す場合

process.env.TEST

CodePipeline の環境変数に設定して、ビルド時に.env 系ファイルに環境変数を渡す(クライアント側へ渡す環境変数も含む)

1. CodePipeline の環境変数を設定する

CodePipeline のビルド時の環境変数の設定は、CodePipeline 初期設定時にビルドステージを追加するで設定を行うか、作成後に追加する場合は、AWS コンソールからCodePipeline-パイプラインに進み、対象のパイプラインを選択して、編集を押下して、Buildステージを編集する-編集アイコン押下で環境変数などの変更が行えます。
※設定後、子画面の完了ボタン →Build ステージの完了ボタン → パイプラインの保存ボタンまで押下しないと反映されませんので注意してください。

2. buildspec.yml に CodePipeline の環境変数を.env 系ファイルに追記する処理を追加する

CodeBuild 用の定義buildspec.ymlecho NEXT_PUBLIC_FOO=$HOGE >> ./src/.env.productionといった形で、ビルド前に env ファイルを環境変数を使用して生成する処理を入れるようにします。

build:
  commands:
    - echo Build started on `date`
    - echo Building the Docker image...
    - cp ./docker/prod/Dockerfile ./Dockerfile
    - echo NEXT_PUBLIC_FOO=$HOGE >> ./src/.env.production
    - docker build -t $REPOSITORY_URI:latest .
    - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG

これで、ビルド時に環境変数を Next.js のクライアント用のNEXT_PUBLIC_変数に渡すことができるようになります。

補足:パラメータストアの利用

AWS のパラメータストアを使用するとこで、環境変数として CodePipeline やタスク定義に直接 Secret などの情報を記載しなくても、安全に値を取り出すことができます。

AWS Systems Manager-パラメータストアで、パラメータを登録しておき、それを、CodePipeline やタスク定義側から参照する形になります。

タスク定義でパラメータストアを参照する場合

ECSタスク定義-コンテナ定義環境変数に設定する際に、ValueFromの区分を選択し、パラメータ側で設定したNameを設定します。

なお、この場合、ECS のタスク実行ロールに対して、IAM-ロールで、AmazonSSMReadOnlyAccessを付与する必要があります。
※パラメータ単位(パラメータのパス階層等)で制御する場合は IAM ポリシーで前方一致制御などを定義する必要があります。

ビルド時にパラメータストアを参照する場合

ビルド時にパラメータストアを参照する場合は、parameter-storeに対して、以下のように定義することで、参照できます。
※この例では、パラメータストアに登録された/sample/test/hogeを変数HOGEFUGAに格納して、echo HOGEFUGA=$HOGEFUGA >> ./src/.env.productionで Next.js 側に引き渡しています。

buildspec.yml
version: 0.2

env:
  parameter-store:
    HOGEFUGA: "/sample/test/hoge"
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...v1
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin <ACCOUNT ID>.dkr.ecr.ap-northeast-1.amazonaws.com
      - REPOSITORY_URI=<REPOSITORY_URI>
      - 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
      - echo HOGEFUGA=$HOGEFUGA >> ./src/.env.production
      - 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":"<CONTAINER NAME>","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

なお、この場合、codebuild のサービスロール(CodeBuild 設定した時に作成 or 選択したもの)に対して、IAM-ロールで、AmazonSSMReadOnlyAccessを付与する必要があります。
※パラメータ単位(パラメータのパス階層等)で制御する場合は IAM ポリシーで前方一致制御などを定義する必要があります。

Discussion