本文へ移動

こんにちは!開発3部のHIROYAです。

先日、AWSのSAA(Solutions Architect – Associate)資格を取得しました。
ただ、資格を取ったからといってすぐに実務で使いこなせるかというと、そう簡単ではありません。

そこで今回は、SPAアプリをAWS上で動かすところまでを実践してみました。

本記事では、実際に利用したAWSサービスの構成と、環境構築時に押さえておきたい設定ポイントに絞って紹介します。
※AWSサービスの利用には料金が発生するため、実際に試される際はご注意ください。

利用したAWSサービス一覧

サービス役割
Amazon S3ReactでビルドしたSPAの静的ファイルを配置
Amazon CloudFrontS3上の静的サイトをCDN経由で配信
Amazon API GatewayブラウザからのAPIリクエスト受付
AWS LambdaJava(Spring Boot)で実装したバックエンド処理
Amazon RDS(MySQL)バックエンドで利用するデータベース
AWS Secrets ManagerDB接続情報を安全に管理

構築の概要

全体の構成は以下の通りです。
静的コンテンツはCloudFront経由で配信し、API通信はAPI Gateway → Lambda → RDS へ流れる構成としています。

フロントエンド(静的サイト配信)

  • ReactでビルドしたSPAをS3に配置
  • CloudFront経由で静的コンテンツを配信
  • ブラウザはHTML / JS / CSS などの静的資材をCloudFront経由で取得

バックエンド(API 通信)

  • ブラウザからのAPIリクエストはAPI Gatewayのエンドポイントを直接呼び出す
  • API Gatewayの統合先としてLambdaを設定

Lambda と RDS の関係

  • LambdaはVPC内のPrivate Subnet(2AZ)に関連付ける
  • 実行時に、Private Subnet上にENI(Elastic Network Interface)が作成される
  • RDS(MySQL)はSingle-AZ構成(サービス利用料を抑えるため)でPrivate Subnetに配置
  • LambdaからVPCエンドポイントを介して、Secrets Managerからデータベース接続に必要な情報を取得
  • LambdaのENIからRDSに接続しデータを取得

アベイラビリティゾーンとサブネット構成

  • アベイラビリティゾーン:2つ
  • 各AZに以下を配置
    Public Subnet:1つ ※現状未使用(将来の拡張用に配置)
    Private Subnet:2つ

フロントエンド構築

フロントエンド資材を準備(ローカルでSPAの開発が完了しているものとします)

  • 「npm run build」を実行し、distフォルダを生成

