【AWS】Next.js+LaravelをECS+Fargateにデプロイする時のアレコレ

最近業務でNext.js+LaravelのアプリケーションをAWSのAmazon ECS(Fargate)にデプロイするタスクを担当していて、デプロイするにあたりNext.js側、Laravel側でやっておくこと、エラー対応などをまとめておく。

  • ECSへのデプロイはEC2へのデプロイに比べそもそもネットに情報が少ない
  • ECSの中でもデータプレーン(コンテナ実行環境)にEC2を使った記事が多くFargateを使った記事はかなり少ない
  • ECS+Fargateへのデプロイでも本記事のようなフロントエンド+APIの構成のデプロイ例はマジで少ない

という状態で結構苦戦したので、自分で苦戦したことを残しておくとともに同じ構成でデプロイする方に向けて参考になれば嬉しいです。

ECS、ECR、Fargateについての技術説明・全体的なデプロイ作業の手順についてはこの記事では扱わないが、この記事(というかZennのbooks)がめちゃくちゃ参考になったかつわかりやすかったのでこれに沿って作業したのでオススメ。

Amazon ECSはElastic Container Serviceの略で「コンテナ化されたアプリケーションを簡単にデプロイ、管理、およびスケーリングできるフルマネージドコンテナオーケストレーションサービス」である。

Zennのbooksは技術はNuxt.js+Railsと見事に被ってないがとてもわかりやすかったのでオススメ。(こういうのが無料で手に入るってすごい…)

この記事では

  • Docker
  • Next.js
  • Laravel
  • AWS

に分けてまとめていく。

目次

Docker

まずはDockerコンテナに関係するところから。

ローカル開発でDocker環境を使う場合にDocker Composeでコンテナを一元管理してdocker-compose.ymlにvolumesオプションでローカルとコンテナ側でソース等を関連付けることがよくあるが、ECSにデプロイする場合はDocker Composeは使えないので基本的にDockerfile内でローカルのソースをコンテナ側にコピーする(COPYコマンド実行)必要がある。

PHP

コンテナを立ち上げる時にLaravel側のソース変更を反映をする必要があり、そこにはenvファイルやconfigファイルの変更も入っている可能性があるのでDockerfileにコマンドを追加してキャッシュをクリアする。

WORKDIR /app

COPY ./api .

RUN composer install && \ 
		php artisan cache:clear && \
		php artisan config:clear && \
		chmod -R 777 storage
  • php artisan cache:clear:envファイルの変更を反映する
  • php artisan config:clear:configディレクトリのファイルの変更を反映する

configファイルの内容を反映するために使う選択肢にphp artisan config:cacheがあるが、これはキャッシュファイルを作成するためのものなのであまり使わない方が良い。

以下の内容なら良いと思います。(というかデプロイ環境ならこっちの方が良いかも?)

WORKDIR /app

COPY ./api .

RUN composer install && \ 
		php artisan cache:clear && \
		php artisan config:clear && \
    php artisan config:cache && \
		chmod -R 777 storage

Nginx

Nginx・Laravelのコンテナ間通信

今回の構成ではNext.js→Nginx→Laravelという通信の流れでNginxとLaravelでコンテナ間通信が生じる。

Dockerでのローカル開発では宛先となるPHPコンテナのサービス名を用いるが、ECSでのコンテナ間接続では宛先のホストはlocalhostになるのでローカル用とECS用の設定ファイルが必要になる。

ローカル開発だけの段階では設定ファイルはdocker/nginx/conf/default.confに置いていたけど、以下のように分けることで対応した。

  • docker/nginx/conf/local/default.conf:ローカル開発用
  • docker/nginx/conf/ecs/default.conf:ECSデプロイ用

コンテナ間通信を表すコードの違いは以下のとおりである。

ローカル開発用

# FastCGIサーバーのアドレスを指定
fastcgi_pass api:9000;

ECS用

# FastCGIサーバーのアドレスを指定
fastcgi_pass localhost:9000;

ここは調べてもあまり情報が出てこなかったので手こずった。

Laravelのソースコードのコピー

NginxコンテナにLaravelのソースコードをコピーしないとNginxにアクセスした時に404になる。(ローカルのapiディレクトリにLaravelを配置している場合)

COPY ./api /app

ローカル環境ではこのコピーがなくても問題なく動いたので全然気づかなかったけど、設定ファイルでLaravelのpublicディレクトリを見に行く設定をしているのでそれはそうだよなとなった。(逆になんでローカルでは動いたんだろうと思っている…)

Node(Next.js用)

Next.js側でもAPIのドメイン等の環境変数の切り替えが必要だが、Next.jsはnext startすると.env.productionを(ファイル名指定で)読み込みにいくので、以下のようにしてdev環境, stg環境, prod環境で使用する環境変数ファイルを切り替えるようにした。(他にも方法があると思うが今の自分にはこれがシンプルでやりやすかった)

# dev環境
COPY ./client/.env.dev ./.env.production

# stg環境
COPY ./client/.env.stg ./.env.production

prod環境の場合は、準備した.env.productionを使えば良いので、上記コードの前に実行するソースコードのコピーコマンドだけで問題ない。

Next.jsの環境変数の取り扱いはルールがややこしいので必要な時に何度も調べて確認するのが良いと思う。

Next.js

getStaticPropsを使う時のリクエスト先のエンドポイント

SSG使う場合のメソッドであるgetStaticProps内で使うAPIのエンドポイントのルールはローカル開発とは異なる。

結論から言うとクライアントサイドからAPIにリクエストするときと同じエンドポイントでOK。

