概要

AWSのCodePipelineを使用してWebアプリケーションを自動でビルド・デプロイする方法について解説します。
今回は例としてNext.jsアプリケーションを使用します。
また筆者の開発環境はWindowsであるためローカルPC上での作業はWindows準拠となります。ご了承ください。
流れとしては以下のようになります。
- 開発者がCodeCommitのmasterブランチにソースをプッシュ
- CodeBuildがアプリケーションをビルドしてS3バケットに保存
- CodeDeployでS3バケットに保存したアプリケーションをEC2にデプロイ
- アプリケーションの起動プロセスを自動で再起動
EC2インスタンスの作成
アプリケーションを稼働させるためのEC2インスタンスを起動します。
今回の設定は以下のようにしました。
※VPCやサブネット、セキュリティグループの詳細については割愛します。
| 名前 | Web Server |
| 追加タグ | キー:CodeDeployTarget 値:True |
| AMI | Amazon Linux 2023 AMI |
| インスタンスタイプ | t2.micro |
| キーペア | ec2_key(新しく作成) |
| VPC | 作成したVPC |
| サブネット | 作成したパブリックサブネット |
| パブリックIPの自動割り当て | 有効化 |
| セキュリティグループ | sshとhttpを許可 |
| ストレージ(ルートボリューム) | 1 x 8 GiB gp3 |
ここで設定したタグ「CodeDeployTarget:True」を持つインスタンスがデプロイ対象となるようにCodeDeployで設定します。
IAMロールのアタッチ
デプロイの際にEC2インスタンスはS3からアプリケーションを取得します。そのためIAMロールをアタッチしてS3へのアクセス許可を行う必要があります。
「AmazonS3ReadOnlyAccess」の許可ポリシーを持ったIAMロールを作成してください。今回は「EC2Role」という名前で作成しました。

作成できたらEC2にアタッチします。


