Next.jsをEC2にデプロイする方法とCI/CDの設定
はじめに
Next.js (App Router) + Cloudfront + S3のデプロイする方法はこちらです。
EC2にデプロイした後、ドメイン経由でACMを利用してインターネットからELBへの通信をHTTPSで行う設定については別の記事にしています。
EC2の作成
インスタンス名(for-next.js
)を入力、
キーペアの作成
キーペアとは、EC2インスタンスに安全にアクセスするための認証です。SSHを通じてインスタンスにアクセスする予定がある場合、キーペアを設定してください。
SSH(Secure Shell)
ネットワークを通じて他のコンピュータにリモートログインし、安全な通信を行うためのプロトコルです。インターネット上でデータを暗号化して送受信することで、セキュリティを確保しながら、サーバーや他のリモートシステムにアクセスすることが可能になっています。
新しいキーペアの作成
を選択、
キーペア名(for-next.js
)を入力したら、キーペアを作成
を押してください。
ネットワーク設定 > 編集
わかりやすいようにセキュリティグループ名(for-next.js
)と説明を変更しておきます。
インスタンスを起動
を押してEC2インスタンスを作成してください。
ダウンロードされたキーは、~/.ssh
ディレクトリに移動させておいてください。
EC2インスタンスはVPC内でのみ起動されます。VPCはデフォルトのものを選択し、インスタンスを起動
を選択してください。
EC2インスタンスにSSH接続
このままローカルから接続すると、下記のような権限のエラーが表示されます。
パーミッションを表示して確認してください。
ls -l ~/.ssh/for-next.js.pem
下記が表示されました。(黒塗りはユーザー名)
この状態では、ファイルの所有者は読み書きが可能ですが、他のユーザーも読み取りが可能となっています。
パーミッションの見方
-
この先頭のダッシュは通常のファイルであることを意味します。ディレクトリであれば、d
、シンボリックリンクの場合はl
が表示されます。
rw-
最初の三文字はファイルの所有者(この場合は ユーザー名)のアクセス権を示します。rw
は読み書き(readとwriteの頭文字)が可能であることを意味し、-
は実行権限がないことを示します。
r--
次の三文字はファイルのグループ(この場合はstaff)のメンバーのアクセス権を示します。r
は読み取り可能であることを意味し、--
は書き込みと実行ができないことを示します。
r--
最後の三文字はその他のユーザーのアクセス権を示します。ここでもr
は読み取り可能であることを意味し、--
は書き込みと実行ができないことを示します。
1
ファイルのハードリンク数です。通常のファイルでは 1
が一般的です。
ユーザー名
ファイルの所有者のユーザー名
staff
ファイルが属するグループ名
1678
ファイルのサイズ(バイト単位)
6 24 12:22
ファイルの最後の変更日時(月、日、時刻)
パーミッションを600
(所有者のみ読み書き可能)に変更してください。
chmod 600 ~/.ssh/for-next.js.pem
権限が正しく変更されているのがわかると思います。
ssh -i キーペアのパス ec2-user@パブリックIPアドレス
パブリックIPアドレスはパブリック IPv4 アドレス
または、自動的に割り当てられた IP アドレス
で確認してください。
今回は下記を実行します。
ssh -i ~/.ssh/for-next.js.pem ec2-user@パブリックIPアドレス
接続の承認が必要なので、yes
と入力してください。
Are you sure you want to continue connecting (yes/no/[fingerprint])?
fingerprint
SSH公開鍵の短縮されたハッシュ値です。
下記のように表示されると、SSH接続ができています。
セットアップ
Next.jsはNode.js上で動作するJavaScriptフレームワークなので、Next.jsアプリケーションをEC2インスタンス上で実行するにはインストールが必要です。
まずは、EC2インスタンスのOSをアップデートしてください。
sudo yum update -y
Node.js をインストールします。NVM(Node Version Manager)を使用すると便利です。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
.bashrc
に記載した内容を反映してください。正しく設定された環境でNode.jsのインストールやその他の操作が行えるようになります。
source ~/.bashrc
nvm
を使用してNode.jsの最新のLTSバージョンをインストールしてください。
nvm install --lts
gitのインストール
sudo yum -y install git
リポジトリのクローン
アプリケーションのリポジトリをSSH経由でEC2インスタンスにクローンする場合、SSHキーの生成が必要です。4096
はビット数です。
ssh-keygen -t rsa -b 4096
下記が聞かれますが、全てEnterで問題ないです。
Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa):
生成されたSSH秘密鍵(私鍵)を保存するファイルパスを入力する
Enter passphrase (empty for no passphrase):
秘密鍵に対して設定するパスフレーズ(パスワード)を入力する。パスフレーズを設定しない場合は、空のままEnterキーを押してください。
Enter same passphrase again:
先ほど入力したパスフレーズを再度入力する
生成されたSSHキーの内容を確認してコピーしてください。ssh-rsa
から始めるものです。
cat /home/ec2-user/.ssh/id_rsa.pub
setting > SSH and GPG keysでNew SSH key
を選択、
Title(ec2-for-next.js
)とkey(コピーしたSSHキー)を入力したらAdd SSH key
を押して登録してください。
これでクローンできるようになっています。
git clone <SSH用のクローンURL>
接続の承認が必要なので、yes
と入力してください。
Are you sure you want to continue connecting (yes/no/[fingerprint])?
リバースプロキシ
クライアントとサーバー(アプリケーション)の間に立ち、ポートの変換やセキュリティ機能、負荷分散などの役割を果たします。そのため、ポートの違いを橋渡しする重要なコンポーネントとして利用されます。
リバースプロキシの設定
Next.jsアプリケーションは通常、3000番ポートで起動し、リクエストを受け付けますが、本番環境ではポート番号を変更(80番ポート(HTTP)または443番ポート(HTTPS))してを使用して公開されます。そのため、ユーザーがブラウザを通じてアクセスする際には、これらのポート(80、443)を介して通信することが一般的です。
このポートの違いの橋渡し(リバースプロキシ)をするために、Nginxなどのリバースプロキシが必要になります。リバースプロキシは、80番や443番ポートで受け取ったリクエストを、バックエンドで稼働している3000番ポートのNext.jsアプリケーションに転送します
ルートディレクトリで、Nginxをインストールしてください。
sudo yum install nginx -y
Next.jsアプリケーションのポートに転送されるようにNginxの設定を変更します。
sudo nano /etc/nginx/nginx.conf
server {}
の内に下記を追加してください。
location / {
proxy_pass http://localhost:3000;
}
/etc/nginx/nginx.conf
の内容を確認
cat /etc/nginx/nginx.conf
以下の内容になっているはずです。
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
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;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
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 {
}
location / {
proxy_pass http://localhost:3000;
}
}
# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers PROFILE=SYSTEM;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# 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 {
# }
# }
}
再起動して変更を適用します。
sudo systemctl restart nginx
下記コマンドでどのポートが開いているか確認することができます。
netstat -tlnt
セキュリティグループの設定
インバウンドルールを下記のように4つ追加してください。
0.0.0/0
IPv4アドレスの範囲で、インターネット上のすべてのIPv4アドレスを指します。
たとえば、AWSのセキュリティグループでインバウンドルールを設定する際に、0.0.0.0/0
をソースとして指定すると、全世界のIPv4アドレスからのアクセスを受け入れることになります。
::/0
IPv6アドレスの範囲で、インターネット上のすべてのIPv6アドレスを指します。
セキュリティグループやファイアウォールの設定で::/0
を使用すると、全世界のIPv6アドレスからのアクセスを許可することになります。
環境変数の設定
環境変数を使用している場合は忘れずに.env
を作成してください。
アプリケーションのディレクトリに移動して、
cd リポジトリ名
.env
の内容を入力してください。
touch .env
ローカルの.env
をコピペしてください。
nano .env
アプリケーションをビルド
アプリケーションのディレクトリで、依存関係のインストール
npm install
Next.jsアプリケーションをビルドします。
npm run build
サーバーを起動してください。
npm run start
パブリックIPアドレス(http://パブリックIPアドレス/ )にアクセスして画面が表示されるか確認してください。
PM2を使用してアプリケーションを永続的に起動させる
現在のままでは、SSH接続でサーバーを立ち上げている時のみしかブラウザにプロジェクトが表示されません。
試しにnpm run start
で起動しているサーバーを停止してください。パブリックIPアドレス(http://パブリックIPアドレス/ )に再度アクセスすると、下記のように表示されると思います。
PM2を使用してアプリケーションを永続的に起動するようにしてください。
ルートディレクトリに移動し、
cd ~/
ルートディレクトリでpm2をインストールしてください。
npm install -g pm2
アプリケーションのディレクトリに移動して、
cd リポジトリ名
アプリケーションを--name
フラグを使用してアプリケーションに任意の名前を付けて起動してください。
下記はPM2を使用してNode.jsのパッケージマネージャであるnpmを通じてNext.jsアプリケーションを起動しています。
pm2 start npm --name "nextjs-app" -- start
再度パブリックIPアドレス(http://パブリックIPアドレス/ )に再度アクセスすると、画面が表示されると思います。
-- start
package.json
のscripts > "start":
に記述されている内容を実行しています。
停止する
"nextjs-app"
はidでも構いません。
pm2 stop "nextjs-app"
停止させると、アクセスしてもこのような表示になっていると思います。
アプリケーションのステータス確認
pm2 status "nextjs-app"
アプリケーションを一覧表示
pm2 list
ログ確認
pm2 status "nextjs-app"
特定のアプリケーションプロセスを削除する
pm2 delete 1
修正後のコードをEC2インスタンスに反映させる
ssh -i ~/.ssh/for-next.js.pem ec2-user@パブリックIPアドレス
アプリケーションのディレクトリに移動して、
cd リポジトリ名
最新のコードをpullしてください。
git pull
再度Next.jsアプリケーションをビルドします。
npm run build
既存のアプリケーションプロセスを削除してください。
pm2 delete nextjs-app
再度、アプリケーションを起動してください。
pm2 start npm --name "nextjs-app" -- start
パブリックIPアドレス(http://パブリックIPアドレス/ )にアクセスして、修正後の内容が表示されいるか確認してください。
CI/CD
コードが変更されるたびに毎回手動で修正後のコードをEC2インスタンスに反映させるのは大変なので、CI/CDの設定を行います。
settings > secret > actionsを選択
New Repository secret
を選択して下記の4つを登録してください。
EC2_HOST: EC2インスタンスのパブリックIPまたはDNS名
EC2_USER: SSH接続に使用するユーザー名(デフォルトはec2-user
です。)
EC2_SSH_KEY: EC2インスタンスへのSSH接続に使用する秘密鍵
EC2_PORT: SSH接続に使用するポート(通常は22
です。)
EC2_SSH_KEY
は下記で確認してください。
cat ~/.ssh/for-next.js.pem
下記の形式のものです。
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
ルートディレクトリに.github/workflows
ディレクトリを作成し、その中にdeploy.yml
を作成します。
mkdir -p .github/workflows && touch .github/workflows/deploy.yml
deploy.yml
の内容を下記に変更してください。
name: Deploy to EC2
on:
push:
branches:
- main # mainブランチにプッシュされたときをトリガーとする
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
script: |
cd <>
git pull
npm run build
pm2 delete nextjs-app
pm2 start npm --name "nextjs-app" -- start
appleboy/ssh-action@v0.1.5
GitHub Actions で使用されるアクションの一つで、SSHを使ってリモートサーバーに接続し、指定したコマンドやスクリプトを実行するために設計されています。
このアクションを利用することで、GitHubのワークフローから直接、サーバー上での作業を自動化できるようになります。
GitHub ActionsのVSCode拡張
VSCodeを使用していると、下記が表示されたのでGitHub Actions
の拡張をインストールします。
リポジトリのActions
を選択してください。
インストール後にサインインしてください。
Allow
を選択してください。
VSCodeを再起動してください。GitHub Actionsのアイコンを選択すると、
Workflow実行履歴の確認などができて便利です。
Actions secrets and variables(GitHub Actionsのシークレットと変数)もエディターで編集することができます。
denied (publickey,gssapi-keyex,gssapi-with-mic).
SSH接続でEC2に接続しようとした際に表示されました。
私の場合、コマンドをタイプミスしていました。
以下はGithub Actionsで表示されたエラーです。
2024/06/26 03:10:51 dial tcp :: i/o timeout
これはSSH接続の試みがタイムアウトにより失敗したことを意味します。
2024/06/26 03:42:47 ssh.ParsePrivateKey: ssh: no key found
SSH秘密鍵を解析しようとした際に、有効なキーフォーマットが見つからなかった。
「ssh: handshake failed: ssh: unable to authenticate, attempted methods [none], no supported methods remain」
SSH接続時に認証が失敗し、利用可能な認証方法がないことを示しています。
GitHub Actionsで使用している秘密鍵(EC2_SSH_KEY)がEC2インスタンスに登録された公開鍵と一致しているか確認してください。
could not parse *** as int value for flag port,p: strconv.ParseInt: parsing "***": invalid syntax
文字列を整数値として解釈しようとした際に、構文が無効であるために解析できなかったことを示しています。
今回の場合はport
とあるのでポート番号に誤りがあります。
終わりに
何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉
Discussion