🛸

Next.jsをAWS EC2にデプロイする方法(さくらのドメイン、さくらのメール)

2024/07/07に公開

YouTube でゼロから開発するプログラミング動画チュートリアル「フルスタックチャンネル」を発信中の「はる」と申します。

フルスタックチャンネル

Next.js を AWS EC2 にデプロイする方法を簡単に紹介します。

Docker を使用せずに、Next.js を EC2 にデプロイする方法になります。

GitHub Actions を使用して、自動デプロイも行います。

ドメインは、さくらのドメインを使用して、さくらのメールの設定も行います。

最後には、セキュリティを高めるために、ロードバランサーと WAF を使用したバージョンも紹介します。

ロードバランサーは、ほぼ必須の設定になっていますが、料金がかかるため、必要に応じて設定してください。

構成

こちらの Next.js バージョンです。

https://zenn.dev/hathle/articles/django-ec2-deploy

IAM 作成

IAM でユーザーを作成します。

FullAccess でポリシーを許可していますが、必要な権限だけ設定するようにしてください。

S3 バケット作成

必要に応じて S3 を作成します。

VPC 作成

VPC を作成します。

アベイラビリティーゾーン数は必要に応じて変更してください。

セキュリティグループ作成

セキュリティグループを作成します。

インバウンドルールで下記のポートをあけます

  • SSH
  • HTTP
  • HTTPS
  • PostgreSQL

EC2 インスタンス作成

先ほど作成した VPC とセキュリティグループを連携します。

サブネットは public1-ap-northeast-1a を指定

Ubuntu Server 22.04

インスタンス:t2.micro

t2.micro はかなり遅いので、必要に応じて変更してください。

オススメ:t3.medium

RDS 作成

PostgreSQL を使用します。

先ほど作成した EC2 と連携します。

インスタンス:t3.micro

Elastic IP 作成

Elastic IP を作成して、EC2 に紐付けます。

先に Elastic IP を作成しておくと、IP アドレスが固定されるので、便利です。

Next.js プロジェクト作成

Next.js プロジェクトを作成して、GitHub にアップロードしておきます。

SSH 接続

EC2 を作成したときに作成した xxx.pem を使用して SSH 接続します。

ssh -i "~/.ssh/xxxxx.pem" ubuntu@ec2-xxxxx.ap-northeast-1.compute.amazonaws.com

RDS 接続

SSH 接続ができることを確認したら、RDS のデータベースを作成します。

RDS はプライベートサブネットに作成しているので、EC2 から RDS に接続します。

ローカルから接続したい場合は、以下のコマンドを実行します。

ssh -i ~/.ssh/xxxxx.pem -L 6000:xxxxx.xxxxx.ap-northeast-1.rds.amazonaws.com:5432 -N -T ubuntu@xx.xxx.xxx.xx

SQL Shell は便利なので、使用しています。

https://www.postgresql.org/download/

・解説
https://zenn.dev/hathle/books/t3-stack-auth-book/viewer/06_postgresql

データベースを作成します。

create database xxxxx;

アプリに日本語(ふりがな)でソートがある場合は、上記ではふりがなのソートは有効にならないため、以下のコマンドでデータベースを作成します。

CREATE DATABASE xxxxx WITH OWNER postgres ENCODING 'UTF8' LC_COLLATE='ja_JP.UTF-8' LC_CTYPE='ja_JP.UTF-8' TEMPLATE=template0;

Node.js インストール

https://github.com/nvm-sh/nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm
nvm install --lts
node -v
npm i -g pm2
sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl start nginx
sudo service nginx restart

この時点で、EC2 の IP アドレスにアクセスして、Welcome to nginx! が表示されることを確認します。

リポジトリクローン

GitHub からリポジトリをクローンします。

git clone https://github.com/xxx/xxxxxx.git
cd xxxxxx

Github の remote 先を変更

Github Actions から git pull するためには、origin をgit@github.comにする必要があります。

git remote -v
origin  https://github.com/xxx/xxxxxx.git

git remote set-url origin git@github.com:xxx/xxxxxx.git

環境変数作成

.envファイルを作成します。

データーベースは RDS の接続先情報を設定します。

必要に応じて変更してください。

nano .env
DATABASE_URL=postgresql://postgres:{RDSのパスワード}@{RDSのエンドポイント}:5432/{作成したデータベース名}?schema=public
NEXTAUTH_SECRET=
NEXTAUTH_URL=http://{EC2のIPアドレス}
NEXT_PUBLIC_APP_URL=http://{EC2のIPアドレス}
EMAIL=
EMAIL_HOST=
EMAIL_PASSWORD=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=
AWS_BUCKET_NAME=

