Node-RED を AWS Fargate + EFS で動かす

[ACCESS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/access) 2 日目の記事になります。

Node-RED を AWS 上で動作させようと検討をした際、Fargate が有力な候補として挙がりました。
AWS コンソールを触りながら手探りで実装検討したところ、ある程度の形になったので記事にして紹介しようと思います。

# 前提知識
## Node-RED

[Node-RED](https://nodered.org/) とは主に IoT アプリの開発に使用されるフローベースプログラミングツールです。Node.js で動きます。ブラウザ上で表示されるノードをつないでフローを作成、デプロイする事でお手軽に IoT デバイスと連携可能なアプリが作成できます。
![スクリーンショット 2020-12-01 22.44.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/291bac1d-2c84-c48c-fa1e-ce2345ba1786.png)

Node-RED は大きく分けると下記 2 種類の機能から構成されています。

* Editor
* Runtime

Editor を操作して作成したフローを flows.json というファイル名でローカルディスクに書き出します。書き出された flows.json を Runtime 機能が読みとって API サーバとして動作する(Node-RED Runtime の中身は Express で実装されている) 形式になってます。
![スクリーンショット 2020-12-01 23.12.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/dff49633-f306-d773-d0ed-ac29c0ad955d.png)

## AWS Fargate

[AWS Fargate](https://aws.amazon.com/jp/fargate) とはホストマシンを意識する事なくコンテナが使えるサービスです。Fargate についての詳細はググれば記事がたくさん出てくるのでそちらを参考にしてみてください。自分が初めて使った時、Docker コンテナだけが宙に浮いてるようにしか見えないのに、コンテナ内のアプリがちゃんと動作してて驚きました。

Fargate に限った話ではないですが、API サーバはステートレスにするのが AWS のインフラ設計パターンです。従って Fargate 上で動いてるコンテナ内部の API サーバもステートを持たないようにしなければいけません。しかし Node-RED を普通に使うだけだとローカルディスクに flows.json を書き出してしまいます。このままだと同じタスクなのに、コンテナ毎に機能が異なる API サーバが出来てしまいます。

# Node-RED を Fargate + EFS で動かす

そこでこの記事の本題です。
Node-RED で生成される flows.json を [EFS](https://aws.amazon.com/jp/efs/) に保存して、各コンテナが同じ flows.json を読み込むようにシステム構成を組みました。
本記事では AWS コンソールを操作して下図のシステムをデプロイするまでの手順を紹介します。
![スクリーンショット 2020-12-02 11.07.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/479cb7d1-3dd5-aa4f-6fc9-28e69406b386.png)

# 実行手順

## ロードバランサー
今回は案件の都合上、ALB を選択しました。しかし ALB だと Node-RED の mqtt ノードが使用できないようです。mqtt ノードが使いたければ NLB を選択してください。

Node-RED のデフォルトリスナーポートが 1880 なので、ロードバランサーのポートも 1880 を指定します。
![スクリーンショット 2020-12-02 11.17.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/b0bfe51e-5662-9f7b-4d04-d004967757b5.png)

セキュリティグループの設定では TCP でポートは 1880 の通信を許可するセキュリティグループを作成します。
![スクリーンショット 2020-12-02 11.23.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/3e915971-e531-774e-b30e-a1c507eb402a.png)

ターゲットグループの設定ではターゲットの種類を IP、ポートは 1880 を指定します。
![スクリーンショット 2020-12-02 12.07.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/2198d4a5-76cf-3b34-2219-93a234b4c024.png)

## EFS

EFS は名前を入力、VPC を選択して一旦ファイルシステムを作成します。
![スクリーンショット 2020-12-02 11.35.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/4163378b-90f0-16c4-8123-26900478d98b.png)
作成したファイルシステムからネットワークタブをクリックしてマウントターゲットを作成してください。
![スクリーンショット 2020-12-02 11.40.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/9066d015-60cb-25f8-382c-7d4bfa222dce.png)
ここで選択しているセキュリティグループの Node-RED-EFS は NFS 通信を許可するためにポート 2049 を開いています。
理由は後述しますが、マウントターゲットの 1 個はパブリックサブネットを選択してください。

### Permission 管理

選択したパブリックサブネット上に EC2 を立てて、ファイルシステムのディレクトリ権限変更を実施します。
コンテナ内で Node-RED を実行する時、Node-RED に管理者権限がないので flows.json の保存に失敗してしまうためです。おそらく Docker 起動時に実行されるコマンドを修正することでこのようなことはしなくて済むはずなのですが、記事執筆時点ではアプリまで手が回りませんでした。

Amazon Linux 2 AMI を選択し、ssh でログインして下記コマンドを実行します。

```sh
mkdir data
sudo mount -t efs {ファイルシステムID}:/ /home/ec2-user/data
sudo chmod 777 /home/ec2-user/data
```

ここまで完了したら EC2 を終了し、マウントターゲットのパブリックサブネットをプライベートサブネットに変更してください。

## Fargate

### タスク定義
Node-RED 用のタスクを定義します。
Fagate を選択してください。
![スクリーンショット 2020-12-02 11.52.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/9ecbb630-0418-349c-ee40-941bf8e478a5.png)

タスクメモリは 0.5GB、タスク CPU は 0.25vCPU を選択しました。

コンテナ追加より先にボリュームを追加します。
先ほど作成したファイルシステム ID を選択します。
![スクリーンショット 2020-12-02 11.56.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/434011fa-6614-bfea-d7ea-afadd7f46bf2.png)

コンテナを追加します。
[DockerHub の node-red](https://hub.docker.com/r/nodered/node-red/) をイメージとして使用します。
ポートは 1880 をマッピングします。
![スクリーンショット 2020-12-02 11.55.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/6f5d2a71-6523-6e66-894b-15e0ea9d239e.png)
マウントポイントに先ほど追加したボリュームを指定します。
パスは /data/ と入力します。
![スクリーンショット 2020-12-02 11.56.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/baff92d1-6320-bcdb-8084-e2174eb86311.png)

### クラスター・サービス
今回は Node-RED-Cluster というクラスターを作成しました。
作成したクラスターのサービスタブから作成を選択します。
先ほど作成したタスクを選択してください。
またプラットフォームのバージョンは 1.4.0 を明示的に選択してください。
2020/12/2 時点では LATEST を選択すると 1.3.0 で動作します。
1.3.0 だと Fargate から EFS にマウント出来ません。
![スクリーンショット 2020-12-02 12.01.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/9d7b21e9-aa52-53ce-7079-95ebcc84c399.png)
タスクの数は 3 を入力します。

サブネットはファイルシステムに作成したマウントターゲットの 3 個のサブネット、セキュリティグループはポート 1880 の通信を許可する物にしてください。

ロードバランサーは先ほど作成した物を選択し、ロードバランス用のコンテナは同時に作成したターゲットグループを選択します。
![スクリーンショット 2020-12-02 12.11.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/3acfd74d-396a-d3cd-1504-1339b96b4716.png)

以上で AWS 上の設定は完了しました。

# 動作確認

タスクが全て RUNNING になってから http://ALBのDNS名:1880 にアクセスすると、Node-RED Editor が表示されます。
![スクリーンショット 2020-12-02 13.19.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/ced1a702-c2e1-3ac0-314a-653201a811d7.png)

Node-RED Editor で /test に http get をリクエストすると json を返すフローを作成、デプロイしました。
デプロイ後、一旦タスクを全て終了します。flows.json を編集した Node-RED 以外は変更後のフローを反映していないためです。
![スクリーンショット 2020-12-02 12.20.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163276/8e80e038-e8b5-b960-feb6-324c42ea8560.png)

再びタスクが自動で 3 個立ち上がるので、全て RUNNING となった状態で curl を叩いてみます。

```sh
curl http://ALBのDNS名:1880/test
{"msg":"Hello, Node-RED!"}
```

複数回実行しても同じレスポンスが返ってくるので、全ての Node-RED で同じ flows.json が読み込まれていることを確認できました。

# 最後に

Node-RED で Fargate を使って冗長構成をとることができました。
手探りで検討していたので、手間がかかる手順になってしまいました。
コードに落とし込むときは、もっと整理したいと思います。

明日は @ten_takano さんの投稿で「大事なデータの守り方」です。お楽しみに。

# 参考記事

https://dev.classmethod.jp/articles/fargate-my-first-step/
https://dev.classmethod.jp/articles/efs-fargate/
https://qiita.com/tandfy/items/829f9fcc68c4caabc660