S3バケット

  • パブリックアクセスをブロックすることで、S3への直接アクセスを遮断
  • バケットポリシーには、後ほど作成するCloudFrontからのみのアクセスを許可するポリシーを設定(直接公開せずCloudFrontを経由させることでセキュリティと配信パフォーマンスの両方を確保する構成としています)
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{作成したバケット名}/*",
            "Condition": {
                "ArnLike": {
                    "AWS:SourceArn": "arn:aws:cloudfront::{AWSアカウントID}:distribution/{ディストリビューションID}"
                }
            }
        }
    ]
}
  • 静的ウェブサイトホスティング無効(デフォルト)
  • 事前準備で作成したdistフォルダの中身をS3にアップロード

CloudFront設定

  • 独自ドメインは使用せずCloudFrontのドメインを利用
  • オリジンタイプをAmazon S3に設定
  • 事前に作成したS3のオリジンを設定
  • Origin Access Control(OAC)を作成し、オリジンに関連付ける
  • デフォルトルートオブジェクトをindex.htmlに指定
  • カスタムエラーレスポンスを設定することでリダイレクト時のエラー回避
    SPAはルーティングをフロント側で行うため、index.htmlを返すことで、フロントエンドのルーティングに処理を委ねる

上記設定が完了したら、ブラウザからCloudFrontのURLにアクセスします。
アップしたフロントエンドのルートページが表示されたら、フロントエンドの接続は成功です。

ただ、現状のままではバックエンド側で整形されるデータが取得できませんので、続いてバックエンド側のデプロイを行います。

バックエンド構築

バックエンド資材の作成(Java / Lambda 対応)

今回はLambdaの設定に合わせてJava17で開発しました。

バックエンドに必要な依存ライブラリ定義

ライブラリ用途
aws-serverless-java-container-springboot3APIAPI GatewayのリクエストをSpring MVCに変換
aws-lambda-java-coreLambdaをJavaで書くための基本API
aws-lambda-java-eventsAPI Gatewayのイベント形式をJavaで扱うための定義
maven-shade-plugin依存関係をすべて含んだfat jarを生成
spring-cloud-aws-starter-secrets-managerSecrets Manager連携

VPC

  • NATゲートウェイ:なし
    外部APIへの通信を行わない構成としたため、NATゲートウェイは作成せず、コスト削減を図る

RDS

  • 事前準備
    Lambda用セキュリティグループ(SG)をデフォルトで作成
    RDS用SGを作成(インバウンドルールにLambda用SGを指定し、Lambdaからの通信のみRDSへ許可する)

外部に非公開かつコストを抑える方針で作成する。

  • RDSの設定
    ⚪︎利用料を抑えるためシングルAZにDBインスタンスを作成
    ⚪︎外部からのアクセスを遮断し安全な構成とするためパブリックアクセスなし
    ⚪︎RDS用SGを付与

作成前に概算月間コストを確認しておくこと(インスタンスの設定により金額が大きく変わります)

Lambda

  • 関数作成設定
    ⚪︎ランタイム:実行環境をJava17に設定(バックエンドの環境と合わせ互換性エラーを回避する)
    ⚪︎AWSLambdaVPCAccessExecutionRoleのポリシーをロールに付与しENI作成を許可する。この設定がないと、 VPC内へのLambda設定ができない。
  • VPC設定
    ⚪︎AZ障害耐性のため冗長化として、各AZのプライベートサブネットを一つずつ選択
    ⚪︎Lambda用SGを設定

Secrets Manager接続

LambdaからDBへアクセスする接続情報を管理するために利用します。

  • VPCのDNS解決・DNSホスト名が有効であることを確認
  • Lambda SGからのTCP 443通信を許可した、Secrets Manager用VPCエンドポイントのSGを作成
  • VPC Endpoint作成
    ⚪︎エンドポイントのタイプはAWSサービスとして利用
    ⚪︎Lambdaを配置したVPCにエンドポイントを作成
    ⚪︎プライベートDNS名を有効化
    ⚪︎Lambdaを配置したそれぞれのサブネットを選択
    ⚪︎作成したSecrets Managerアクセス用のSGを付与
  • シークレットタイプ「Amazon RDSデータベースの認証情報」を選択し作成したデータベースに紐づけたシークレットを作成
  • Lambdaに付与したロールにSecrets Managerへのアクセス許可設定を付与
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
			],
			"Resource": "arn:aws:secretsmanager:<リージョンコード>:<AWSアカウントID>:secret:<シークレット名>*"
		}
	]
}

バックエンド資材をアップ

  • バックエンド対応
    CORS設定にCloudFrontのオリジンを設定
    Secrets Managerから取得したデータでDBにアクセスできるよう設定
    ./mvnw clean packageでjarファイルを作成
  • S3作成(バックエンド用の資材を一時保管)
    jarファイルのサイズが50MBより大きい場合、Lambdaに直接アップロードすることができないため、一度S3に置いた後に、コピーをLambdaに配置
  • バックエンド用に作成したS3のバケットにjarファイルをアップロード

Lambda設定

  • LambdaにS3のjarをコピー(アップロード元をAmazon S3に設定しS3にアップしたjarのURLを指定し保存)
  • ハンドラ設定をバックエンドの実装に合わせる

DB初期データ登録

  • 初期データ登録用のJava実装を作成
  • バックエンドのLambdaを参考にLambda設定を行う
    ⚪︎Lambdaにjarファイルをアップロード
    ⚪︎RDSへ接続するSG設定
    ⚪︎Secrets ManagerからのDBデータ接続設定
    ⚪︎Javaの処理に合わせたハンドラ設定
    ⚪︎Lambdaテスト画面でテスト実行

最後にAPI Gatewayを介してフロントエンドとバックエンドの接続を行います。

API Gateway

  • Gateway設定
    ⚪︎REST API + {proxy+} + ANY + Lambdaプロキシ統合。ルーティングをすべてSpring側に任せることで、API Gatewayでの設定を簡素化

API Gatewayのレスポンスヘッダ

  • /{proxy+}にレスポンスヘッダを追加
    Access-Control-Allow-Headers:Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
  • 設定が完了したらAPIをデプロイする

資材の再アップ

API Gatewayは、フロントエンドとバックエンドの間に立つ「仲介役」として存在します。
そのため、フロントエンドからAPIを呼び出す際は、必ずAPI Gatewayが発行したエンドポイントを接続先として指定する必要があります。
フロントエンド側でAPIの接続先URLを修正後、再度ビルドしてS3へアップロードします。

以上ですべての設定が完了しました。
最後に、ブラウザから CloudFrontのURLにアクセスし、データベースの情報が取得できていればデプロイ成功です。

構築中はエラーが多く、試行錯誤の連続でしたが、実際に手を動かすことで、資格学習で得た知識をより自身に落とし込むことができました。

現場では、すでに用意された環境に対して機能を追加していくことが主なタスクになることが多いため、
インフラ構成を一から設計・構築する経験はとても新鮮で楽しかったです。

一方で、ベストプラクティスや実運用を意識した設計という点では、まだ改善の余地も多く残っています。
今後も開発を継続しながら、構成や運用面のブラッシュアップを進めていきたいと思います。

クロノスではクラウドネイティブ開発に取り組みたい仲間を大募集しております!
少しでも興味を持っていただけた方はぜひカジュアル面談でお話しましょう!

この記事を書いた人

開発3部 HIROYA

2023年に未経験中途としてクロノスに入社。
「自分にできないことはない」と信じる負けず嫌い精神で、日々精進しています。
とにかく楽しく仕事をする。その思いを伝染させていきたい。

おすすめ