Nginx 設定

Nginx の設定を行って、画面を表示します。

sudo nano /etc/nginx/conf.d/xxx-production.conf
server {
    listen 80;
    server_name {EC2のIPアドレス};
    client_max_body_size 10g;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
sudo nginx -t
sudo systemctl restart nginx

この時点で、EC2 の IP アドレスにアクセスして、502 Bad Gateway が表示されることを確認します。

セットアップ

Next.js のセットアップを行います。

npm install
npx prisma migrate deploy
npm run build
pm2 start 'npm start' --name app
pm2 log

これで、EC2 の IP アドレスにアクセスして、Next.js の画面が表示されることを確認します。

SSH 公開鍵作成

Github Actions からデプロイをするために、ssh 公開鍵を作成します。

ssh-keygen -t rsa -b 4096 -C "your_email@gmail.com"

# 公開鍵を登録
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Deploy keys

Github の Settings の Deploy keys に id_rsa.pub の公開鍵を登録します。

設定からSSH and GPG keysをクリックしてアクセスします。

ここに登録しないと、EC2 で git pull できません。

Github Actions 環境変数

Github の Secret and variables の Actions に環境変数を追加します。

HOST_PROD ec2-xx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
USERNAME_PROD ubuntu
SSH_KEY_PROD  id_rsaの中身
AWS_ACCESS_KEY_ID xxx
AWS_SECRET_ACCESS_KEY xxx

GitHub Actions

Github Actions から git pull して、リスタートまでを自動的に実行します。

.github/workflows/deploy.yml

name: Deploy to EC2 Production

on:
  push:
    branches:
      - release/production

jobs:
  build-and-deploy:
    name: Build and Deploy to EC2
    runs-on: ubuntu-22.04

    steps:
    - name: Checkout to the branch
      uses: actions/checkout@v2

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1

    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST_PROD }}
        username: ${{ secrets.USERNAME_PROD }}
        key: ${{ secrets.SSH_KEY_PROD }}
        script: |
          cd /home/ubuntu/xxxx
          git pull
          npm install
          npx prisma migrate deploy
          npm run build
          pm2 del app
          pm2 start 'npm start' --name app

ドメイン取得

さくらのドメインでドメインを取得します。

https://domain.sakura.ad.jp/

AWS Route53

Route53 でドメインを設定します。

さくらのドメインで取得したドメインを登録します。

NS レコードを 4 つコピーします。

最後の.は削除してください。

さくらのドメインでネームサーバー設定

さくらのドメインで、取得したドメインの Whois を設定します。

Whois のタブでネームサーバーを編集をします。

ネームサーバーの反映は少し時間がかかるかもしれません。

さくらのレンタルサーバ

さくらのレンタルサーバで、ドメインを設定します。

ドメイン新規追加画面に遷移します。

さくらインターネットで取得の独自ドメインを使う場合で、ドメインを追加します。

ゾーン情報でゾーンが設定されていることを確認します。

さくらのメール設定

さくらのレンタルサーバーでメールドレスの新規ユーザーを追加します。

Route53 MX レコード設定

Route53 で MX レコードを設定します。

さくらのレンタルサーバーのサーバー情報のホスト名をコピーして設定します。

10 ホスト名

Route53 TXT レコード設定

Route53 で TXT レコードを設定します。

さくらのレンタルサーバーのサーバー情報の IPv4 をコピーして設定します。

"v=spf1 ip4:xxx.xxx.x.xx -all"

Route53 A レコード設定

Route53 で A レコードを設定します。

EC2 に割り当てた Elastic IP を設定します。

xxx.xxx.x.xx

以上で Route53 の設定は完了です。

設定確認

レコードの設定が正しいか確認します。

ネームサーバーの反映が完了しているか確認しておきます。

dig xxxx.com ns +short
dig xxxx.com mx +short
dig xxxx.com txt +short

HTTPS 化

Let's Encrypt を使用します。

sudo apt-get install letsencrypt
sudo systemctl stop nginx
sudo letsencrypt certonly --standalone -d xxxx.com
sudo nano /etc/nginx/conf.d/xxx-production.conf

nginx の設定を変更します。

server {
    listen 80;
    listen [::]:80;
    server_name xxxx.com;
    return 301 https://$host$request_uri;
}

