Node-REDをAWS API Gateway + lambda + S3で動かす方法

# はじめに
[2017年 Node-RED Advent Calendar](https://qiita.com/advent-calendar/2017/nodered)の３日目の記事です。

2015年 Node-RED Advent Calendarで@stomitaさんが [Node-REDをAWS Lambdaで動かす話](https://qiita.com/stomita/items/0d564c3950d91dc3f6dc) という記事を書かれています。
この方式はS3やSESなどのトリガーで動かせるというメリットがありますが、**HTTP inノード**の利用を諦める＋専用ノードを使ってフローを作る必要がありました。

今回の記事は、**HTTP inノード**が利用できる状態でNode-REDをlambdaで動かす方法です。
これによって**HTTP inノード**を使ったフローを修正なしでlamdbaで動かせるようになります。
（デメリットとしてAPI Gateway以外のトリガーでは動かせません。）

また、フロー更新のたびに毎回`flow.json`を含んだNode-REDをlambdaにデプロイする構成では、デプロイ時間がネックで修正サイクルがリズムよく回せません。
これでは開発効率が悪いので、`flow.json` ファイルはS3に置いてフロー更新はS3のファイルを更新すれば済む構成にします。

# 構成図
今回の構成は下図のとおりです。この記事では青点線範囲の設定方法を説明します。
![node-red-on-lambda-with-api-gw-overview.png](https://qiita-image-store.s3.amazonaws.com/0/133603/c52f03a6-9043-c05b-2c8d-2be414697435.png)

# 準備するもの
* AWSアカウント
* AWS CLI が使える端末（AWSアクセスキー他が設定済みであること）

    ```bash:
$ export AWS_ACCESS_KEY_ID=AKIAXXXXXXXXXXXXXXXX
$ export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ export AWS_DEFAULT_REGION=us-west-2
```

* ブラウザ

# 設定方法
* 所要時間参考：約10分

1. GitHubから[aws-serverless-node-red](https://github.com/sakazuki/aws-serverless-node-red) リポジトリをクローンします

    ```bash:
$ git clone https://github.com/sakazuki/aws-serverless-node-red.git
$ cd aws-serverless-node-red
```

2. 自分のAWS環境に合わせた設定ファイルを準備します

    ```bash:
$ AWS_ACCOUNT_ID=123456789012
$ S3_BUCKET=nodered12
$ AWS_REGION=us-west-2
$ AWS_FUNCNAME=api12
$ AWS_STACK_NAME=Node-RED
$ npm run config -- --account-id="${AWS_ACCOUNT_ID}" \
    --bucket-name="${S3_BUCKET}" \
    --region="${AWS_REGION}" \
    --function-name="${AWS_FUNCNAME}" \
    --stack-name="${AWS_STACK_NAME}"
```
※これにより次のファイルが自分のAWS環境用に更新されます。

    ```bash:
package.json
simple-proxy-api.yaml
cloudformation.yaml
settings.js
```

3. Node-REDと必要パッケージをインストールして、lambdaにデプロイします。

    ```bash:
$ npm run setup
```

4. PC上でNode-REDを起動してフローを作成します

    ```bash:
$ node node_modules/.bin/node-red -s ./settings.js
```
`http://localhost:1880` にブラウザでアクセスしてNode-REDエディタでフローを作成します。
**HTTP inノード**が使えることを確認するためフォームのサンプルフローを作ります。
    ![node-red-on-lambda-flow.png](https://qiita-image-store.s3.amazonaws.com/0/133603/89483510-7c32-af78-ac12-5081bb11d35e.png)

    <details><summary>フローデータはこちら</summary>コピーして、右上のドロップメニューから**[読み込み]-[クリップボード]**でインポートできます<pre>
[{"id":"e164ca79.d4bba8","type":"http in","z":"d540fb70.fb4658","name":"","url":"/hello","method":"get","upload":false,"swaggerDoc":"","x":170,"y":100,"wires":[["f5cc40d7.836fd"]]},{"id":"4818807c.9a996","type":"http response","z":"d540fb70.fb4658","name":"","statusCode":"","headers":{},"x":510,"y":100,"wires":[]},{"id":"f5cc40d7.836fd","type":"template","z":"d540fb70.fb4658","name":"form","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"&lt;form method=\"POST\"&gt;\n    Input your name&lt;input type=\"text\" name=\"key1\"&gt;\n    &lt;input type=\"submit\"&gt;\n&lt;/form&gt;","output":"str","x":350,"y":100,"wires":[["4818807c.9a996"]]},{"id":"5c77ba58.b744e4","type":"http in","z":"d540fb70.fb4658","name":"","url":"/hello","method":"post","upload":false,"swaggerDoc":"","x":180,"y":180,"wires":[["4c91fba9.11ed84"]]},{"id":"4c91fba9.11ed84","type":"template","z":"d540fb70.fb4658","name":"view","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"&lt;h1&gt;Hello {{payload.key1}}&lt;/h1&gt;","output":"str","x":350,"y":180,"wires":[["4818807c.9a996"]]}]</pre>
</details>

5. 動作確認(ローカル)
デプロイしたら、次のURLにアクセスしてローカルで動作確認してください。

    ```
http://localhost:1880/hello
```

6. 動作確認(API Gateway)
正常動作が確認できたら、API Gateway経由でも確認します。
次のコマンドでAPI Gatewayの**ApiUrl**を確認します。

    ```bash:
$ aws cloudformation describe-stacks --stack-name ${AWS_STACK_NAME} \
  --output json --query "Stacks[*].Outputs"
[
    [
        {
            "Description": "Invoke URL for your API. Clicking this link will perform a GET request on the root resource of your API.",
            "OutputKey": "ApiUrl",
            "OutputValue": "https://1xxx2y5zzz.execute-api.us-west-2.amazonaws.com/prod/"
        },
        {
            "Description": "Console URL for the API Gateway API's Stage.",
            "OutputKey": "ApiGatewayApiConsoleUrl",
            "OutputValue": "https://us-west-2.console.aws.amazon.com/apigateway/home?region=us-west-2#/apis/1xxx2y5zzz/stages/prod"
        },
        {
            "Description": "Console URL for the Lambda Function.",
            "OutputKey": "LambdaFunctionConsoleUrl",
            "OutputValue": "https://us-west-2.console.aws.amazon.com/lambda/home?region=us-west-2#/functions/api12"
        }
    ]
]
$
```
ブラウザで **ApiUrl**にアクセスして動作確認します。

    ```bash:
https://1xxx2y5zzz.execute-api.us-west-2.amazonaws.com/prod/hello
```
    ![node-red-on-lambda-with-api-gw (6).png](https://qiita-image-store.s3.amazonaws.com/0/133603/a30a7fc2-e9a5-0cca-ba8a-025e2ed19e8e.png)
動作が確認できました。

1. 後片付け（オプション）
この手順で作成されたものを削除する場合には以下のコマンドを実行します。

    ```bash:
$ npm run delete-stack
```

# まとめ

* lambdaでNode-REDのフローを動かしました
* Node-RED on lambdaで**HTTP inノード**が使えるようになりました
* フローファイル(`flow.json`)をS3から読み込ませました。
    * これでNode-RED本体の再デプロイをすることなく、フローファイルだけを更新できるようになり、開発や修正が簡単になりました。

Node-REDで作ったAPIアプリは、lambdaに簡単に載せられることがわかりました。
皆さんも、試してみてはどうでしょうか。

# 明日は
@taiponrockさんの**Node-RED on IBM Cloud with Watson API**の記事です。

# 参考
- [aws-serverless-node-red](https://github.com/sakazuki/aws-serverless-node-red)
- [aws-serverless-expressのexamples](https://github.com/awslabs/aws-serverless-express)