カスタマイズしたNode-REDコンテナを作って、IBM Cloud Code Engineで動かすまで

# 目的
IBM Cloudで、コンテナを手軽に動かす手段として、IBM Cloud Code Engineがあることから、自分でカスタマイズや開発したコンテナイメージをIBM Cloud Code Engineで動かす方法を備忘録的にまとめるために作成しました。

# 準備しておくこと
- DockerHubのアカウント登録
- DockerをインストールしたLinux開発環境
    - この記事では、Ubuntu 24.04 Serverの仮想マシンをパソコン上に用意して開発環境・検証環境として使用。 - Linux開発環境に、Node-RED環境を用意
- Linux開発環境に、IBM Cloud CLIをインストール

## DockerHubのアカウント登録
https://hub.docker.com/ にアクセスして、アカウント登録を済ませてください。

## Linux開発環境の用意
この記事では、Ubuntu 24.04 Serverの仮想マシンにしています。VirtualBoxやUTMなり使って用意しましょう。

## Linux開発環境に、Node-RED環境を用意
カスタマイズしたコンテナイメージを作るために、いろいろと検証する環境が必要ですから、Linux開発環境にNode-RED環境を用意するために、次のコマンドを実行します。
```
sudo snap install node-red
```
ホスト名.local でアクセスできるように、avahi-daemonを入れます。
```
sudo apt-get install avahi-daemon
```
これで、仮想のLinux開発環境を動かしてい入るホストマシンのWebブラウザから、http://Linux開発環境のホスト名.local:1880 でNode-RED環境を表示できるようになります。

## IBM Cloud CLIのインストール
IBM CLoud CLIをインストールするには、次のコマンドを実行します。
```
curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
```
IBM Cloud CLIがインストールできたことを確認するために、次のコマンドを実行します。
```
ibmcloud help
```
インストールされたバージョンも確認してみましょう。
```
ibmcloud --version
```
2024年9月現在は、こちらになります。
```
ibmcloud 2.27.0+d5addb5-2024-07-05T19:22:17+00:00 Copyright IBM Corp. 2014, 2024
```

# カスタマイズしたNode-REDのコンテナイメージを作成
カスタマイズしたNode-REDコンテナの作成には、「Dockerfile」、「package.js」、「settings.js」、「flows.json」の4つが最低限必要となります。普段使っているNode-RED環境から複製すれば楽に用意できるのですが、ここではサンプルを提示します。

## 作業用ディレクトリの作成
何でもよいので作業用ディレクトリをLinux開発環境内に作成します。
```
mkdir noderedwork1
cd noderedwork1
```
## Dockerfile
Dockerfileは、Node-RED公式ドキュメントの[Running under Docker](https://nodered.org/docs/getting-started/docker)から引用し、1か所だけ変更します。「COPY flows_cred.json /data/flows_cred.json」の先頭に「#」を追加し、コメントアウトしています。
「flows_cred.json」を使用する場合は、コメントアウトせずに使ってください。

エディタを使って、Dockerfileを作成します。エディタはnanoを使っていますが、viでもvimでも構いません。
```
nano Dockerfile
```
記述内容
```
FROM nodered/node-red

# Copy package.json to the WORKDIR so npm builds all
# of your added nodes modules for Node-RED
COPY package.json .
RUN npm install --unsafe-perm --no-update-notifier --no-fund --only=production

# Copy _your_ Node-RED project files into place
# NOTE: This will only work if you DO NOT later mount /data as an external volume.
#       If you need to use an external volume for persistence then
#       copy your settings and flows files to that volume instead.
COPY settings.js /data/settings.js
#COPY flows_cred.json /data/flows_cred.json
COPY flows.json /data/flows.json

# You should add extra nodes via your package.json file but you can also add them here:
#WORKDIR /usr/src/node-red
```
## package.json
package.jsonは、Node-REDに必要な各ノードを記述しておくことで、Node=RED起動時に、たとえばIBM WatsonやWatsonx、ChatGPT、Amazon Alexaスキル、IoTデバイスのObniz等を扱うことのできるノードを事前にインストールしておくことができます。

エディタを使って、package.jsonを作成します。エディタはnanoを使っていますが、viでもvimでも構いません。
```
nano package.json
```
今回の記述内容は次のとおりです。「"node-red": "*",」は必須ですが、それ以外は、Node-REDで必要となるノードについて記述していきます。
```
{
    "name": "node-red-project",
    "description": "initially created for you by Node-RED 3.1.0",
    "version": "0.0.2",
    "private": true,
    "dependencies": {
        "node-red": "*",
        "node-red-node-watson": "*",
        "node-red-contrib-cloudantplus": "*",
        "node-red-contrib-simple-chatgpt": "*",
        "node-red-contrib-dotnsf-watsonxai": "*",
        "node-red-contrib-alexa-remote2-applestrudel": "*",
        "node-red-contrib-play-sound": "*",
        "node-red-contrib-node-line-api": "*",
        "node-red-contrib-google-sheets": "*",
        "node-red-contrib-image-output": "*",
        "node-red-contrib-obniz": "*",
        "node-red-contrib-mongodb-aleph": "*",
        "node-red-dashboard": "*",
        "node-red-node-base64": "*"
    }
}
```
実際にビルドしたNode-REDのコンテナイメージを動かすと、このようになります。「watsonxai」や「obniz」、「Googleスプレッドシート」など、Node-REDの標準の状態では、まず入っていないノードが追加されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/9cc339e6-ea7a-cd74-bbc7-de6e0e975936.png)

