AWS CodePipelineでNext.jsアプリケーションをCI/CDする

記事タイトルとURLをコピーする

こんにちは、屋根裏エンジニアの折戸です。

今回はNext.jsアプリケーションをCI/CDするための設定手順についてのご紹介です。

構成

f:id:swx-orito:20220412121848p:plain

  1. AWS CodePipeline(以下、CodePipeline)のソースステージでGitHubのリポジトリのPushを検知
  2. ソースコードをS3のSourceArtiへ保存
  3. AWS CodeBuild(以下、CodeBuild)でbuildspec.ymlを元にソースコードをビルド
  4. ビルドファイルをS3のBuildArtifへ保存
  5. AWS CodeDeploy(以下、CodeDeploy)でappspec.ymlを元に任意のEC2へデプロイ

これらの一連の処理をCodePipelineでまとめて自動化する様な構成です。

前提

  • CodePipeline作成の流れで同時にGitHubリポジトリとCodeBuildを設定できますが、 CodeDeployは事前に作成しておいた方がスムーズに設定できるため、CodeDeploy関連のリソースを先に作成する手順としています。
  • ローカル環境とデプロイ先のEC2共に、インターネットから接続可能な環境に構築されているものとします。

事前準備

ミドルウェア

  • npm

    • Next.jsアプリケーションを作成するため、npmコマンドとnpxコマンドを使用します
    • インストールされていない場合は、適宜以下のページより環境に応じたnpmをご準備してください nodejs.org
    • 今回は以下のnpmバージョンを利用
      ローカルMac端末
      % npm --version 8.3.0
      デプロイ先EC2インスタンス
      $ npm --version 6.14.16
  • pm2

    • デプロイ先のNext.jsアプリケーションの起動に使用します。
    • 今回は以下のpm2バージョンを利用
      デプロイ先EC2インスタンス
      $ pm2 --version … [PM2] PM2 Successfully daemonized 5.2.0
  • リポジトリ

    • ソースリポジトリはGitHubのリポジトリとします

      • アカウントをお持ちでない場合は、適宜準備してください github.com
    • GitHubリポジトリの認証方法はSSHとします

      • 設定されてない場合は、適宜設定してください docs.github.com

ローカル環境へNext.jsアプリケーションを作成

npxコマンドでNext.jsアプリケーションを作成

アプリケーションを作成したい任意のディレクトリで以下コマンドを実行します

% npx create-next-app@latest
…
Ok to proceed? (y). → y をEnter
…
✔ What is your project named?  → 【アプリケーション名】Enter
…

アプリケーション名:任意(今回はnext-cicdとしました) 完了すると、 next-cicd の名前のディレクトリが作成されます

npm build & start

作成されたディレクトリへ移動し、ビルドします
その後、ローカル環境で起動させます

% cd next-cicd/
% npm run build
% npm run start

起動したアプリケーションへアクセス

任意のブラウザより
http://localhost:3000
へアクセスします

f:id:swx-orito:20220106005556p:plain 上記のページが表示されれば成功です
これでローカル環境へアプリケーションを作成できました🎉

アプリケーションを停止

起動確認は完了したので、ローカル環境のアプリケーションは停止させておきます

Ctrl+c

Git 作業

GitHubにリポジトリを作成

GitHubアカウントへログインし、リポジトリを作成します

f:id:swx-orito:20220117081018p:plain 右上ペイン + -> New repository クリック

f:id:swx-orito:20220411154433p:plain

Repository name :任意(今回は next-cicd としました)
Private 選択
Create repository クリック

ローカル環境のアプリケーションをGitHubリポジトリへ push

npxコマンドでNode.jsアプリケーションを作成すると初回のgit commitまで実行されている状態です

% git log
commit b423d743bf148697a260db67fa7a93406de40de3 (HEAD -> main)
Author: Ryota Orito <…@…>
Date:   Fri Jan 14 22:44:28 2022 +0900

    Initial commit from Create Next App

よって、git remote add コマンドから順に実行します

% git remote add origin git@github.com:【GitHubユーザー名】/【Repository name】.git
% git branch -M main
% git push -u origin main
Enter passphrase for key '/Users/user/.ssh/git': → GitHubのSSHキーパスワードを入力

【GitHubユーザー名】 【Repository name】 は適宜変更してください

EC2 アタッチ用ロール 作成