server {
    listen  443 ssl;
    server_name xxxx.com;
    client_max_body_size 10g;

    ssl_certificate         /etc/letsencrypt/live/xxxx.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/xxxx.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

nginx のチェック

sudo nginx -t
sudo systemctl start nginx

環境変数変更

.envファイルのNEXTAUTH_URLNEXT_PUBLIC_APP_URLを変更します。

NEXTAUTH_URL=https://{ドメイン}
NEXT_PUBLIC_APP_URL=https://{ドメイン}

ビルド

npm run build

pm2 リスタート

pm2 del app
pm2 start 'npm start' --name app

動かない場合

nginx のエラーログを確認します。

sudo tail -f /var/log/nginx/error.log
sudo ufw allow 80
sudo ufw allow 443
curl http://127.0.0.1:3000
sudo systemctl restart nginx

証明書自動発行

定期的に証明書を更新するために、cron を使用します。

sudo crontab -e
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
00 05 01 * * sudo systemctl stop nginx; sudo letsencrypt renew; sudo systemctl start nginx

nodemailer を使用してメール送信

さくらのレンタルサーバーのメールを使用する場合です。

.env ファイルにメールの設定を追加します。

EMAIL_HOST は、さくらのレンタルサーバーのホスト名です。

EMAIL=test@xxxx.com
EMAIL_HOST=xxxx.sakura.ne.jp
EMAIL_PASSWORD=xxxxxxxxxxxxxxxx
import nodemailer from "nodemailer"

// SMTPサーバの設定
const transporter = nodemailer.createTransport({
  host: process.env.EMAIL_HOST!,
  pool: true,
  port: 587,
  secure: false,
  requireTLS: true,
  auth: {
    user: process.env.EMAIL!,
    pass: process.env.EMAIL_PASSWORD!,
  },
})

// メール送信
export const sendEmail = async (
  subject: string,
  body: string,
  sendTo: string
) => {
  const mailOptions = {
    // 送信元
    from: `テスト <${process.env.EMAIL}>`,
    // 送信先
    to: sendTo,
    // 件名
    subject: subject,
    // 本文
    html: body,
  }

  // メール送信
  transporter.sendMail(mailOptions, (error) => {
    if (error) {
      console.log(error)
    }
  })
}

使用例

パスワード再発行メール送信の例です。

import { sendEmail } from "@/actions/sendEmail"
import { db } from "@/lib/prisma"

interface SendForgotPasswordOptions {
  userId: string
}

// パスワード再発行メール送信
export const sendForgotPassword = async ({
  userId,
}: SendForgotPasswordOptions) => {
  try {
    // ユーザー取得
    const user = await db.user.findUnique({
      where: {
        id: userId,
      },
      include: {
        PasswordResetToken: {
          orderBy: {
            createdAt: "desc",
          },
          take: 1,
        },
      },
    })

    if (!user || !user.email) {
      return { error: "ユーザーが存在しません" }
    }

    // トークン取得
    const token = user.PasswordResetToken[0].token

    // パスワード再発行リンク
    const resetPasswordLink = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password/${token}`

    // 件名
    const subject = "パスワード再発行のご案内"

    // 本文
    const body = `
<div>
  <p>
    ご利用ありがとうございます。<br />
    あなたのアカウントでパスワード再発行のリクエストがありました。
  </p>

  <p><a href="${resetPasswordLink}">パスワードの再発行を行う</a></p>

  <p>このリンクの有効期限は24時間です。</p>
  <p>このメールに覚えのない場合は、このメールを無視するか削除して頂ますようお願いします。</p>
</div>
 `

    // メール送信
    await sendEmail(subject, body, user.email)
    return { success: "パスワード再発行用のメールをお送りしました" }
  } catch (error: any) {
    console.error("[SEND_FORGOT_PASSWORD]", error)
    return { error: "メール送信中にエラーが発生しました" }
  }
}

ロードバランサーと WAF

ロードバランサーとは?

ロードバランサーは、複数のサーバーにトラフィックを分散させるネットワーク機器またはソフトウェアの一種です。負荷分散を行うことで、個々のサーバーへの過剰な負担を防ぎ、以下のようなメリットを提供します:

  1. 可用性の向上:ロードバランサーは、アプリケーションへのリクエストを複数のサーバーに振り分けることで、どのサーバーがダウンしても他のサーバーが代わりに処理を引き継ぎます。これにより、アプリケーションの稼働率を高く保つことができます。

  2. スケーラビリティ:ロードバランサーを利用すると、トラフィック量の増加に応じてサーバーを動的に追加できます。これにより、急激なアクセス増加時にもスムーズなサービス提供が可能になります。

  3. パフォーマンスの最適化:ロードバランサーは、最も利用可能なサーバーにトラフィックを振り分けることで、アプリケーション全体の応答速度を向上させます。これにより、ユーザーの体験が向上します。

AWS では、特に Application Load Balancer(ALB)が HTTP および HTTPS トラフィックの負荷分散に適しています。ALB は、リクエストレベルでの負荷分散が可能で、Cookie ベースのセッション管理や SSL 終端(SSL の復号処理)などの高度な機能を備えています。これにより、Web アプリケーションの動作がより安定し、セキュリティの強化も図れます。

なぜロードバランサーを設定する必要があるのか?

ロードバランサーの設定は、アプリケーションの可用性、スケーラビリティ、パフォーマンスの向上のために不可欠です。具体的な理由は以下の通りです:

  • 高トラフィック対応:複数のサーバーにリクエストを分散させることで、サーバーダウンのリスクを軽減し、高いトラフィックにも対応可能です。
  • シームレスな拡張:アプリケーションの成長に合わせて、サーバーを簡単に追加・削除でき、運用負荷が軽減されます。
  • DDoS 対策:ALB には、基本的な DDoS(分散型サービス拒否攻撃)対策機能が含まれており、攻撃による負荷集中を軽減します。

WAF とは?

Web アプリケーションファイアウォール(WAF)は、Web アプリケーションに対する不正アクセスやサイバー攻撃を防ぐためのセキュリティサービスです。WAF は HTTP および HTTPS のリクエストを監視し、以下のような攻撃からアプリケーションを保護します:

  1. SQL インジェクション:データベースを操作する不正なクエリを含むリクエストを検出し、アプリケーションデータの漏洩や破壊を防ぎます。

  2. クロスサイトスクリプティング(XSS):攻撃者が Web ページに不正なスクリプトを挿入し、ユーザーのブラウザでそのスクリプトが実行されるのを防ぎます。

  3. ファイルインクルージョン攻撃:アプリケーションにファイルの読み込みや実行を強制させる攻撃をブロックし、アプリケーションやデータの不正操作を防ぎます。

AWS WAF を使うことで、攻撃パターンに基づいたルールを柔軟に設定し、特定の IP アドレスや地域からのアクセス制限、リクエストサイズの制限、カスタムルールの設定が可能です。

なぜ WAF を設定する必要があるのか?

WAF の設定は、アプリケーションのセキュリティを強化し、攻撃からの防御を行うために不可欠です。具体的な理由は以下の通りです:

  • 攻撃防御:SQL インジェクションや XSS といった一般的な Web アプリケーション攻撃からアプリケーションを保護し、データ漏洩やアプリケーションの不正操作を防ぎます。
  • 規制遵守:GDPR や PCI-DSS などのデータ保護規制を遵守するために、WAF の導入が必要な場合があります。
  • トラフィックフィルタリング:特定の地域や IP アドレスからのアクセス制限を行い、セキュリティリスクを軽減します。

ロードバランサーと WAF を組み合わせることで、可用性、パフォーマンス、セキュリティを同時に確保でき、より強固なアプリケーションインフラを構築できます。

ターゲットグループ作成

トラフィックを分散させるためのターゲットグループを作成します。

  1. AWS マネジメントコンソールで「EC2」サービスを選択し、「ターゲットグループ」をクリックします。
  2. 「ターゲットグループの作成」を選択し、以下の設定を行います:
  • ターゲットタイプ:インスタンス
  • ターゲットグループ名:任意の名前
  • プロトコルとポート:HTTP:80
  • IP アドレスタイプ:IPv4
  • VPC:作成した VPC を選択
  • プロトコルバージョン:HTTP1

設定完了後、使用可能なインスタンスを選択し、80 番ポートを「保留中として以下を含める」に追加します。

ターゲットに 80 番ポートが追加されていることを確認したら、「ターゲットグループの作成」ボタンをクリックします。

ロードバランサー作成

次に、Application Load Balancer(ALB)を作成します。

  1. AWS マネジメントコンソールで「ロードバランサー」を選択し、「ロードバランサーの作成」をクリックします。
  2. ロードバランサーのタイプとして「Application Load Balancer」を選択します。

  1. ロードバランサー名を入力します。

  1. ネットワークマッピングで、作成した VPC を選択し、少なくとも 2 つのサブネットを選択します。

  1. セキュリティグループで、HTTP:80 と HTTPS:443 を許可するセキュリティグループを選択します。

  1. リスナーとルーティングで、HTTP:80 を先ほど作成したターゲットグループに設定します。

  1. WAF の設定:必要に応じて、AWS WAF を有効化します。

料金が発生するため、詳細は公式サイトで確認してください。

https://aws.amazon.com/jp/waf/pricing/

  1. 設定が完了したら、「ロードバランサーの作成」ボタンをクリックします。

Route53

ドメイン名をロードバランサーに関連付けるために、Route 53 で A レコードを作成します。

  1. AWS マネジメントコンソールで「Route 53」を選択し、該当するホストゾーンを開きます。
  2. 以前設定した固定 IP の A レコードを削除し、新しい A レコードを作成します。
  3. エイリアスをオンにし、先ほど作成したロードバランサーを選択します。

AWS Certificate Manager での SSL 証明書の作成

HTTPS 通信を行うために、SSL 証明書を作成します。

  1. AWS マネジメントコンソールで「AWS Certificate Manager」を選択し、「証明書のリクエスト」をクリックします。
  2. ドメイン名を入力し、指示に従ってリクエストを完了します。

リクエストが完了したら、「Route 53 でレコードを作成」ボタンを押してください。

自動的に Route53 に CNAME レコードが作成されます。

Route53 には、最低限 4 つのレコードが作成されていれば問題ありません。

MX と TXT レコードは、メールの設定がある場合は必要になります。

ロードバランサーのリスナー設定の修正

HTTPS 通信に対応するために、ロードバランサーのリスナーを修正します。

  1. AWS マネジメントコンソールで「ロードバランサー」を選択し、該当するロードバランサーの詳細ページを開きます。
  2. リスナーの追加を選択し、以下の設定を行います:
  • プロトコル:HTTPS
  • ポート:443
  • ターゲットグループ:先ほど作成したターゲットグループ

セキュアリスナーの設定

  1. 証明書の選択で、AWS Certificate Manager で作成した SSL 証明書を選択します。

  1. 設定が完了したら、「追加」ボタンを押します。

HTTP リスナーのリダイレクト設定

  1. 既存の HTTP:80 リスナーを編集します。
  2. 「アクションを追加」で、以下の設定を行います:
  • アクションの種類:リダイレクト
  • プロトコル:HTTPS
  • ポート:443
  1. HTTP でアクセスされたリクエストを自動的に HTTPS にリダイレクトするように設定します。

nginx の設定修正

EC2 の nginx 設定を修正し、ロードバランサーからの HTTP リクエストに対応します。

  1. EC2 インスタンスに SSH 接続し、nginx 設定ファイルを編集します。
  2. nginx の設定ファイルを開きます。
sudo nano /etc/nginx/conf.d/xxx-production.conf
  1. 以下のように nginx 設定を修正します。ロードバランサーからのリクエストは 80 番ポートで受け取るため、HTTPS 設定は不要です。
server {
    listen 80;
    server_name xxx.com;
    client_max_body_size 10g;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
  1. nginx の設定を再読み込みし、適用します。
sudo nginx -t
sudo systemctl restart nginx
  1. ドメインにアクセスして、HTTPS でサイトが正しく表示されることを確認します。

WAF 設定

最後に、WAF の設定を行い、ウェブアプリケーションのセキュリティを強化します。

  1. AWS マネジメントコンソールで「WAF & Shield」を選択し、「Web ACLs」をクリックします。
  2. WAF のルールを設定し、ロードバランサーに紐付けた Web ACL を確認します。

  1. 必要に応じて、AWS が用意しているルールを適用し、攻撃からアプリケーションを保護します。

WAF の設定が完了すると、SQL インジェクションやクロスサイトスクリプティング(XSS)などの一般的な攻撃からの保護が可能になります。

ソースコード

LINE 登録すると講座のソースコードがダウンロードできます。

新着の講座がいち早く届きますので、ぜひ登録をお願いします。

LINE お友達登録
https://lin.ee/NKoTJnV

フルスタックチャンネル

講座の不明点や個人開発の疑問点など何でもご質問ください。

https://www.fullstackchannel.com/

さらに多くの知識と技術を共有し、また新しい学びの機会を提供することを楽しみにしています。

よろしくお願いします。

Discussion