## settings.js
settings.js は、Node-REDにおけるセキュリティ設定などを担っています。下記は、Linux開発環境で動くNode-RED環境のsettings.js を複製して使っています。
この作業のもとになるsettings.jsは、Linux開発環境でNode-REDのインストール作業を行なったディレクトリ内の、.node-red という名称の隠しディレクトリにあります。これを、作業用ディレクトリに複製し、編集します。

エディタを使って、settings.jsを作成します。エディタはnanoを使っていますが、viでもvimでも構いません。
```
nano settings.js
```
変更箇所のみ記載します。見てのとおり、ベーシック認証を有効化しています。
```
~~~ 略 ~~~~ 

    adminAuth: {
        type: "credentials",
        users: [{
            username: "admin",
            password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
            permissions: "*"
        }]
    },

~~~ 略 ~~~~
```
$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.は、password という文字列をハッシュ値にしたものになります。詳しくは、[Node-RED公式ドキュメントのセキュリティ](https://nodered.jp/docs/user-guide/runtime/securing-node-red)をご確認ください。

カスタマイズしたNode-REDコンテナイメージを起動すると、認証を求められるようになります。標準のNode-REDコンテナイメージを起動しただけでは、認証画面は出てきません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/805daccf-7bbf-8be6-f088-2ce8202928d1.png)

## flows.json
flows.json を用意することで、Node-RED起動時に表示するフローを変更することができます。
ここでは、Hello World！と表示するだけのフローを用意しました。
```
nano flows.json
```
次のような内容です。
```
[
    {
        "id": "73b498fb847aa995",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "59cecb601c425aec",
        "type": "inject",
        "z": "73b498fb847aa995",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "Hello World !",
        "payloadType": "str",
        "x": 110,
        "y": 60,
        "wires": [
            [
                "2a7f281b45c623d6"
            ]
        ]
    },
    {
        "id": "2a7f281b45c623d6",
        "type": "debug",
        "z": "73b498fb847aa995",
        "name": "debug 1",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 380,
        "y": 60,
        "wires": []
    }
]
```
実際に動かすとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/f00a873a-99d2-d4e0-0e68-cc410c3e7223.png)

Linux開発環境にインストールしたNode-RED環境でフローを試作し、試作したフローを流用しても良いでしょう。

