サーバレスアーキテクチャにおける Web アプリケーションの実装事例(前編)

SCSKの畑です。

今更感はあるのですが、今回はこれまでの投稿で取り上げてきた Web アプリケーションのアーキテクチャについて、改めて概要や各サービスの役割などを記載していこうと思います。正直、最初の方の投稿で先に説明しておくべきだったかもしれません・・

アーキテクチャ図

いつものやつです。図内に注釈もあるので、各サービスの役割は何となくご理解いただけるものかと思います。

構築・開発中のアプリケーションアーキテクチャ概要図です。

 

アプリケーションの目的・用途について

初回のエントリ含め何回か記載していますが、改めて記載するとデータベース/ DWH 上のテーブルデータをメンテナンスすることが目的のアプリケーションとなります。本事例の場合のメンテナンス対象は Redshift となります。また、実はここで初めて言及するのですが、データメンテナンスの要件として、以下2点のような機能も実装しています。

  • アプリケーションのメンテナンス対象テーブルの(データ)バージョン管理機能
    • 過去バージョンデータの参照や、バージョン間のデータ比較が可能
  • アプリケーション経由でテーブルデータを更新する際の簡易ワークフロー機能
    • 更新を承認する権限を持った別ユーザによる承認を以って Redshift 側に更新が反映される

また、アーキテクチャ概要図には載せていませんが、本事例においては DWH (Redshift) 上にトランザクションデータを投入・更新するための ETL/ELT ジョブネットをアプリケーション経由で実行する機能も実装しました。同ジョブネットは StepFunctions 及び Lambda で実装しており、 StepFunctions のステートマシンをアプリケーションから実行するような形式となります。

他、アプリケーション自体の運用管理機能として、メンテナンス対象テーブルの情報や実行対象 ETL/ELT ジョブネットの情報、Cognito のユーザ/グループなどをメンテナンスできる機能も用意しました。このあたりは当初のスコープにはなかったのですが、お客さんの要望を聞きつつ必要になりそうな機能をピックアップした形です。

 

アプリケーションのビルド・デプロイについて

大した内容ではありませんが、先にこちらを簡単に説明しておきます。

ビルドについては、Nuxt3 の場合サーバエンジン (nitro) の設定に Lambda デプロイ用のプリセットがあるため、それを指定してビルドするだけで OK です。このお手軽さは非常に魅力的でした。

デプロイについては、お客さん環境の都合上手動で行う必要がある関係で、CloudFront 配下の Lambda と S3 にそれぞれ AWS マネジメントコンソールから粛々とデプロイしています。ただ、8回目のエントリで説明した AppSync の手動移植と比較すると大分ラクなのが幸いでした。ただ、Web上には Serverless Framework を使用した手順が多く、手動でのデプロイ方法を調べるのに少し手間取りました。具体的にはほぼ以下サイト様の通りですが、今回の事例において少し異なる点もありましたので以下に記載しています。

Serverless Frameworkを使わず、Nuxt3でSSR構成(CloudFront + Lambda + S3)をする方法 - Qiita
対象者自分と同じAWS初心者「俺たちは感覚でAWSを触っている」状態の人バックエンドなんもわからんけどAWSへのデプロイを控えている人Next全盛の今NuxtのSSRで頑張っている人なぜや…
  1. .output/public/ 配下のファイル一式を S3 にアップロード
    • ビルドの度に静的コンテンツのファイル名がハッシュ値に応じたものに変わるため、デプロイ前に S3 上のファイルを削除しておく方がベターです。ファイル名は固定するような設定に変更することもできるようですが。
  2. .output/server/ 配下のファイル一式を Lambda にアップロード
    • アップロード操作を踏まえると一式を zip ファイルにしてしまうのが良いと思います。
    • Lambda のハンドラ設定については、上記のようにアップロードする前提においては index.handler のままで良いようです。当初は .output/index.mjs を実行する index.js を別途作っていたのですが、不要でした。

 

フロントエンド側のサービス

フロントエンド側のサービスとしては、以下3つです。

  • Amazon CloudFront
  • Amazon S3
  • AWS Lambda

アーキテクチャ図に記載の通り、CloudFront 配下に S3 及び Lambda を配して、Nuxt.js (Nuxt3) による SSR (Server-Side Rendering) 構成で Web アプリケーションを動作させています。厳密には Nuxt3 における Universal Rendering を使用しているので CSR (Client-Side Rendering) との併用となっています。

ということで、各サービスの位置づけ・役割については概ね想像できるところだと思うのですが、少しだけ詳細な話も含めつつ以下に記載していきます。

 

Amazon CloudFront

アーキテクチャ概要図の通り、クライアント端末からのアクセスを受けてアプリケーションを配信しています。サーバサイド生成(動的)コンテンツは Lambda 経由、静的コンテンツは S3 経由です。サーバレスアーキテクチャとしては標準的な構成と思いますし、CloudFront 自体の設定も特にトリッキーなところはなかったため、今回は取り上げません。

ただ、お客さんの AWS 環境で構築するにあたりセキュリティ関連の設定について幾つか考慮する必要があったため、以下にまとめました。

なお、原理的には CloudFront ではなく API Gateway でも代替できるものと思われます。当初はアプリケーションの要件的に CloudFront を持ち出すのは少々大袈裟かなと感じたこともあって API Gateway を試していたのですが、URL に API Gateway の Stage が含まれてしまうせいか上手く動かず。Nuxt 側の設定を色々と変更してみたのですがダメでした。

