【追記あり】Dockerコンテナ上で動作するNode-REDアプリケーションを簡単に構築する方法

こんにちは。株式会社 日立ソリューションズの山本です。
この記事は、日立ソリューションズグループAdvent Calender 2021の記事になります。

(2021/12/28追記)
起動したDockerコンテナ上のNode-REDのフローエディタへのアクセスを抑止するために、Dockerfile内のNode-RED起動コマンドに``-D httpAdminRoot=false -D httpNodeCors.origin="*" -D httpNodeCors.methods="GET"``を追記しています。

# 1．はじめに
この記事ではNode-REDアプリケーションを作成し、Dockerfileで構築する方法を紹介します。
Dockerfileを使用することで、環境を意識せずにNode-REDアプリケーションを構築することができます。

# 2．全体の構成
## 2．1 全体の構成と操作の流れ
本記事で構築する構成と操作の流れを説明します。

![構成図_rev3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/eb14304b-769e-a7a4-69de-0f4eb90a39ad.png)

<table width="100%" font-size="10.5pt">
 <tr align="center">
  <th>#</th>
  <th>項目</th>
　<th>内容</th>
 </tr>
 <tr align="center">
  <td>1</td>
  <td>REST API作成</td>
　<td align="left">Node-REDでREST APIを作成します。</td>
 </tr>
 <tr align="center">
  <td>2</td>
  <td>Dockerコンテナ起動</td>
  <td>作成したREST APIを指定するDockerfileを作成します。DockerfileをビルドしてDockerイメージを作成し、DockerイメージからDockerコンテナを起動し、Node-REDをDockerコンテナ上に構築します。</td>
 </tr>
 <tr align="center">
  <td>3</td>
  <td>動作確認</td>
  <td>Dockerコンテナ上に構築したNode-REDに対してREST APIリクエストを送信し、レスポンスが返ってくることを確認します。</td>
 </tr>
</table>

## 2．2 ソフトウェアとバージョン情報

本記事で使用したソフトウェアとバージョン情報です。

<table width="100%" font-size="10pt">
 <tr align="center">
  <th>#</th>
  <th>ソフトウェア</th>
  <th>バージョン</th>
 </tr>
 <tr align="center">
  <td>1</td>
  <td>Docker for windows</td>
  <td align="left">3.5.2</td>
 </tr>
 <tr align="center">
  <td>2</td>
  <td>Node.js</td>
  <td align="left">16.3.0</td>
 </tr>
 <tr align="center">
  <td>3</td>
  <td>Node-RED</td>
  <td align="left">2.1.3</td>
 </tr>
</table>

# 3．REST API作成
Node-REDで2つの入力値の合計を返す、REST APIを作成します。

## 3．1 全体のフロー
全体のフローは以下の通りです。
http inノード、functionノード、http responseノードを順に配置し、接続します。
![全体のフロー.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/577c5230-3138-7034-b945-87b9d554a711.png)

最終的なフローはこちらです。すぐに試したい方は、コピーしてNode-REDで読み込んでください。

<details><summary>flows.json</summary><div>

```flows.json
[
    {
        "id": "3df6f2735f64a7c9",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "0bbdaac489888327",
        "type": "http in",
        "z": "3df6f2735f64a7c9",
        "name": "",
        "url": "/test",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 210,
        "y": 200,
        "wires": [
            [
                "66154fd2213df4c5"
            ]
        ]
    },
    {
        "id": "184e9f8cf1d2dc8d",
        "type": "http response",
        "z": "3df6f2735f64a7c9",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 630,
        "y": 200,
        "wires": []
    },
    {
        "id": "66154fd2213df4c5",
        "type": "function",
        "z": "3df6f2735f64a7c9",
        "name": "",
        "func": "msg.payload = {sum : parseInt(msg.req.query.a) + parseInt(msg.req.query.b)};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 420,
        "y": 200,
        "wires": [
            [
                "184e9f8cf1d2dc8d"
            ]
        ]
    }
]
```
</div></details>

## 3．2 各ノードの設定
配置した各ノードの設定を行い、REST APIを作成します。
### http inノード
REST APIの受付口をhttp inノードで設定します。

+ 使用するメソッドを`GET` 、URLは`/test`を指定します。

![httpinノード.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/79364e81-c332-a482-59fc-0a84f8a3b6a8.png)
### functionノード
REST API内の処理を定義します。
GETで受け取ったパラメータ`a`、`b`に格納された数値を足して、`msg.payload`に格納し、フロー上で共有します。