各ポリシー 概要

  • AmazonSSMManagedInstanceCore
    CodeDeployエージェントインストール用ポリシー
    デプロイ処理はEC2へインストールされたCodeDeployエージェントを通して実行されます。
    当手順ではEC2へCodeDeployエージェントをインストールするために、AWS Systems ManagerからCodeDeployエージェントをインストールしています。
    このインストール処理に必要なポリシーがAmazonSSMManagedInstanceCoreです。
    ※この方法はAWS公式ドキュメントでお勧めの方法ですが、AWS CLI でS3バケットから CodeDeploy エージェントをインストールすることもできます。 docs.aws.amazon.com

    Using AWS Systems Manager is the recommended method for installing and updating the CodeDeploy agent. You can also install the CodeDeploy agent from an Amazon S3 bucket.

  • AmazonEC2RoleforAWSCodeDeploy
    CodeDeployがEC2へデプロイするためのポリシー

ロール 作成

IAM > ロール > ロールを作成 f:id:swx-orito:20220407143852p:plain

f:id:swx-orito:20220407143945p:plain 信頼されたエンティティタイプ:AWSのサービス
ユースケース:EC2
次へ

f:id:swx-orito:20220410032603p:plain AmazonSSMManagedInstanceCore チェック

f:id:swx-orito:20220407144102p:plain AmazonEC2RoleforAWSCodeDeploy チェック
次へ

f:id:swx-orito:20220410032747p:plain f:id:swx-orito:20220410033145p:plain

ロール名:任意(今回はAmazonSSMManagedInstanceCoreAndAmazonEC2RoleforAWSCodeDeployRoleとしました) ロールを作成

EC2へアタッチ

デプロイしたいインスタンスへロールをアタッチ
EC2 > インスタンス > インスタンス選択 > アクション > セキュリティ > IAMロールを変更 f:id:swx-orito:20220410033930p:plain

f:id:swx-orito:20220410034100p:plain 作成したロールを選択
保存

CodeDeploy用ロール 作成

CodeDeploy自体のロールを作成
IAM > ロール > ロールを作成 f:id:swx-orito:20220407143852p:plain

f:id:swx-orito:20220407145429p:plain 信頼されたエンティティタイプ:AWSのサービス
ユースケース:他のサービスのユースケース > CodeDeploy
次へ

f:id:swx-orito:20220407145527p:plain AWSCodeDeployRole 選択
次へ

f:id:swx-orito:20220407145642p:plain f:id:swx-orito:20220407145059p:plain ロール名:任意(今回はAWSCodeDeployRoleとしました)
ロールを作成

CodeDeploy 作成

アプリケーション 作成

CodeDeploy > アプリケーション > アプリケーションの作成 f:id:swx-orito:20220410035010p:plain

f:id:swx-orito:20220407120144p:plain アプリケーション名:任意(今回はnext-cicdとしました)
コンピューティングプラットフォーム:EC2/オンプレミス
アプリケーションの作成

デプロイグループ 作成

アプリケーション選択 > デプロイグループ > デプロイグループの作成 f:id:swx-orito:20220407120228p:plain

f:id:swx-orito:20220407153123p:plain f:id:swx-orito:20220407153218p:plain f:id:swx-orito:20220407153300p:plain デプロイグループ名の入力:任意(今回はnext-cicd-deploy-groupとしました)
サービスロール:作成したCodeDeploy用ロールのArnを選択
デプロイタイプ:インプレース 環境設定:Amazon EC2 インスタンス チェック
キー/値:デプロイしたいEC2のタグを入力 Load balancer:チェックを外す デプロイグループの作成

CodeDeploy エージェント インストール 確認

AWS Systems Manager を使用したエージェント設定によってEC2へCodeDeployエージェントがインストールされていることを確認

AWS Systems Manager > ステートマネージャー > 関連付けの名前:CodeDeployDG-【CodeDeployデプロイグループ名】 f:id:swx-orito:20220411003011p:plain ステータス:成功 確認

ちなみに関連 IDを確認すると、、 f:id:swx-orito:20220411003155p:plain スケジュール式が rate(14 days) となっており、14日間隔で自動的にCodeDeployエージェントが更新される設定となっています。

EC2インスタンス

$ systemctl status codedeploy-agent status
● codedeploy-agent.service - AWS CodeDeploy Host Agent
   Loaded: loaded (/usr/lib/systemd/system/codedeploy-agent.service; enabled; vendor preset: disabled)
   Active: active (running) since 木 2022-04-07 16:49:08 UTC; 4min 39s ago
  Process: 12990 ExecStart=/bin/bash -a -c [ -f /etc/profile ] && source /etc/profile; /opt/codedeploy-agent/bin/codedeploy-agent start (code=exited, status=0/SUCCESS)
 Main PID: 13000 (ruby)
   CGroup: /system.slice/codedeploy-agent.service
           ├─13000 codedeploy-agent: master 13000
           └─13004 codedeploy-agent: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller of master 13000

 4月 07 16:49:08 ip-10-0-0-5.ap-northeast-1.compute.internal systemd[1]: Starting AWS CodeDeploy Host Agent...
 4月 07 16:49:08 ip-10-0-0-5.ap-northeast-1.compute.internal systemd[1]: Started AWS CodeDeploy Host Agent.