ローカルのDocker環境の場合は例えば、Nginxコンテナ(サービス名はnginx)のポートが

  • ローカル:8000
  • コンテナ:80

とした場合、

  • クライアントサイドからリクエストするエンドポイント:http://localhost:8000
  • getStaticProps内でリクエストするエンドポイント:http://nginx:80

となるが、ECSにデプロイした場合は、上記2つは同じエンドポイントを指定する。

ローカル開発でエンドポイントが異なる件に関してはZennのスクラップで雑だけど残している。

ヘルスチェック用のエンドポイントを定義する

ECSのコンテナにALB(Application Load Balancer)経由でアクセスする場合はヘルスチェックが必要になるため、ヘルスチェック用のAPIエンドポイントを設定する必要がある。

ヘルスチェック対象のパスを/api/healthcheckとした場合は、pages/api/healthcheck.tsを作成する。

import { NextApiRequest, NextApiResponse } from 'next'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.statusCode = 200
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ status: 'ok' }))
}

ヘルスチェックは指定のエンドポイントからステータスコード200のレスポンスが返却されるとOK

後から書くが、バックエンドにもALBを設ける場合はLaravel側にも同様にヘルスチェック用のAPIエンドポイントが必要になる。

Laravel

環境でenvを切り替える

LaravelもNext.js同様、デプロイする環境(dev, stg, prod)で読み込むenvファイルを切り替える必要がある。

切り替え方法はいくつかあるが、この記事では個人的に簡単だと思った方法を紹介する。

public/.htaccessを以下のように修正する。

# Set APP_ENV
SetEnvIf Host "api.sample.com" APP_ENV=prod
SetEnvIf Host "api.stg.sample.com" APP_ENV=stg
SetEnvIf Host "api.dev.sample.com" APP_ENV=dev

これで以下のような設定にすることができる

  • https://api.sample.co/〜: env.prodを読み込む
  • https://api.stg.sample.co/〜: env.stgを読み込む
  • https://api.dev.sample.co/〜: env.devを読み込む

他の方法はこちらの記事に書かれている。(結構古い記事…)

Laravel Sanctum用の設定を変更する

これはフロントエンドとLaravelの認証にLaravel SanctumのSPA認証を使っている場合の話になるが、環境変数の修正が必要になる。(以下はdev環境を想定)

SESSION_DOMAIN=.dev.sample.com # ドメインの頭に . をつける
SANCTUM_STATEFUL_DOMAINS=dev.sample.com # フロントエンドのドメインを指定

バックエンドのドメインにapi.dev.sample.comのようにサブドメインを使っている場合でも、SESSION_DOMAINには独自ドメイン(ここでいうとdev.sample.com)の頭に.をつけた値を設定する。

ローカル開発の場合はSESSION_DOMAIN=localhostでいけたのは何でだろうか…(.をつけてない)

Laravel Sanctumについては別の記事でまとめている。

ヘルスチェック用のエンドポイントを定義

バックエンドのコンテナへのアクセスにALBを使う場合はヘルスチェック用のAPIエンドポイントを設ける。

フロントエンドと同様に/api/healthcheckを対象パスとしてroutes/api.phpに追記する。

// ヘルスチェック用
Route::get('/healthcheck', function () {
    return response()->json(200);
});

AWS

デプロイするソースコードを修正した時の再デプロイ手順

ECS+Fargateにデプロイしたアプリケーションのソースコード(アプリ・Docker環境とも)変更を再度デプロイしたい時の手順は以下の順番で行う。

  • ECRへのログイン
  • イメージのビルド
  • イメージへのタグ付け
  • イメージをECRへプッシュ
  • タスク定義のリビジョンアップ
  • サービスの更新(最新のタスク定義を選択)

これを毎回する必要があるので、自動化したいなという気持ちが湧き上がる。

AWSのCode BuildCode Pipelineというサービスでできるらしい。

GUIツールでRDSに接続する方法

まず、GUIツールを使ってDBにアクセスするのは絶対にしたい。

EC2インスタンス上でECSコンテナを動かす場合は、そのEC2にSSH接続してRDSに接続できるので

  • RDSの情報(ホスト/ユーザー名/パスワード/ポート)
  • SSH用の情報(EC2のホスト/ユーザー名/秘密鍵)

を使ってGUIツール(Sequel AceTable Plus)でRDSに接続することができる。

が、Fargate上でコンテナを動かす場合はそれができない。

ECS Execを使えばLaravelのコンテナに入ることができるので、マイグレーションとかは実行できるようになる。

コンテナ実行環境にFargateを使っている場合は、踏み台サーバー的としてEC2を準備してそのEC2にSSHしてRDSに接続しにいく。(EC2のスペックは最小限でOK)

最後に

初めて実務でAWSをガッツリ触るタスクが「Next.js+LaravelのアプリケーションをECS+Fargateにデプロイする」という重めのものだったけど、学びになることがたくさんあってよかった。(めちゃくちゃ苦戦しました。)

心残りがgetServerSidePropsがうまくいかず、SSGだとまずい箇所は全てuseEffect内でクライアントでデータフェッチするように変更したこと。。

せっかく実務でAWSを触ったので最低限のキャッチアップはして記事を書こうかなと思っている。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

大学院(機械工学)→重工業→エンジニア→プロダクトマネージャー(PdM)兼エンジニア

神戸で「つながる勉強会」を運営↓
https://tsunagaru-kobe.connpass.com/

神戸グルメのインスタアカウントを運用しています。

目次
閉じる