# サーバーレス環境構築手順書
# 前提
* node: 8.16
* serverlessインストール済み
* AWSのアクセスキー取得済み
* デプロイを行うIAMユーザーの AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY
デプロイを行うIAMユーザーは下記の権限が必要となります。
- AWSLambdaFullAccess
- IAMFullAccess
- AmazonAPIGatewayAdministrator
- AWSLambdaRole
- AWSDeepRacerCloudFormationAccessPolicy
- AmazonS3FullAccess
# 構成
全体構成図
Lambda に4つ関数を追加し、それを API Gateway、CloudFront と接続します。
デプロイ操作用に Cloud9 を利用します。

# S3 バケットを作成
アクセスログを保存するバケットと、コンテンツを置くバケットを追加します。
## S3 アクセスログ用ケットを作成 (構成図のNo1)
S3アクセルログを保存するバケットを作成します。任意のバケット名(この場では prod-j2-serverless-access-log )を指定してください。

オプションの設定はデフォルト値でかまいません。次へをクリック。

システムのアクセス許可の管理にて「Amazon S3 ログ配信グループにこのバケットへの書き込みアクセス権限をする」を指定し、次へをクリック。

次へをクリック。

バケットを作成をクリック。

## CloudFrontアクセスログ用バケット(構成図のNo2)
CloudFrontアクセスログ用バケットを作成します。
任意のバケット名(この場では prod-j2-serverless-cf-access-log )を指定、次へをクリック。

デフォルト設定のまま、次へをクリック。

デフォルト設定のまま、次へクリック。

## コンテンツ用バケット(構成図のNo3)
コンテンツ用のバケットを作成します。
任意のバケット名(この場では prod-j2-serverless-contents )を指定し、次へをクリック。

サーバーアクセルログを記録にて、「バケットへのアクセスリクエストを記録します。」のチェックボックスをチェック。ターゲットバケットを指定する。ここでは prod-j2-serverless-contents を指定。次へをクリック。

デフォルト設定で次へをクリック。

確認し、バケット作成をクリック。

# Cloud9 を作成(構成図のNo4)

Name 及び Description を入力し、Next Step をクリック。

Environment settings にてデフォルト設定で Next Step をクリック。

Review(確認画面)で Create environment をクリックし、Cloud9を追加する。

## Cloud9 上にソースを展開
任意の方法で Cloud9 の環境にソースをコピーする。
一例で、外部サーバから scp でコピーする。
```
$ scp xxx@xxxxxx.xxxx:realplan_serverless.zip .
$ unzip realplan_serverless.zipo
```
ディレクトリ realpla_serverless が出来上がる想定です。

## AWS のアクセスキー・シークレットキーを反映
```
$ export AWS_ACCESS_KEY_ID=XXX
$ export AWS_SECRET_ACCESS_KEY=XXX
```
## serverlessインストール
```
$ cd realpla_serverless/backend
$ npm install serverless serverless-offline -g
$ npm install axios
```
### インストール確認
```
$ sls -v
1.46.1
```

## serverless.yml の修正
- lambda が node.js 6.10 未対応となったため、設定を node.js 8.10 に変更
- デプロイ対象を dev から prod へ変更
- 「plugin: -serverless」 の設定は不要のため削除(あるとエラーが発生する)
```
< runtime: nodejs6.10
---
> runtime: nodejs8.10
31c31
< stage: ${opt:stage, 'dev'}
---
> stage: ${opt:stage, 'prod'}
163,164c163,164
< plugins:
< - serverless-offline
---
> #plugins:
> # - serverless-offline
```
## serverlessからlambdaへデプロイ
## 全体デプロイ(No5)
コードのデプロイを行う。
```
$ sls deploy
```
コンソール上のメッセージ
```
(中略)
endpoints:
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/rentToshinBukkenSearch
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/rentToshinTargetManshonSearch
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/rentToshinTenpoSearch
GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/api/tyotyomokuSearch
```
APIの endpoint のURLを確認する。(CloudFrontにて設定するため)
# CloudFront 設定(構成図のNo6)
CloudFrontを設定し、S3 及び API Gateway と接続します。
CloudFrount コンソールの Create Distribution をクリック。

Web項目の Get Started をクリック。

CloudFront を経由して S3 内のコンテンツへアクセスできるように設定する。
- Origin Domain Name ・・・ 先ほど作成したコンテンツ用のバケットを選択(ここでは prod-j2-serverless-contents)
- Restrict Bucket Access ・・・ Yes を選択
- Origin Access Identity ・・・ Create a new identity を選択
- Grant Read Permissions on Bucket ・・・ Yes, Update Bucket Policy を選択
- Viewer Protocol Policy ・・・ Redirect HTTP to HTTPS
- Query String Forwarding and Caching ・・・ Forward all, cache based on all を選択
- Default Root Object ・・・ デフォルトで表示されるファイル名を指定。ここでは index.html
- Logging ・・・ On を選択
- Bucket for Logs ・・・ 先ほど作成したCloudFrontアクセスログ用のバケットを選択(ここでは prod-j2-serverless-cf-access-log)
- Log Prefix ・・・ 任意のプレフィックを入力