## Dockerfileをもとにビルド
作業用ディレクトリ内で、次のコマンドを実行します。これで、カスタマイズしたNode-REDのコンテナイメージが、作業用のパソコン内に登録されます。
```
docker build -t mynode-red:latest .
```
コンテナイメージとして登録されたか確認するため、次のコマンドを実行します。レポジトリ名を「mynode-red」、タグ名が「latest」としてコンテナイメージが登録されます。
```
docker images | grep node-red
```
実行結果
```
REPOSITORY                     TAG        IMAGE ID       CREATED        SIZE
mynode-red                     latest     3a4ddab5dc9a   3 hours ago    765MB
```
IMAGE IDは、実行毎に異なるので、必ず自分の環境で確認してください。この後の作業で、IMAGE IDを使いますので、コピペ作業は無意味です。

# 作成したコンテナイメージを、DockerHubにプッシュ
コンテナレジストリの１つである「DockerHub」に、ビルドしたカスタマイズ済みのNode-REDコンテナイメージをプッシュ（登録）します。
先ずは、DockerHubsに接続しましょう。ここでは、DockerHubのユーザー名を、user1 とします。必ず自分のユーザー名を使ってください。
```
docker login --username user1
```
DockerHubのパスワードを求められますので入力し、Enterキーを押して、DockerHubに接続します。
プッシュするために、docker tag コマンドを使って、コンテナイメージにタグをつけます。
```
docker tag コンテナのIMAGE ID DockerHubのユーザー名/レポジトリ名:タグ名
```
上記、先ほど確認したIMAGEIDとユーザー名を当てはめて、次のようにしました。IMAGE IDとユーザー名は、各自で異なるので、コピペは無意味です。
```
docker tag 3a4ddab5dc9a user1/mynode-red:ibmcloud
```
docker push コマンドを使って、DockerHubにコンテナイメージをプッシュ（登録）します。
```
docker push DockerHubのユーザー名/レポジトリ名:タグ名
```
上記、docker tag の時と同じDockerHubのユーザー名、レポジトリ名、タグ名を使わないといけないので、次のように記述し、実行しました。
```
docker push user1/mynode-red:ibmcloud
```
これで、user1というDockerHubユーザーが管理する、mynode-red:ibmcloud というコンテナイメージを利用できるようになりました。
DockerHubをWebブラウザで表示すると、コンテナイメージが登録されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/440013a4-1b94-7444-96f2-fe4fc22d8b49.png)

この記事では、DockerHubを使いましたが、「IBM Cloud Container Registry」や「GitLab Container Registry」などを使っても良いでしょう。

# IBM Cloud CLI から、IBM Cloud Code Engineにアプリケーションをデプロイ
IBM Cloud CLIからIBM Cloudにログインし、作成したコンテナイメージを使って、IBM Cloud Code Engine上で起動します。
## IBM Cloud CLIからログイン
現在のIBM Cloudでは多要素認証を使いログインしますので、多くの人が多要素を有効化しているはずです。
そこで、WebブラウザでIBM Cloudにログインし、IBM Cloud CLIからログインするためのコマンドを取得します。
IBM Cloudにログイン後、画面右上のアバターアイコンをクリックし、「CLIとAPIにログイン」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/8211e4af-b7b0-36b9-7fa3-720546451951.png)

IBM Cloud CLI向けのワンタイムパスコード付きログインコマンドをコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/42a08685-a3db-a29d-e1e0-041113a2e51b.png)