ソフトウェアのインストール
作成したインスタンスにssh接続して各種ソフトウェアをインストールしていきます。
まずはCodeDeployエージェントをインストールします。基本的には公式のユーザーガイド通りに進めるだけです。
sudo yum update
sudo yum install ruby
sudo yum install wget
cd /home/ec2-user
wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status
Next.jsのアプリケーションを実行するためにはNode.jsが必要なのでインストールします。
sudo su -
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install 18
注意
curlコマンド実行後に一度ssh接続を切断して再ログインしてください
pm2(Node.jsのプロセスマネージャー)をインストールします。
npm install -g pm2
アプリケーションのデプロイ場所となるディレクトリを作成しておきます。
mkdir -p /var/www/next-app
Nginxをインストールします。
sudo yum -y install nginx
sudo service nginx start
Nginxの設定ファイルを編集します。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
Nginxを再起動して設定を反映させます。
sudo service nginx restart
以上でEC2の設定は完了です。
アプリケーションの作成
ローカル環境でアプリケーションを作成します。
Node.jsをインストールしてNext.jsのプロジェクトを作成します。
今回Node.jsのバージョン18を使用します。
npx create-next-app@latest
途中でいろいろ質問されますが今回は以下のようにしました。
What is your project named? next-app
Would you like to use TypeScript? → Yes
Would you like to use ESLint? → Yes
Would you like to use Tailwind CSS? → No
Would you like to use `src/` directory? → No
Would you like to use App Router? (recommended) → Yes
Would you like to customize the default import alias (@/*)? → No
プロジェクトが作成されたらトップページに「Hello World!」を表示するように修正します。
import Image from 'next/image'
import styles from './page.module.css'
export default function Home() {
return <h1>Hello World!</h1>
}
app/globals.css の中身は空にしておきます。
編集が済んだら確認のため実行してみましょう。
npm run dev
http://localhost:3000 にアクセスして以下のように表示されればOKです。

CodeCommitでソース管理
次にこのソースコードをCodeCommitで管理していきます。
「next-app」という名前のリポジトリを作成してください。

SSHキーの作成
ローカルPCとCodeCommit間の通信はsshで行うためまずは鍵を作成する必要があります。
ローカル環境で以下のコマンドを実行して鍵を作成します(passphraseは入力せずEnterでOK)。
cd C:\Users\ユーザー名\.ssh
ssh-keygen -t rsa -f codecommit_rsa
これで秘密鍵(codecommit_rsa)と公開鍵(codecommit_rsa.pub)が作成されます。
次にIAM > ユーザー > セキュリティ認証情報タブ から公開鍵のアップロード を行います。
入力エリアが表示されるのでcodecommit_rsa.pubの中身をコピペしてください。

作成できたらSSHキーIDを控えます。

ローカルPCに以下のファイルを作成してください。
Host git-codecommit.*.amazonaws.com
User 控えたSSHキーID
IdentityFile ~/.ssh/codecommit_rsa
これでCodeCommitと通信できるようになりました。
ソースコードのプッシュ
プロジェクトフォルダ直下で以下のコマンドを実行してリポジトリにプッシュします。
※ Next.jsはプロジェクト作成時に自動でGit管理されるため git init コマンドは不要です。
git remote add origin ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/next-app
git add -A
git commit -m "first commit"
git push origin master
S3バケットの作成
ビルドされたファイル群は一旦S3に保存する必要があるので事前にバケットを作成しておきましょう。
バケット名は「next-app-build」、リージョンは東京にしておきます。
その他の項目はデフォルトのままで作成してください。

CodeBuildの設定
buildspec.yml
プロジェクトフォルダ直下に「buildspec.yml」を作成します。
buildspec.ymlはCodeBuildで参照される設定ファイルです。
詳細は公式リファレンスをご参照ください。
phasesプロパティでフェーズ毎に実行するコマンドを記述します。
今回はパッケージをインストール(npm install)してからビルド(npm run build)を行っています。
パッケージが格納されているnode_modulesフォルダをtarで圧縮しているのは、デプロイの際に中身のシンボリックリンクが実ファイルに変換されてエラーになるのを防ぐためです。
artifactsプロパティのfilesではS3にアップロードする対象ファイルを指定します。
version: 0.2
phases:
install:
commands:
- echo Installing dependency...
- npm install
build:
commands:
- echo Build started on `date`
- npm run build
post_build:
commands:
- tar cf node_modules.tar node_modules
artifacts:
files:
- .next/**/*
- next.config.js
- node_modules.tar
- package.json
- appspec.yml
- scripts/start.sh
ではCodeBuildeでビルドプロジェクトを作成します。設定は以下を参照してください。
| プロジェクトの設定 | |
| プロジェクト名 | next-app-build |
| ソース | |
| ソースプロバイダ | AWS CodeCommit |
| リポジトリ | next-app |
| ブランチ | master |
| 環境 | |
| 環境イメージ | マネージ型イメージ |
| オペレーティングシステム | Amazon Linux 2 |
| ランタイム | Standard |
| イメージ | aws/codebuild/amazonlinux2-x86_64-standard:5.0 |
| イメージのバージョン | このランタイムバージョンには常に最新のイメージを使用してください |
| 環境タイプ | Linux EC2 |
| サービスロール | 新しいサービスロール |
| ロール名 | CodeBuildRole |
| Buildspec | |
| ビルド仕様 | buildspecファイルを使用する |
| アーティファクト | |
| タイプ | Amazon S3 |
| バケット名 | next-app-build |
| セマンティックバージョニングの有効化 | チェック |
| アーティファクトのパッケージ化 | なし |
| ログ | |
| CloudWatch Logs - オプショナル | チェック |
今回のアプリはNode.jsのバージョン18を使用して作成したため、ビルド環境イメージに「aws/codebuild/amazonlinux2-x86_64-standard:5.0」を指定しています。
ランタイムとイメージの対応表は下記ページを参照してください。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/available-runtimes.html
ビルドプロジェクトが作成できたら実際にビルド実行してみてください。
先ほど作成したバケット内にファイルができていれば成功です。

CodeDeployの設定
appspec.yml
プロジェクトフォルダ直下に「appspec.yml」を作成します。
appspec.ymlはCodeDeployで参照される設定ファイルです。
詳細は公式リファレンスをご参照ください。
filesプロパティではS3上のどのファイルをEC2インスタンスのどのディレクトリに配置するか指定します。
今回は全ファイルを /var/www/next-app に配置しています。
hooksプロパティで実行するシェルスクリプトを指定します。
version: 0.0
os: linux
files:
- source: /
destination: /var/www/next-app
hooks:
ApplicationStart:
- location: scripts/start.sh
timeout: 300
runas: root
シェルスクリプトは以下の通りです。
これもプロジェクト内に追加しておいてください。
#!/bin/bash
source /root/.bash_profile
cd /var/www/next-app
# node_modulesを展開
tar xf node_modules.tar
rm -f node_modules.tar
# サーバを起動(既に起動中の場合は再起動)
pm2 stop next-app
pm2 start npm --name next-app -- start
IAMロールの作成
CodeDeployがEC2にアクセスするためのIAMロールを作成しておきます。
名前は「CodeDeployRole」にします。許可ポリシーには「AWSCodeDeployRole」を指定します。

アプリケーションの作成
CodeDeployでアプリケーションの作成を行います。
アプリケーション名は「next-app」とし、コンピューティングプラットフォームには「EC2/オンプレミス」を選択します。

デプロイメントグループの作成
このアプリケーションに対するデプロイメントグループを作成します。
| デプロイグループ名 | |
| デプロイグループ名の入力 | next-app-group |
| サービスロール | |
| サービスロール名の入力 | arn:aws:iam::アカウントID:role/CodeDeployRole |
| デプロイタイプ | |
| アプリケーションのデプロイ方法 | インプレース |
| 環境変数 | |
| Amazon EC2 インスタンス | キー:CodeDeployTarget 値:True |
| デプロイ設定 | |
| デプロイ設定 | CodeDeployDefault.AllAtOnce |
CodePipelineの設定
ここまでで作成してきたCodeCommit、CodeBuild、CodeDeployをCodePipelineで一連のプロセスにしていきます。
パイプラインを作成します。設定は以下の通りです。
| パイプラインの設定 | |
| パイプライン名 | next-app-pipeline |
| サービスロール | 新しいサービスロール |
| ロール名 | CodePipelineRole |
| ソースステージ | |
| ソースプロバイダー | AWS CodeCommit |
| ブランチ名 | master |
| 検出オプション | Aws ClowdWatch Events |
| 出力アーティファクト形式 | CodePipelineのデフォルト |
| ビルドステージ | |
| プロバイダー | AWS CodeBuild |
| リージョン | アジアパシフィック(東京) |
| プロジェクト名 | next-app-build |
| ビルドタイプ | 単一ビルド |
| デプロイステージ | |
| デプロイプロバイダー | AWS CodeDeploy |
| リージョン | アジアパシフィック(東京) |
| アプリケーション名 | next-app |
| デプロイグループ | next-app-group |
これで全ての設定が完了しました。
ソースコードの変更をCodeCommitにプッシュしてみてください。
ソースがビルドされてEC2の/var/www/next-appにデプロイされるはずです。
EC2のグローバルIPアドレスにブラウザでアクセスして「Hello World!」が表示されていればOKです。

それでは「Hello World!」の部分を「Hello Next.js!」に変更してプッシュしてみます。

しばらくすると変更が反映されます。
まとめ
以上AWS CodePipelineを使用した自動デプロイでした。
やってることは単純ですが色々と設定項目が多く大変でした。
自動ビルドにテストも含めて完全なCICDにするか迷いましたが、テストはプルリクを契機に実行した方が便利かと思い今回は組み込みませんでした。CodePipelineでこれを実現しようとすると結構面倒でLamdaなども必要になるみたいです。このあたりはGithubの方が便利ですね。。。
今回はEC2を使用しましたがDockerコンテナをECSにデプロイするパターンもいつか試してみたいです。
