トレンドのVuejs/NuxtをAws ECS, FargateでSSR、詳細解説します🚀

トレンドのVuejs/NuxtをAws ECS, FargateでSSR、詳細解説します🚀

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!

ウェブアプリケーションの開発でデファクトスタンダードな VueJS / NuxtJS ですが、SSR 時のデプロイ方法についてはみなさん悩まれることが多いのではないでしょうか?

SPA であれば AWS Cloud Front と S3 のコンビで十分に運用可能ですが、要件に SSR が入ってくると Nodejs のランタイムが必要になりますね。

そんな時に大活躍するのが ECS, Fargate です!

想定する読者

  • AWS サーバーレス環境で NuxtJS で SSR したいヒト
  • NuxtJS のデプロイ方法について悩んでいるヒト

はじめに

Lambda での SSR 時の問題

実は、Lambda と API Gateway のコンビの方がもっと手軽にSSRを構築可能です。しかし、Lambda にはレスポンスボディサイズの制約、デプロイ可能な ZIP サイズの制約などがあり、開発が進むにつれて窮屈になってきます…。

そもそも Lambda はマイクロサービスの思想で付き合った方が健全だと感じます(制約も多いですし)ので、SSR、つまりフロントエンド全体のレンダリングの責任を持たせるのはアーキテクチャーとしてイマイチな気がします。

例えば、わたしたちのコーポーレートサイトは、現在 VueJS / NuxtJS で構築されていますが、以前までは手軽に AWS Lambda で SSR させていましたが、制作が拡大させていくにつれて Lambda の制約事項に色々と引っ掛かり、 2019 年よりインフラを ECS, Fargate へ完全に移管しています。

EC2 運用での SSR 時の問題

例えば、Laravel でバックエンドの API を構築している場合、Laravel を実行する Nginx、PHP 環境が必要です。

それらを EC2 インスタンスに展開し、ELB と繋いで…ということをしていると、デプロイプロセスが複雑になったり、EC2 自体にVulsを実行するなどしてセキュリティのアップデートを定期的に実行しないといけません。

これは、Nuxtjs の SSR で同様のことが言えます、継続的にソフトウェアのバージョンが形骸化しないようにアップデート、パッチを当てるなどが必要です。

Aws にマネージ メントを任せられる部分は、どんどん任せていき、開発効率向上に努めていくことが重要です。Fargate を用いることで、EC2 インスタンスの管理から解放されるでしょう。

ECS, Fargate を使用した場合の開発フロー

実際に開発を行った場合の流れを解説します。

ローカル環境の準備

まず、ローカルで開発時する際に Docker コンテナーを介してNPMコマンドを実行するか、介さないかを選択する必要があります。

私の推奨は、NuxtJS や NextJS の SSR 程度であれば、ローカル開発では Docker を介さないで開発することを推奨します。

複数人での開発時に Docker わからない人は苦しむし、NVM で NodeJS のバージョンを指定すれば大体不具合発生しません。( SSR で OS 依存した処理がなければ)NodeJS のバージョンだけ最低限合わせて開発するようにしましょう。

  1. 公式通りに Nuxt 環境を構築
  2. Docker 向けに package.json の scripts を調整(後述)
  3. Dockerfile を使用してイメージを作成
  4. docker run -d … ローカルで起動確認 ( SSR できているか確認 )
  5. ECR へイメージをプッシュ

公式通りの Nuxt 環境を構築

こちらのページに則って環境を構築します。

$ yarn create nuxt-app sampleproject
> ...
$ cd sampleproject 
$ npm run dev
npm run dev

Docker 向けに package.json の scripts を調整

HOST=0.0.0.0とPORTを指定します。

...
"scripts": {
  "dev": "HOST=0.0.0.0 PORT=3000 nuxt",
  "build": "nuxt build",
  "start": "HOST=0.0.0.0 PORT=3000 nuxt start",
},
...

Dockerfile を使用してイメージを作成

NodeJS のイメージは公式を指定しましょう。タグでバージョン指定が可能です。

FROM node:10.20.1-jessie

RUN mkdir -p /var/www/sampleproject
WORKDIR /var/www/sampleproject
COPY ./ /var/www/sampleproject
RUN npm run build

EXPOSE 3000

ENTRYPOINT ["npm", "run", "start"]
Docker hub node

docker run -d … ローカルで起動確認

事前に Docker Desktop をインストールしておきましょう。以降で使用する Docker コマンドが一緒にインストールされます。

$ docker build -t sampleproject .
$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
sampleproject           latest              XXXXXX        52 seconds ago      838MB
$ docker run -d -p 3000:3000 sampleproject
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
4085a439c0bd        sampleproject       "npm run start"     4 seconds ago       Up 3 seconds        0.0.0.0:3000->3000/tcp   hungry_williams

access to : https://blog.ragate.co.jp:3000

ECR へイメージをプッシュ

事前に ECR へリポジトリを作成しておいて下さい。

$ npm run build
$ $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION})
$ docker build -t sampleproject:latest .
$ docker tag sampleproject:latest:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/sampleproject:latest
$ docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/sampleproject:latest
Aws ECR