## API Gateway を接続する設定を行う。
コンソールより該当の CloudFront をクリック。

Create Origin をクリック。

Origin Domain Name に、API Gateway のドメイン(ここでは xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com )を設定。
Origin Protocol Policy を HTTPS only に設定。
Create をクリック。

Behaviors タブをクリック。Create Behavior をクリック。

Path Pattern に /prod/** を指定する。(API のパス)
Origin or Origin Group に Origin で指定した API の Domein 名を指定する。
Create をクリック。

# API Gateway の設定(構成図のNo7)
API Gatewayの設定は serverless.yml での自動設定ではカバーできない箇所があるため、コンソール上から設定を行う。
API Gateway のコンソールを開く。
prod-backend をクリック。

prod-backend をクリック。
次の4つのAPIについて設定をそれぞれ行う
- /api/rentToshinBukkenSearch
- /api/rentToshinTargetManshonSearch
- /api/rentToshinTenpoSearch
- /api/tyotyomokuSearch
### /api/rentToshinBukkenSearch の設定
/api/rentToshinBukkenSearch の GET をクリック。


### メソッドリクエストをクリック
URL クエリ文字列パラメータを開く
クエリ文字列の追加をクリック
areaCd と bukkenId を追加

### 統合リクエストをクリック

Lambda プロキシ統合の使用 のチェックを外す
URL クエリ文字列パラメータを開く
bukkenId と areaCd を追加
名前:bukkenId
マッピング元:'method.request.querystring.bukkenId'
名前:areaCd
マッピング元:'method.request.querystring.areaCd'
マッピングテンプレートに application/json を追加。テンプレートに以下を設定。
※マッピングテンプレート
```
## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
#set($allParams = $input.params())
{
"body-json" : $input.json('$'),
"params" : {
#foreach($type in $allParams.keySet())
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
},
"stage-variables" : {
#foreach($key in $stageVariables.keySet())
"$key" : "$util.escapeJavaScript($stageVariables.get($key))"
#if($foreach.hasNext),#end
#end
},
"context" : {
"account-id" : "$context.identity.accountId",
"api-id" : "$context.apiId",
"api-key" : "$context.identity.apiKey",
"authorizer-principal-id" : "$context.authorizer.principalId",
"caller" : "$context.identity.caller",
"cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider",
"cognito-authentication-type" : "$context.identity.cognitoAuthenticationType",
"cognito-identity-id" : "$context.identity.cognitoIdentityId",
"cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId",
"http-method" : "$context.httpMethod",
"stage" : "$context.stage",
"source-ip" : "$context.identity.sourceIp",
"user" : "$context.identity.user",
"user-agent" : "$context.identity.userAgent",
"user-arn" : "$context.identity.userArn",
"request-id" : "$context.requestId",
"resource-id" : "$context.resourceId",
"resource-path" : "$context.resourcePath"
}
}
```
### メソッドレスポンス
HTTPのステータス、200、400、500を追加する。

HTTP のステータス 200 を開く。
下記のレスポンスヘッダーを追加する。
- Cache-Control
- Access-Control-Allow-Headers
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Content-Type

### 統合レスポンス
200 を開く。
ヘッダーのマッピングを設定
- Cache-Control: 'public, max-age=600'
- Access-Control-Allow-Headers: 'Content-Type,X-Amz- Date,Authorization,X-Api-Key'
- Access-Control-Allow-Origin: '*'
- Access-Control-Allow-Methods: 'GET'
- Content-Type: 'application/json;charset=UTF-8'

### API のデプロイ
/api/rentToshinBukkenSearch の GET を選択している状態で、アクションタブから「APIのデプロイ」をクリック。

デプロイされるステージで「prod」を選択し、デプロイをクリック。

### /api/rentToshinTargetManshonSearch の設定
/api/rentToshinBukkenSearch の GET をクリック。


### メソッドリクエストをクリック
URL クエリ文字列パラメータを開く
クエリ文字列の追加をクリック
areaCd を追加

### 統合リクエストをクリック
Lambda プロキシ統合の使用 のチェックを外す。
URL クエリ文字列パラメータを開く。
areaCd を追加。
名前:areaCd
マッピング元:'method.request.querystring.areaCd'
マッピングテンプレートに application/json を追加。テンプレートに以下を設定。
※上記マッピングテンプレートを参照

### メソッドレスポンス
HTTPのステータス、200、400、500を追加する。

HTTP のステータス 200 を開く。
- Cache-Control
- Access-Control-Allow-Headers
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Content-Type

### 統合レスポンス
Lambdaエラー正規表現に 400.*、ステータス 400 を追加。
Lambdaエラー正規表現に 500.*、ステータス 500 を追加。

200 を開く。
ヘッダーのマッピングを設定
- Cache-Control: 'public, max-age=600'
- Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key'
- Access-Control-Allow-Origin: '*'
- Access-Control-Allow-Methods: 'GET'
- Content-Type 'application/json;charset=UTF-8'
マッピングテンプレートの application/json を削除。

### API のデプロイ
/api/rentToshinTargetManshonSearch の GET を選択している状態で、アクションタブから「APIのデプロイ」をクリック。

デプロイされるステージで「prod」を選択し、デプロイをクリック。

### /api/rentToshinTenpoSearch の設定
/api/rentToshinTenpoSearch の GET をクリック。

### メソッドリクエストをクリック
URL クエリ文字列パラメータを開く
クエリ文字列の追加をクリック
tenpoCd を追加

### 統合リクエストをクリック
Lambda プロキシ統合の使用 のチェックを外す。
URL クエリ文字列パラメータを開く。
tenpoCd を追加。
名前:tenpoCd
マッピング元:'method.request.querystring.tenpoCd'
マッピングテンプレートに application/json を追加。テンプレートに以下を設定。
※上記マッピングテンプレートを参照

### メソッドレスポンス
HTTPのステータス、200、400、500を追加する。

HTTP のステータス 200 を開く。
- Cache-Control
- Access-Control-Allow-Headers
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Content-Type

### 統合レスポンス
Lambdaエラー正規表現に 400.*、ステータス 400 を追加。
Lambdaエラー正規表現に 500.、ステータス 500 を追加。

200 を開く。
ヘッダーのマッピングを設定
- Cache-Control: 'public, max-age=600'
- Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key'
- Access-Control-Allow-Origin: '*'
- Access-Control-Allow-Methods: '*'
- Content-Type: 'application/json;charset=UTF-8'
マッピングテンプレートの application/json を削除。

### API のデプロイ
/api/rentToshinTenSearch の GET を選択している状態で、アクションタブから「APIのデプロイ」をクリック。

デプロイされるステージで「prod」を選択し、デプロイをクリック。

### /api/tyotyomokuSearch の設定
/api/tyotyomokuSearch の GET をクリック。

### メソッドリクエストをクリック
URL クエリ文字列パラメータを開く
クエリ文字列の追加をクリック
sikutyousonCd を追加。
todoufukenCd を追加。

### 統合リクエストをクリック
Lambda プロキシ統合の使用 のチェックを外す。
URL クエリ文字列パラメータを開く。
sikutyousonCd,todoufukenCd を追加。
名前:sikutyousonCd
マッピング元:'method.request.querystring.sikutyousonCd'
名前:todoufukenCd
マッピング元:'method.request.querystring.todoufukenCd'
マッピングテンプレートに application/json を追加。テンプレートに以下を設定。
※上記マッピングテンプレートを参照

### メソッドレスポンス
HTTPのステータス、200、400、500を追加する。

HTTP のステータス 200 を開く。
- Cache-Control
- Access-Control-Allow-Headers
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Content-Type

### 統合レスポンス
Lambdaエラー正規表現に ((.*invalid params.*)|(400 .*))、ステータス 400 を追加。
Lambdaエラー正規表現に 500.、ステータス 500 を追加。

200 を開く。
ヘッダーのマッピングを設定
- Cache-Control: 'public, max-age=600'
- Access-Control-Allow-Headers: '*'
- Access-Control-Allow-Origin: '*'
- Access-Control-Allow-Methods: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key'
- Content-Type: 'application/json;charset=UTF-8'
マッピングテンプレートの application/json を削除。

### API のデプロイ
/api/tyotyomokuSearch の GET を選択している状態で、アクションタブから「APIのデプロイ」をクリック。

デプロイされるステージで「prod」を選択し、デプロイをクリック。

# コンテンツのアップロード
/ 配下に置くコンテンツのアップロードを行います。(この例では prod-j2-serverless-contets バケットへアップロード)
Cloud9 環境か、 aws コマンドインストール済及び環境変数 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY が反映されている環境で実行してください。
```
$ aws s3 sync contents/ s3://prod-j2-serverless-contents/
```
# 動作確認
全て設定後、以下のURLをブラウザからアクセスし、動作確認を行います。エラーなく、JSONを応答することを確認します。
xxxxxxxx.cloudfront.net は CloudFront のコンソールでドメインを確認してください。
```
# S3 バケット内のコンテンツにアクセスできること
https://xxxxxxxx.cloudfront.net/index.html
# API はそれぞれ、CloudFront 経由でアクセスできることを確認する
https://xxxxxxxx.cloudfront.net/prod/api/rentToshinTargetManshonSearch?areaCd=1
https://xxxxxxxx.cloudfront.net/prod/api/rentToshinTenpoSearch?areaCd=1
https://xxxxxxxx.cloudfront.net/prod/tyotyomokuSearch?todoufukenCd=1&sikutyousonCd=1
https://xxxxxxxx.cloudfront.net/prod/api/rentToshinBukkenSearch?bukkenId=4W919127×tamp=1563262539248
```
# 再構築する場合
環境を再構築する場合、下記の削除のコマンドを実行後、デプロイ手順を実施する。
```
$ sls remove
```