Linux開発環境で、コピーしたコマンドを実行します。
```
ibmcloud login -a https://cloud.ibm.com -u passcode -p xxxxxxxxxxxxx
```
リージョンの選択が求められます。この記事では、4 を選びました。
```
Select a region (or press enter to skip):
1. au-syd
2. in-che
3. jp-osa
4. jp-tok
5. kr-seo
6. eu-de
7. eu-es
8. eu-gb
9. ca-tor
10. us-south
11. us-east
12. br-sao
Enter a number> 4
```
## リソースグループを指定
次のコマンドを実行し、既存のリソース・グループを表示します。
```
ibmcloud resource groups
```
結果の例
```
Name      ID                                 Default Group   State
default   xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx   true            ACTIVE
```
ここでは名前が「default」というリソース・グループをターゲットに指定するため、次のコマンドを実行します。
```
ibmcloud target -g default
```
## Code Engine CLI プラグインのインストール
IBM Cloud CLIで、IBM Cloud Code Engineを使用するために、Code Engine CLI プラグインをインストールします。
```
ibmcloud plugin install code-engine
```
実行後は画面の指示に従って操作します。
インストールが完了すると、以下のように表示されます。(2023年9月15日現在)
```
Plugin Name                              code-engine[ce]
Plugin Version                           1.45.4
Plugin SDK Version                       1.1.0
Minimal IBM Cloud CLI version required   1.0.0
Private endpoints supported              true
```
## Code Engine用プロジェクトの作成
IBM Cloud Code Engineでは、アプリケーションやジョブなどを動かすための器を「プロジェクト」と言い、必ず用意しなければいけません。
次のコマンドを実行します。作成するプロジェクト名は、1stproject としました。プロジェクト名は英数字が使えますので、好みの名称で良いでしょう。
```
ibmcloud ce project create --name 1stproject
```
作成済みの既存のプロジェクトは、次のコマンドを実行することで確認できます。
```
ibmcloud ce project list
```
## プロジェクトの選択
プリケーションを作成するにあたり、プロジェクトを指定するため、次のコマンドを実行します。ここでは、1stprojectを指定しています。実際は、1stprojectのところがプロジェクト名を指定する箇所になりますので、各自が作成したプロジェクト名に置き換えてコマンドを実行してください。
```
ibmcloud ce project select -n 1stproject
```
## Code Engine アプリケーションの作成
前の作業でDockerHubに登録したコンテナイメージを呼び出して、IBM Cloud Code Engineにアプリケーションとして作成します。
次のコマンドを実行します。(例として、DockerHubのユーザー名を、user1としています。)
```
ibmcloud ce application create --name mynodered --image user1/mynode-red:ibmcloud --min-scale 1
```
## Webブラウザで、アプリケーションのデプロイ状況を確認
Webブラウザで、IBM Cloud Code Engineの管理画面にアクセスします。
IBM CloudにWebブラウザでログイン後、画面左上の「三」>>「Code Engine」>>「プロジェクト」の順にクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/0835b2fa-7ff0-5c44-6dae-bfc55598e6bc.png)
作成済みのプロジェクト名をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/15dd578b-29a7-a8ef-302f-9f6f16f742a8.png)
画面表側の「アプリケーション」をクリックします。
アプリケーションの一覧画面に、デプロイしたアプリケーションが表示されています。下図では「mynodered」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/f438d894-7ac9-081e-6c9e-df53b006925e.png)

この画面でアプリケーションの稼働状況などの詳細な情報を知りたい場合は、アプリケーション名をクリックすることで表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/9f20b1ef-ec9f-ced9-a4ba-324a2c50a398.png)

# 動作確認
Code EngineのWeb画面で、プロジェクト内のアプリケーション画面からデプロイ済みのアプリケーションについて、「URLを開く」を実行することで、Webブラウザで、Code Engineで起動したカスタマイズ済みのNode-REDを利用できることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/f438d894-7ac9-081e-6c9e-df53b006925e.png)

Code Engineで動かしているため、自動でHTTPS接続になっていることがわかります。また、カスタマイズしたNode-REDコンテナイメージを使っているため、Node-REDの公式イメージには存在しないノードがインストールされていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41414/02f5b9cf-9ac2-63d0-4a83-0015fff85cf1.png)

# まとめ
手元のLinux開発環境を使い、好みのコンテナイメージを作り、IBM Cloud Code Engineにデプロイすることを確認できました。IBM Cloudにデプロイする前に、Linux開発環境を用意しておけば、出来ることの幅が広がリます。

筆者は、Debian・Ubuntu系のLinux環境を好んで使っていますが、人それぞれに、Red Hat Enterprise Linux、国産のVine Linux、Slackwareなど好みのものを使ってください。

# 参考資料
- IBM Cloud Code Engine ドキュメント
    - https://cloud.ibm.com/docs/codeengine?topic=codeengine-codeengine-relnotes
- Node-RED ドキュメント
    - 日本語版 https://nodered.jp/docs/
    - 英語版 https://nodered.org/docs/
- DockerHub ドキュメント
    - https://docs.docker.com/docker-hub/