Serverless+SPA構成
最近低費用でサービス運用できないかと、色々試してたのをまとめてみます。
Serverless構成だとEC2と比べて費用が安くなり、SPAであればルーティングがサーバ側制御ではないためS3のバケットに静的html、css、js、画像の配置にてサービス構築が可能となります。
SPAにはReactJSを採用してますが、Vue.jsやUnityとかのサービスでもいけると思います。
良い点ばかりではなく、デメリットも結構あるのでそれを考慮した上で導入しましょう。
個人でお金をかけたくなくてサービス運用したい場合などには良いかもしれません。
(Lambdaの無料枠+アクセス量のみで計算される。ドメイン費込みでも月約$3程度)
メリット
・ とにかく費用が安い
・ AWSの設定周りの変更だけでスケールする
デメリット
・ 利用しているサービスが多いため、デプロイ時などの障害発生ポイントが多い、各サービスに関する知識もある程度必要
・ Lambdaに関するAPI処理実行時の起動速度制限やメモリ上限がある
・ DBがDynamoDBでないとスケールしずらい
・ バックエンド側は完全AWS依存のため、別の環境に簡単には移行できない
Serverlessについてや各種AWSサービス料金に関しては下記参考にしてください
AWS料金早見表
ServerlessアーキテクチャとSPA(ReactJS)でサービス構築するには
以下のような構成になります。
利用用途:
- CloudFormation: Serverlessフレームワーク経由でデプロイジョブを実行
- IAM: Lambdaの実行権限やDynamoDBアクセスをLambdaに付与
- CloudWatch: ログ収集
- Route53: ドメイン割当
- ACM: SSL証明書作成、SSL認証
- CloudFront: CDN、ドメインでの公開配信
- S3: SPAファイルを配置(ReactJSリリースビルド済みのファイル群)
- API Gateway: APIのエンドポイント作成
- Lambda: APIの処理を実行
- DynamoDB: データ保存
Serverlessフレームワーク+ReactJSによる実装
サンプルのソースコードは次gitにあります。
serverless-react
API側の実装とデプロイはserverlessフレームワークにて行います。
serverlessフレームワークはバージョン別で破壊的変更が入り、互換性がないことが多いので注意です。
今回はv1.24.1にて実装を行っています。
下記コマンドでServerlessフレームワークをインストールします。
$ npm install -g serverless
サンプルの構成です。
clientフォルダにはReactJSでのフロントエンドの実装、
serverless-apiフォルダにはServerlessフレームワークでのバックエンドの実装がそれぞれ格納してあります。
configフォルダにはnode-configの設定ファイルがあります。今回はAPIサーバの起動ドメインの設定を記述してあります。
今回はTODOサービスのサンプルを作成しました。
ReactJSからAPI経由でのDynamoDBのCRUD操作を行います。(create.js、list.js、update.js、delete.js)
├── client
│ ├── package.json
│ ├── src
│ │ ├── App.js
│ │ ├── components
│ │ │ ├── NotFound.js
│ │ │ ├── TodoPage.js
│ │ │ └── TopPage.js
│ │ ├── index.js
│ │ ├── reducer
│ │ │ ├── reducer.js
│ │ │ └── todo.js
│ │ └── static
│ │ └── index.html
│ ├── webpack.build.js
│ ├── webpack.config.js
│ └── yarn.lock
├── config
│ ├── default.js
│ └── production.js
└── serverless-api
├── lib
│ └── dynamodb.js
├── offline
│ └── migrations
│ └── todo.json
├── package.json
├── serverless.yml
├── src
│ ├── create.js
│ ├── delete.js
│ ├── list.js
│ └── update.js
└── yarn.lock
今回のサンプルをデプロイする上でドメインが必要です。(ローカルでの起動は問題はない)
ドメインを未購入の人は下記記事を参考にドメインの購入をしてください。(ドメインがないとデプロイ時にAWS側で自動割当されたものとなり、色々不便なので)
Amazon Route 53でドメインを購入する
ローカルでの起動
次のコマンドでローカルでのサーバのシミュレーション起動ができます。
$ cd serverless-api
$ yarn
$ sls dynamodb install
$ SLS_DEBUG=* sls offline start
デバッグ方法は下記記事を参考にしてください
Serverlessフレームワークでデバッグする(ブレークポイントかける)方法
上記サーバを立ち上げた状態で
クライアント側は下記コマンドでwebpack-dev-serverにて起動します。
$ cd client
$ yarn
$ NODE_CONFIG_DIR=../config webpack-dev-server
# ブラウザを開く
$ open http://localhost:3001
デプロイ(初回)
クライアント(ReactJS)のリリースビルドとserverlessフレームワークでのデプロイを行います。
ローカル時は下記node-configの設定ファイルにてクライアント側から指定のAPIサーバに対してリクエストを投げます。
module.exports = {
API_SERVER: 'http://localhost:3000',
PORT: 3000,
}
リリースビルド時は下記production.jsの方が参照されます。
teradonburi.info
の部分は各自取得したドメインで書き換えてください
私はapi.(ドメイン名)
にしました。
module.exports = {
API_SERVER: 'https://api.teradonburi.info',
}
クライアント側は下記コマンドでwebpackにてリリースビルドを行えるようにしました。
distフォルダにビルドされたリリース用のSPAのファイル群が作成されます。
$ cd client
$ yarn
$ rm -rf dist;NODE_ENV=production NODE_CONFIG_DIR=../config parallel-webpack -p --config webpack.build.js
ReactJSでのSPA作成方法やリリースビルドの詳細に関しては下記を参考にしてください。
ReactJSで作る今時のSPA入門(基本編)
ReactJSで作る今時のSPA入門(リリース編)
serverlessの設定ファイルのclientのbucketNameのteradonburi.info
の部分は各自取得したドメインで書き換えてください
私はstatic.(ドメイン名)
にしました。
serverless.yml
custom:
client:
bucketName: static.teradonburi.info
下記コマンドでCloudFormation経由で各サービスにデプロイします。
(CloudFormation、API Gateway、Lambda、S3、DynamoDB、CloudWatch)
# デプロイ先のAWSアカウントを明示的に指定(~/.aws/credentialsのアカウントが複数ある場合)
$ export AWS_ACCESS_KEY_ID=(アクセスキー)
$ export AWS_SECRET_ACCESS_KEY=(プライベートキー)
$ export AWS_REGION=(リージョン)
$ SLS_DEBUG=* sls deploy
# distフォルダを新規作成S3バケットにアップロード
$ SLS_DEBUG=* serverless client deploy
デプロイが完了するとCloudFormationのスタックが作成されます。
API Gatewayのエンドポイントが作成されます。
Lambdaの関数が作成されます。
DynamoDBのテーブルが作成されます。
S3のバケットが作成されます。
serverless.ymlについて
serverless.ymlにはデプロイの設定情報を記述します。
pluginsに追加のプラグインを記述します。
ローカル実行用にserverless-offline、serverless-dynamodb-localを導入しました。
S3へdistフォルダアップロード用にserverless-finchを導入しています。
DynamoDBのauto scalingにserverless-dynamodb-autoscalingを使用しています。
plugins:
- serverless-offline
- serverless-dynamodb-local
- serverless-finch
- serverless-dynamodb-autoscaling
providerにはlamdaから実行できるIAMロールの指定を記述します。
今回はLambdaからDynamoDBアクセスするため、DynamoDBのIAMロールを追加します。
provider:
name: aws
runtime: nodejs8.10
region: ap-northeast-1
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
customにはその他の設定を記述します。
今回はやっていませんが、開発環境stage、商用環境stage別の設定なども可能です。
dynamodbにはローカルでdynamodbをシミュレーション起動する設定を記述しています。
clientにはserverless-finchでのdistフォルダのデプロイ先S3バケットを指定しています。
custom:
# dynamodb local
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
migration:
dir: offline/migrations # localテーブル定義ファイル
# dynamodb auto scaling
capacities:
- table: TodosDynamoDbTable # DynamoDB Resource name
read:
minimum: 1 # Minimum read capacity
maximum: 10 # Maximum read capacity
usage: 0.75 # Targeted usage percentage
write:
minimum: 1 # Minimum write capacity
maximum: 10 # Maximum write capacity
usage: 0.75 # Targeted usage percentage
# s3
client:
bucketName: static.teradonburi.info
distributionFolder: ../client/dist
API Gatewayレスポンスの注意点
statusCode、headers、bodyには下記のようなレスポンスが必要です。
Access-Control-Allow-Credentials、Access-Control-Allow-Origin、Content-Typeに関してheaderの指定が必要です。
bodyはJSON文字列である必要があります。(今回はapplication/json想定、binaryの場合などはまた別の設定が必要)
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Credentials": true,
"Content-Type": "application/json",
"Access-Control-Allow-Origin" : "*" // Required for CORS support to work
},
body: JSON.stringify(result.Attributes),
}
callback(null, response)
Serverlessフレームワークでの設定は未検証ですがAPI Gateway+Lambda経由の画像のアップロードも今では可能です。
参考:API Gatewayがバイナリデータをサポートしたので試してみました
CloudFrontの設定
参考:S3 CloudFront Route 53 でReactで作ったSPAを配信する
参考:CloudFrontでS3のウェブサイトをSSL化する
CloudFrontを作成します。
Origin Domain NameにはS3のバケット名を指定します。
Origin IDは自動で入ります。
Object CachingをCustomizeにします。
TTLを全て0にしてます。(キャッシュを有効にする場合はS3デプロイ時に手動でInvalidationsにてキャッシュクリアする必要が出てきます。)
Forward CookiesをAllにします。
Compress Objects Automaticallyを有効にします。(gzip圧縮されてレスポンスが早くなるので)
Price ClassesはAll Edgeにすると日本からのアクセスは早くなりますが値段は上がります。(お財布と相談で)
Altanative Domain Names(CNAME)にはstatic.(ドメイン名)
を指定します。
SSL CertificateをCustom SSL Certificateにします。
→Request or Import a Certificate with ACMにてSSL証明書を作成します(後述)
Default Root Objectをindex.htmlにします。
CloudFront作成後に払い出されたDomain Nameをコピーします。
Route53にてstatic.(ドメイン名)
にCloudFrontのDomain NameをAレコードのaliasとして指定します。
ACMで独自ドメインのSSL証明書作成
ACMにてSSL証明書を作成します。
CloudFrontで利用するため、北部バージニア(us-east-1)リージョンで作成する必要があります。
*.(ドメイン名)
でサブドメインを作成します。
Route53にて払い出されたCNAMEを登録します。(Route53でのレコードの作成)
SPAのルーティングを有効にする
デフォルトのままだと
S3のエラーページが表示されてしまい、SPAのルーティングが有効になりません(Reactの場合、React-Routerが効かない)
そこでCloudFront側でエラーページの遷移を修正します。
Create Custom Error Responseを作成します。
403エラーコードの場合にindex.htmlを指定するように修正します。
API Gatewayの設定
参考:Amazon API Gateway にカスタムドメインを設定する
参考:【新機能】Amazon API GatewayがACM (AWS Certificate Manager)に対応。簡単に独自ドメインAPIがSSL化。
API Gatewayにカスタムドメインを割り振ります。
API Gatewayのカスタムドメイン名から作成します。
私はapi.(ドメイン名)
にしました。(config/production.jsと合わせてください)
作成にはかなり時間がかかります。(30分以上?)
保存するとターゲットドメイン名が出力されますのでコピーします。
Route53にてCreate Record SetsでAレコードを作成します。
aliasをyesにして先程のターゲットドメインを指定します。
本番環境の確認
上記すべての設定が完了すれば
https://static.(ドメイン名)
にアクセスするとReactJSのSPAページが表示され、
https://api.(ドメイン名)
でAPI Gateway→Lambda→DynamoDBでのRest APIが動作するようになります。
APIの再デプロイ
API部分は次コマンドで再デプロイします。
# デプロイ先のAWSアカウントを明示的に指定(~/.aws/credentialsのアカウントが複数ある場合)
$ export AWS_ACCESS_KEY_ID=(アクセスキー)
$ export AWS_SECRET_ACCESS_KEY=(プライベートキー)
$ export AWS_REGION=(リージョン)
$ SLS_DEBUG=* sls deploy
S3の再デプロイ(アップロード)
serverless-finchの最新版では次のコマンドで再デプロイ可能
$ SLS_DEBUG=* sls client deploy