動機
Next.js のソースコードをプッシュしたら、自動でビルド、ECS へのデプロイを行う仕組みを CodePipeline で作りたかったのだけど、AWS の記事は UI が違っていたりして、とても苦労しました。
そこではじめて CodePipeline を触る人のためになればと思い、構築方法を残します。
自分では Next.js を使っていましたが、どのフレームワークでもほぼ同じはずです。
また環境変数のようにソースコードでも AWS 側でも設定できるものは、今回は分かりやすさのためにソースコード側で行うようにしました。
流れ
デプロイまでの流れは以下の通りです。
- CodeCommit にソースコードを push する
- CodeCommit の変更を検出して CodeBuild が Docker イメージをビルドを開始する
- Docker イメージを ECR に push する
- ECR から ECS にデプロイ
また CodeCommit と CodeBuild が成果物を保存するのに S3 を使っています。
前提
Docker や Node.js や AWS CLI はあらかじめインストールしておきます。
私は WSL2 で作業していますが、たぶんどの OS でもあまり変わらないと思います。
ここではローリングアップデートでの自動デプロイを紹介します。
ブルーグリーンデプロイとはファイルや設定が異なるので注意してください。
React のプロジェクトを作成
アプリのひな形を作成
ここではデフォルトの my-app
という名前で作成します。
TypeScript、ESLint、Tailwind CSS などのオプションは全部デフォルト値のままにしました。
npx create-next-app@latest
cd my-app
ファイル追加
Dockerfile
と buildspec.yaml
をルートディレクトリ(package.jsonと同じところ)に追加します。
Dockerfile
とりあえず動いていますが、もっと良い書き方があるかもしれません。
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
EXPOSE 3000
USER node
CMD ["npm", "start"]
buildspec.yaml
REPOSITORY_NAME
には、この後出て来る ECR のリポジトリ名を入れます。
同様に CONTAINER_NAME
には ECS で使うコンテナ名を入れます。
version: 0.2
env:
variables:
REPOSITORY_NAME: my-ecr-repo
CONTAINER_NAME: my-container
phases:
pre_build:
commands:
- echo $REPOSITORY_NAME
- echo $CONTAINER_NAME
- AWS_ACCOUNT_ID=$(echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :)
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$REPOSITORY_NAME
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Build started on `date`
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json
CodeCommit
リポジトリを作成したら git clone を行います。
私は HTTPS (GRC) を使っていますが、リポジトリが空だと各方式での git clone のやり方が書いてあるので、迷わないと思います。
私は多要素認証があるので一手間(get-session-token)かかっています。下記のページにあるスクリプトを利用させてもらってます。
aws sts get-session-token --serial-number arn:aws:iam::111111111111:mfa/mkt1234 --profile mkt1234 --token-code 112233 | source set-aws-session-token
git clone codecommit::ap-northeast-1://my-repository
先ほど作成した Next.js のファイルを追加し push します。
git add .
git commit -m "add app"
git push
ECR (Elastic Container Registry)
リポジトリを作成します。作成する名前は buildspec.yaml
に書いた REPOSITORY_NAME
と同じものにします。
VPC
以前書いた記事を参考に作成します。
ここでは my-vpc
という名前の VPC を作成します。
セキュリティグループのインバウンドルールに 80 番ポートを足すのを忘れないでください。
ECS
下記ページを参考にクラスターとタスク定義とサービスを作成します。
クラスター
my-cluster
という名前でクラスターを作成します。
タスク定義
タスク定義のコンテナ名を buildspec.yaml
に書いた CONTAINER_NAME
と同じものにします。
イメージ URI は作成した ECR のリポジトリ名の latest
にしておきます。
サービス
既存のクラスターを my-cluster
にします。
サービス名は my-service
にします。
ネットワーキングとロードバランシングについては上記のページを参考にしてください。
ここでいったんデプロイが始まり失敗しますが、ECR にイメージが無いのが原因なので気にしないでください。
ポリシー作成
CodeBuild に最小限の権限を付与するために、IAM で新規にポリシーを作成します。
ポリシーは複雑なので JSON で作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CodeBuildPolicy",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"ecr:GetAuthorizationToken",
"ecr:UploadLayerPart",
"ecr:PutImage",
"ecr:CompleteLayerUpload",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"s3:GetObject",
"s3:PutObject"
],
"Resource": "*"
}
]
}
ロール作成
CodeBuild 用のロールを作成します。
IAM でロールを作成します。先ほど作成したポリシーを使います。
CodePipeline
CodeCommit
CodeBuild
3ページ目のビルド設定はちょっと複雑です。
「プロジェクトを作成する」をクリックするといったん別のウィンドウが開きます。
CodeDeploy
この設定でパイプラインを作成します。
結果
更新
CodeCommit の master ブランチに変更が発生するたびに、自動的にビルド→デプロイが実行されます。