Unit status.service could not be found.

CodePipeline 作成

CodePipeline > パイプラインの作成 f:id:swx-orito:20220407110235p:plain

f:id:swx-orito:20220407110349p:plain パイプライン名:任意(今回はnext-cicdとしました)
サービスロール:新しいサービスロール
ロール名:新しいサービスロールを選択すると、自動で入力されます
次に

f:id:swx-orito:20220407110507p:plain ソースプロバイダ:GitHub(バージョン2)
GitHub に接続する

f:id:swx-orito:20220407110607p:plain 接続名:任意(今回はnext-cicdとしてますが、他にCodeDeployなどで連携したいリポジトリが存在する場合は、より汎用的な名称が良いです。)
GitHub に接続する

f:id:swx-orito:20220411163147p:plain GitHub認証画面で認証情報を入力し、Sign in
2段階認証設定している場合は、適宜認証完了させます。

f:id:swx-orito:20220411163741p:plain GitHub アプリ:選択項目のアプリケーションを選択
接続

AWS Connector for GitHub 設定

GitHubの設定で、AWSからリポジトリへのアクセス権限を設定します。
右上アイコンペイン > Settings > Applications > AWS Connector for GitHub Configure f:id:swx-orito:20220411225856p:plain

f:id:swx-orito:20220411230230p:plain Only select repositories
Select repositories:リポジトリを選択

f:id:swx-orito:20220411231423p:plain Save

CodePipelineの設定画面へ戻ります。

f:id:swx-orito:20220407113825p:plain リポジトリ名:AWS Connector for GitHub で追加したリポジトリを選択
ブランチ名:main 出力アーティファクト形式:完全クローン
次に

ビルドステージを追加する

f:id:swx-orito:20220407114049p:plain プロバイダーを構築する:AWS CodeBuild リージョン:アジア・パシフィック(東京) プロジェクト名:プロジェクトを作成する

f:id:swx-orito:20220407114445p:plain

f:id:swx-orito:20220407114526p:plain

プロジェクト名:任意(今回はnext-cicdとしました) 環境イメージ:マネージド型イメージ
オペレーティングシステム:Amazon Linux 2
ランタイム:Standard
イメージ:aws/codebuild/amazonlinux2-x86_64-standard:3.0
イメージのバージョン:このランライムバージョンには常に最新のイメージを使用してください
環境タイプ:Linux
サービスロール:新しいサービスロール
ロール名:自動入力
Buildspec:buildspec ファイルを使用する CodePipeline に進む

f:id:swx-orito:20220407114757p:plain CodeBuild で next-cicd が正常に作成されました。 を確認
次に

デプロイステージを追加する

f:id:swx-orito:20220411234235p:plain デプロイプロバイダー:AWS CodeDeploy
リージョン:アジアパシフィック(東京)
アプリケーション名:CodeDeployアプリケーションを選択(今回はnext-cicd)
デプロイグループ:CodeDeployデプロイグループを選択(今回はnext-cicd-deploy-group)
次に

設定内容を確認し、パイプラインを作成する

Build が失敗していることを確認

f:id:swx-orito:20220411235648p:plain CodePipelineは一通り設定できましたが、まだ完全ではないので追加で設定していきます。

デプロイ先インスタンスにデプロイ先ディレクトリ 作成

$ mkdir 【デプロイ先ファイルパス】

今回は/home/orito/next-cicd/としました。
もしデプロイ先で既にアプリケーションを手動で作成していた場合は、デプロイ先のソースコードを削除しておきましょう。

CodeBuild サービスロールへ接続使用権限 付与

参考 docs.aws.amazon.com

Gitクローン:ソースコードをビルド環境に直接ダウンロードできます。 Gitクローンモードでは、動作するGitリポジトリとしてソースコードを操作できます。このモードを使用するには、接続を使用するためのCodeBuild環境権限を付与する必要があります。

パイプラインのSource Arn確認

f:id:swx-orito:20220407124406p:plain

f:id:swx-orito:20220407124549p:plain ConnectionArn を控えておきます。

ビルドプロジェクトサービスロール 遷移

