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

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

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

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

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

なぜECS, Fargate?

LambdaでのSSR時の問題

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

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

例えば、わたしたちのコーポーレートサイトは、現在Vuejs/Nuxtで構築されていますが、以前までは手軽に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コマンドを実行するか、介さないかを選択する必要があります。

私の推奨は、NuxtやNextの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"]

https://hub.docker.com/_/node/

Docker hub node

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

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

Docker Desktop

$ 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の構築・運用はぜひRagateまでお問い合わせください!