また、S3 を 使用せず Lambda 単体で動作させるように Nuxt の設定を変更することは可能で、Web 上にその事例も多く出てきたのですが、将来的なことを考えると S3 を使用するべきに構成しておくべきと判断し、CloudFront を使用することにしました。

 

WAF 設定

お客さんの環境ポリシーにより Web WAF を設定しています。

設定自体はお客さんの所管であったため詳細は割愛しますが、本アプリケーションはインターネット上に公開せず、アクセスできるネットワーク/IP アドレスのレンジを限定する必要があったため、その点については考慮する必要がありました。最も、概要図の通り CloudFront にアクセスする対象はクライアント端末のみであり、それらが属しているネットワーク内からのみアクセス可能とすれば OK だったので特に難しい要件ではなく、お客さん側のネットワーク構成も相まって特に問題なく設定できました。

なお、後編で取り上げるバックエンド側のサービスについて、こちらと同じように考えれば良いやと安易に構えていたところ、痛い目に遭いかけました。詳細については別エントリで改めて説明しようと思います。

 

Origin access control 設定

CloudFront の配下に配する S3 及び Lambda について、CloudFront からのアクセスのみを許可するような設定が必要でした。幸い、こちらも Web 上の情報や知見は豊富で、上記のようにいずれも Origin access control 設定により実現できました。

特に、Lambda については構成上 CloudFront からのアクセスに関数 URL 機能を使用する必要がありますが、同機能には WAF などの外部アクセス制限機能を適用できないことから、当初はお客さんの環境ポリシーに準拠せず NG が出ていました。幸い、2024/4 に Lambda の関数 URL も Origin access control をサポートするようになっていたため、その旨をお客さんに伝えてクローズと相成りました。

Amazon CloudFront が Lambda 関数 URL オリジンのオリジンアクセスコントロール (OAC) を新たにサポート

 

Amazon S3

CloudFront の項目で説明した通り、アプリケーションから使用する静的コンテンツを配置しています。

構成上必要となる設定も前述の Origin access control くらいで、正直特に言及することはありません。せいぜい、デプロイ時に AWS マネジメントコンソール経由だとネットワークの具合次第でアップロードに失敗することがあり、その場合のオペレーションが面倒だったくらいですが、まあこれって環境依存の話なので正直関係ないですね・・。

 

AWS Lambda

CloudFront の項目で説明した通り、サーバサイドで動的なコンテンツを返すために使用しています。具体的には、ビルドしたアプリケーションのコード一式を Lambda にデプロイするような形となります。デプロイ手順としては先述した通り難しくはないのですが、S3 と比較すると幾つか躓いた点がありましたので備忘も含めて以下にまとめました。

  • 先述した内容の繰り返しとなりますが、ハンドラ設定は index.handler で OK でした。
  • Lambda 実行ロールについて、当初はサーバサイド側で実行され得る機能に対応した権限(Cognito なり AppSync 関連)が必要になると考えていたのですが、いずれも不要でした。
    • サーバサイドで認可が必要な処理が行われるとしても、それを実行するための権限は2回目のエントリで書いたように Cognito 経由で付与されるロール側で付与されるためと理解しています。
  • これは余談寄りですが、アプリケーションで使用している pdf ファイル表示用のライブラリで不具合があった関係で、Node.js を 22 系に上げたかったのですが、Lambda におけるリリースタイミングが 2024/11 と思ったより遅かったため、不具合の解消にやや時間を要しました。(Node.js の 22 系リリースは 2024/4)
    • https://aws.amazon.com/jp/about-aws/whats-new/2024/11/aws-lambda-support-nodejs-22/
    • このへんのスピード感(早い or 遅い)が肌感覚として良くわからないですね。リリースから半年と考えると標準的な気もしますし、Node.js の 22 系が安定版と考えるとちょっと遅いような気もしますし・・

 

余談など

Next.js(React) ではなく Nuxt.js(Vue) を使用した理由をたまに問われるのですが、最終的には自分の好みです。特に、Vue ファイル内で HTML テンプレートライクに書けるのが、昔から HTML タグ打ちしていたような人間にとっては馴染みやすかったというのが大きかったです。それから調べた限りだと、今回のような少人数開発のアプリケーションにおいてはある意味どちらを選んでも大差ないと感じたのも理由の一つです。なので最終的に自分の好みを優先させてしまっても良さそうだな、と・・

また、今振り返ると、SSR 構成にする必要性は機能要件上はなかったかもしれません。当初はサーバサイドで重い処理をさせてクライアント側に返すようなことがアーキテクチャ上できるようにしておいた方が良いと考えていましたが、アプリケーションの特性上クライアント側で処理する内容が多い上、重い処理も結局のところは AppSync 経由で実行されるバックエンド処理用の Lambda が担うことがほとんどであったためです。とは言え、アクセス時のレスポンス向上などの効果はあったと思いますし、この構成にしたことによるアプリケーション実装上のデメリットが本アプリケーションについては正直思っていたほど出なかったこともあり、悪い選択でもなかったとは思っていますが。

ただ、CloudFront など一部サービスの構築は所管上お客さんにお願いすることになったこともあり、事前準備やリードタイムに一定の工数を要したのも事実ではありました・・
 

まとめ

今振り返ると、当時は自分なりに色々調べた上での判断とはいえ、ちょっと考慮が足りていなかったかもという点があるというのが正直なところです。特に SSR 構成を前提としてアーキテクチャを検討していた点については、もう少し深堀りして調査できていればなお良かったかもしれません。このあたりは次回以降の案件などで活かしていければと思っています。

そして、、思ったより長くなってしまったので、バックエンド側の話は後編に回したいと思います。

本記事がどなたかの役に立てば幸いです。

タイトルとURLをコピーしました