140
161

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Serverless+SPAで超低費用な個人サービス構築のススメ

Last updated at Posted at 2017-12-28

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)でサービス構築するには
以下のような構成になります。

スクリーンショット 2017-12-29 5.37.03.png

利用用途:

  • 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でドメインを購入する

ローカルでの起動

次のコマンドでローカルでのサーバのシミュレーション起動ができます。

server.sh
$ cd serverless-api
$ yarn
$ sls dynamodb install
$ SLS_DEBUG=* sls offline start

デバッグ方法は下記記事を参考にしてください
Serverlessフレームワークでデバッグする(ブレークポイントかける)方法

上記サーバを立ち上げた状態で
クライアント側は下記コマンドでwebpack-dev-serverにて起動します。

client.sh
$ cd client
$ yarn 
$ NODE_CONFIG_DIR=../config webpack-dev-server
# ブラウザを開く
$ open http://localhost:3001
スクリーンショット 2017-12-29 5.26.35.png スクリーンショット 2017-12-29 5.26.55.png

デプロイ(初回)

クライアント(ReactJS)のリリースビルドとserverlessフレームワークでのデプロイを行います。
ローカル時は下記node-configの設定ファイルにてクライアント側から指定のAPIサーバに対してリクエストを投げます。

config/default.js
module.exports = {
  API_SERVER: 'http://localhost:3000',
  PORT: 3000,
}

リリースビルド時は下記production.jsの方が参照されます。
teradonburi.infoの部分は各自取得したドメインで書き換えてください
私はapi.(ドメイン名)にしました。

config/producation.js
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)

deploy.sh
# デプロイ先の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のスタックが作成されます。

スクリーンショット 2017-12-29 3.41.14.png

API Gatewayのエンドポイントが作成されます。

スクリーンショット 2017-12-29 3.44.03.png

Lambdaの関数が作成されます。

スクリーンショット 2017-12-29 3.40.58.png

DynamoDBのテーブルが作成されます。

スクリーンショット 2017-12-29 3.41.37.png

S3のバケットが作成されます。

スクリーンショット 2017-12-29 3.39.37.png

serverless.ymlについて

serverless.ymlにはデプロイの設定情報を記述します。
pluginsに追加のプラグインを記述します。
ローカル実行用にserverless-offlineserverless-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の場合などはまた別の設定が必要)

create.js
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にします。

スクリーンショット 2017-12-29 5.28.23.png スクリーンショット 2017-12-29 5.30.43.png スクリーンショット 2017-12-29 5.30.57.png スクリーンショット 2017-12-29 5.31.10.png

CloudFront作成後に払い出されたDomain Nameをコピーします。
スクリーンショット 2017-12-29 5.22.44.png

Route53にてstatic.(ドメイン名)にCloudFrontのDomain NameをAレコードのaliasとして指定します。
スクリーンショット 2017-12-29 5.21.14.png

ACMで独自ドメインのSSL証明書作成

ACMにてSSL証明書を作成します。
CloudFrontで利用するため、北部バージニア(us-east-1)リージョンで作成する必要があります。
*.(ドメイン名)でサブドメインを作成します。
スクリーンショット 2017-12-29 5.04.44.png

DNSの検証を選択し、確定とリクエストします。
スクリーンショット 2017-12-29 5.09.58.png

Route53にて払い出されたCNAMEを登録します。(Route53でのレコードの作成)
スクリーンショット 2017-12-29 5.10.44.png

スクリーンショット 2017-12-29 5.12.51.png

SPAのルーティングを有効にする

デフォルトのままだと
S3のエラーページが表示されてしまい、SPAのルーティングが有効になりません(Reactの場合、React-Routerが効かない)
そこでCloudFront側でエラーページの遷移を修正します。

Create Custom Error Responseを作成します。
スクリーンショット 2017-12-29 5.14.00.png

403エラーコードの場合にindex.htmlを指定するように修正します。
スクリーンショット 2017-12-29 5.16.41.png

API Gatewayの設定

参考:Amazon API Gateway にカスタムドメインを設定する
参考:【新機能】Amazon API GatewayがACM (AWS Certificate Manager)に対応。簡単に独自ドメインAPIがSSL化。

API Gatewayにカスタムドメインを割り振ります。
API Gatewayのカスタムドメイン名から作成します。
私はapi.(ドメイン名)にしました。(config/production.jsと合わせてください)
作成にはかなり時間がかかります。(30分以上?)

スクリーンショット 2017-12-29 4.23.28.png

保存するとターゲットドメイン名が出力されますのでコピーします。

スクリーンショット 2017-12-29 4.26.23.png

Route53にてCreate Record SetsでAレコードを作成します。
aliasをyesにして先程のターゲットドメインを指定します。

スクリーンショット 2017-12-29 4.29.23.png

本番環境の確認

上記すべての設定が完了すれば
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
140
161
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
140
161

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?