+ 以下のコードをfunctionノードのコード欄に入力します。

```
msg.payload = {sum : parseInt(msg.req.query.a) + parseInt(msg.req.query.b)};
return msg;
```

![function_rev1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/2abd3514-ebb7-01e5-4759-be161fb91c16.png)

### http responseノード
REST APIのレスポンスとして`msg.payload`に設定したデータを送信します。
特に設定はいりません。

# 4．Dockerコンテナ起動
作成したREST APIを指定するDockerfileを作成します。
DockerfileをビルドしてDockerイメージを作成し、DockerイメージからDockerコンテナを起動し、Node-REDをDockerコンテナ上に構築します。

## 4．1 Dockerfile作成
Node-REDを構築し、作成したREST APIをNode-RED上で起動する手順を記載するDockerfileを作成します。
今回は``/data``を作業ディレクトリとします。
Node-RED起動コマンドに``-D httpAdminRoot=false -D httpNodeCors.origin="*" -D httpNodeCors.methods="GET"``を付与して、起動したDockerコンテナ上のNode-REDのフローエディタへのアクセスを抑止します。

```Dockerfile
#利用するDockerイメージの宣言
FROM node:lts-slim

#ビルドツールをインストール
RUN apt-get update \
  && apt-get install -y build-essential

#「/data」というディレクトリを作成し、作業ディレクトリとする
WORKDIR /data

#Node-REDインストール
RUN npm install node-red

#flows.jsonを「/data」ディレクトリにコピー
COPY ./flows.json /data/

#npmでインストールしたモジュールを格納するパスの設定
ENV NODE_PATH=/data/node_modules

#Node-RED起動コマンド
CMD node ./node_modules/node-red/red.js --userDir . -D httpAdminRoot=false -D httpNodeCors.origin="*" -D httpNodeCors.methods="GET" flows.json
```

## 4．2 Dockerイメージ作成
作成したDockerfileを使用してNode-RED及び作成したREST APIを含むDockerイメージを作成します。
Node-REDで作成したflows.jsonとDockerfileを構築する環境のカレントディレクトリに格納します。

カレントディレクトリより、以下のコマンドを実行して、Dockerイメージを作成します。
プロキシ環境内で実行する際は`build-arg`オプションを記載してください。
Dockerイメージ名は任意の名称を指定してください。

```
docker build --build-arg http_proxy=http://<your.proxy.url>:<port>/ --build-arg https_proxy=http://<your.proxy.url>:<port>/ . -t <Dockerイメージ名>
```

以下のコマンドを実行して、Dockerイメージが作成されたことを確認します。

```
# Dockerイメージの確認
docker images
 --- 実行結果例 ここから --------------------------------------------------------------------
REPOSITORY            TAG       IMAGE ID       CREATED           SIZE
<Dockerイメージ名>    latest    196c1ccae37b   18 seconds ago     485MB
 --- 実行結果例 ここまで---------------------------------------------------------------------
```

## 4．3 Dockerコンテナ起動
作成したDockerイメージからDockerコンテナを起動します。
以下のコマンドを実行します。
`-p`オプションを使用し、Dockerコンテナの内部で使用するNode-REDのポート`1880`を外部に公開する際に使用するポートを指定します。
今回は外部にはDockerコンテナをポート`8080`で公開します。
Dockerコンテナ名は任意の名称、Dockerイメージ名は「4．2 Dockerイメージ作成」で作成したDockerイメージ名を指定してください。

```
docker run -p 8080:1880 --name <Dockerコンテナ名> <Dockerイメージ名>
```

# 5．動作確認
Dockerコンテナ上に構築したNode-REDアプリケーションのREST APIに対してリクエストを送信し、レスポンスが返ってくることを確認します。
ブラウザより、以下のURLにアクセスします。
今回は引数に``a=2``、``b=1``を設定します

![リクエスト.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/2eed5507-feb5-15af-54c0-0f5c74dea1f9.png)

``a``、``b``の値を受け取ったNode-REDアプリケーションが``a``、``b``の合計値を計算し、``{"sum":3}``をレスポンスとして送信します。

![ブラウザ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2311423/5e8b950b-f2f3-5184-ca58-98c3840d9566.png)

# 6．おわりに
本記事ではDockerfileを使用してNode-REDアプリケーションを構築する方法を紹介しました。
Dockerfileを使用することで簡単にNode-REDアプリケーションを構築できるため、作成したNode-REDアプリケーションを構築する際に参考にしてみてください