これで AWS デプロイ前の下準備が終わりました!

ECR へ Docker イメージがちゃんとプッシュされているか、ローカルで起動できているか、確認してくださいね。

AWS 側の準備

ALB を構築したら、基本的に ECS の設定をザクザクしていくだけです。タスク定義にて Docker 定義を色々と書きますが、ここがかなり難所です。

Docker に深く精通いていないと細かいチューニングは難しいので、細かい設定は抜きにしてざっくりと構築します。

  1. ALB の構築
  2. ECS でタスク定義を作成
  3. ECS でクラスターを作成
  4. クラスターにサービスを作成しタスクを展開

ALB の構築

AWS のマネージメントコンソールより、ALB を構築します。( VPC などの細かい設定は割愛します )

ALB構築画面ー1
ALB構築画面ー2

ECS でタスク定義を作成

ECS の画面より、タスクの定義の作成を行います。

タスク定義の作成

起動タイプは Fargate を使用します。

起動タイプの選択、Fargate

基本的に初期設定で進みますが、「コンテナーの追加」だけ自分で設定を行います。先ほど ECR へ用意したイメージを指定する必要があります。

また、ここの CPU とメモリーサイズのが、使用料金に関わってきますので、学習時は必ず最低スペックなどにしてください。( 最高スペックにしてアクセスしまくると軽く月額 10 万円を超えます )

タスクの定義の作成
Dockerコンテナーを追加

あとはどの VPC へアタッチするかなどの設定ですので、割愛します。お手元の VPC に合わせて環境構築を行ってください。

ECS でクラスターを作成

私たちの経験上、クラスターはサービス単位( 1 ドメイン単位)で構築するのが良いでしょう。なぜなら、ALB のターゲットグループへの ECS よしなにインスタンスをアタッチしますが、その際にパスパターンでアタッチするからです。詳しく知りたい方は、構築後にターゲットグループを見てみてください。

クラスターの作成

Fargate 指定します、クラスターの役割は、「 Docker コンテナーを実行するインスタンスの管理」となりますので、ここでもし EC2 を指定すれば、 Docker コンテナーを EC2 でをマネージします。Fargate にすることで、Lambda のような感覚で EC2 インスタンスは隠蔽され、裏側でよろしくやってくれます。素敵ですね。ただ、私の所感では、あくまでも OS レベルのセキュアは AWS がやってくれますが、ネットワークのレベルでのセキュアは開発者が担保しないといけません。( Private Subnet、Public Subnet のアクセスポリシー厳格にするなど)Lambda との大きな違いは、インスタンスの属するネットワークのセキュアは開発者に依存すると言えます。( Lambda でも VPC にアサインできるような機能ありますがあれはあくまでもアクセスができるようになる話と解釈しています)

クラスターテンプレートにFargateを指定

以降は VPC の設定などになりますので、割愛します。お手元の環境に合わせて指定してください。

クラスターにサービスを作成しタスクを展開

ではいよいよサービスを作成します、起動したクラスターの画面に移動してください。

サービスの作成

サービスの作成画面ですが、ここに Lambda との決定的な違いがあります。

それは、「タスク数」という項目で常時起動可能なタスク数を指定可能ということです。例えば負荷の高い API サーバー、レスポンス速度を常に求められるタスクは、タスク数を調整することをお勧めします。

話を戻すと、Nuxt の SSR に関してはタスク数 1 で十分です。というのも、Cloud Front を手間に入れてキャッシュさせるケースが多いためです。( Cloud Front でリクエストが止まるので常時起動させる意味がない )

タスク数の考え方としては、私たちの経験上の下記のパターンがあります。

  • 小さいタスクを大量に捌く→タスク数多めに
  • リクエストは多くないが大きなタスクを捌く→タスク数少なめで CPU やメモリをハイスペックに
サービスの作成画面

以降は、VPC、ロードバランサーに接続するなどを行うので割愛します。

ポイントとしては、ECS は勝手にターゲットグループを作成し、指定した ALB に勝手にアタッチするということです。運用の際にピーク時などに勝手に台数を増やしてアタッチするなどを Fargate がよしなにやってくれます。( Auth Scaling )

ターゲットグループのインスタンスが healthy にならない

デプロイ後に動作確認をする際に、ターゲットグループのインスタンスが healthy にならない場合は、ネットワークの設定などを見直してみましょう。

例えば、 ALB が Private ネットワークにいるなどの場合にターゲットグループのインスタンス判定は un healthy となり、Fargate がインスタンスに問題が起こったと認識し無限にデタッチ→アタッチを続けます。

まとめ

弊社で採用事例の多い Fargate、ECS ですが、まだまだ奥深いです。特にタスクの定義周りはかなりチューニングを細かくできるので、ぜひ触ってみてください。

また、デプロイ自動化は Code Pipeline に任せると Github と連携してかなり楽できますよ、オススメです。

ECS, Fargate の構築・運用はお気軽にご相談ください。