ビルドプロジェクト > ビルドの詳細 > サービスロール f:id:swx-orito:20220407123147p:plain

f:id:swx-orito:20220407123256p:plain アクセス許可を追加
ポリシーをアタッチ

ポリシー 作成

f:id:swx-orito:20220407123402p:plain ポリシーの作成

f:id:swx-orito:20220407124834p:plain JSON タブ
以下を入力

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "codestar-connections:UseConnection",
            "Resource": "【控えておいたConnectionArn】"
        }
    ]
}

次のステップ:タグ
次のステップ:確認 f:id:swx-orito:20220407125017p:plain 名前:任意(今回はcodebuild-next-cicd-service-role-codestar-policyとしました)
ポリシーの作成

作成したポリシーをアタッチ

f:id:swx-orito:20220407125301p:plain 作成したポリシーを検索し、チェック
ポリシーをアタッチ

リポジトリへ設定ファイルなどをPush

buildspec.yml 作成

リポジトリへbuildspec.ymlを作成します。

  • buildspec.yml
  - source: /
version: 0.2

env:
  variables:
    NODE_ENV: "production"

phases:
  install:
    commands:
      - echo Installing dependency...
      - npm install
  build:
    commands:
      - echo Build started on `date`
      - echo Compiling the Next.js code
      - npm run build
artifacts:
  files:
    - .next/**/*
    - next.config.js
    - node_modules/**/*
    - package.json
    - appspec.yml
    - restart.sh
  enable-symlinks: yes

appspec.yml 作成

リポジトリへappspec.ymlを作成します。

  • appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: 【デプロイ先ファイルパス】
hooks:
  ApplicationStart:
    - location: restart.sh
      timeout: 180

デプロイ先:デプロイしたいファイルパスを指定(今回は/home/orito/next-cicd/としました)

再起動用スクリプト 作成

リポジトリへrestart.shを作成します。 - restart.sh

#!/bin/bash
cd /home/orito/next-cicd/
pm2 list
pm2 stop next-cicd
pm2 start npm --name next-cicd -- start

作成したファイルをGit push

$ git add appspec.yml buildspec.yml restart.sh
$ git commit -m "Add Setting files"
$ git push origin main

CI/CD確認

index.js を編集

変更されたことを確認するために、トップページの表示文言を変更します

$ vim pages/index.js

本稿では以下の Welcome to の部分を Hello へ変更しました

% git diff pages/index.js
       <main className={styles.main}>
         <h1 className={styles.title}>
-          Welcome to <a href="https://nextjs.org">Next.js!</a>
+          Hello <a href="https://nextjs.org">Next.js!</a>
         </h1>

Git push

$ git add pages/index.js
$ git commit -m "Edit text"
$ git push origin main

CodePipeline 確認

f:id:swx-orito:20220412060908p:plain

コミットメッセージが反映され、 Source->Build->Deployと、 順次進行されていく様子が確認できます。

Deploy が成功となったら、
EC2のアプリケーションURLへアクセスします

f:id:swx-orito:20220106162139p:plain

Welcome to の部分が Hello に変わっていることが確認できれば、成功です🎉🎉

【参考】デプロイ時のログ

デプロイ処理がうまく設定できない場合等に確認するログです。

$ tail -f /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

【参考】CodeBuild アーティファクト確認

buildspec.yml の artifacts のファイルがS3のcodepipeline-ap-northeast-1-********** バケット内の BuildArtif 配下へzip化されて保存されていることが確認できます。 このビルドファイルを元にCodeDeployの処理が進行します。 f:id:swx-orito:20220407141438p:plain

  • ダウンロード 確認 f:id:swx-orito:20220407141700p:plain

【参考】ハマリポイント

  • エラー
Error: Cannot find module '../build/output/log' Require stack: - /home/orito/next-cicd/node_modules/.bin/next

node_modules内でシンボリックリンクが定義されているようで、ビルドソースがS3バケットへ保存されるときに内部シンボリックリンクを保持するように設定しておく必要がありました。

restart.sh で アプリケーションディレクトリへ cd しないと、npm run start コケる

pm2 で起動するためにはアプリケーションのルートディレクトリへ移動させる必要がありました。

最後に

開発サイクルの早いアプリケーション開発にとって、CI/CD環境はかかせません。
かなり手順は多いですが、一度設定してしまえば運用コストが削減できるので、どなたかのお役に立てれば幸いです。

では。

折戸 亮太(執筆記事の一覧)

2021年10月1日入社
クラウドインテグレーション部技術1課

屋根裏エンジニア