Node-REDの自作FLOWをAKS上で動かすまで

# アジェンダ

+ ローカルでNode-REDをDockerコンテナで動かす
+ AKSで動かしてみる

# ローカルでNode-REDをDockerコンテナ上で動かす

## 準備

+ 作業端末(以下、ホストサーバとします)は以下のサービスが動くことを前提とします
    + Docker
    + azure-cli
    + kubectl

+ コンテナはオフィシャルのものを使用します
    + Docker Hub
        + https://hub.docker.com/r/nodered/node-red-docker/
    + 上記のソースのGitHub
        + https://github.com/node-red/node-red-docker

+ 参考資料: 公式ドキュメント(日本語)
    + https://nodered.jp/docs/platforms/docker

## ローカルにて構築

+ 基本コマンドライン

```
docker run -d \
           -p 1880:1880 \
           --name mynodered \
           nodered/node-red-docker
```

+ ブラウザで確認
    + http://127.0.0.1:1880

![images_img-01.png](https://qiita-image-store.s3.amazonaws.com/0/25582/60e65b69-dac2-c62a-139b-74abe9463dca.png)




ここまでで、Node-RED( on Docker)の最低限の環境が出来ました
しかし、このままだとコンテナを破棄した時点でFLOWが無くなってしまいます

+  対処方法
    + FLOWをjsonで書き出す
        + 自作したFLOWをノード、フロー、全フローの単位でJSON形式で出力(または入力)することが出来ます
        + 保存したいタイミングで都度生成して、別途コピーしておくことも可能ですが…結構手間です
    + ローカルにマウントする
        + Node-REDのデータ領域をDockerコンテナ外(=ホストサーバ)にマウントします
        + 今回はこちらで行っていきます

## ホストサーバの一部の領域をDocker内にマウントしつつ起動

+ 専用のディレクトリを作成します

```
cd /opt/node-red-aks-sample # お好きなPATHで構いません
mkdir node-red-data
chmod 0777 -R node-red-data
```

+ 作成したディレクトリをマウントしつつ、Dockerを起動します

```
docker run -d \
      -p 1880:1880 \
      -v $(pwd)/node-red-data:/tmp/data \
      --name mynodered \
      nodered/node-red-docker
```

+ 先ほど、起動したDockerコンテナにログインします

```
docker exec -it mynodered /bin/bash
```

+ Node-REDのデータ領域内のデータをホストサーバにマウントしたディレクトリ配下にコピーします


```
cp -arf /data/* /tmp/data/
cp -arf /data/.config.json* /tmp/data/
```

+ 作業が完了したら、ログアウトします

```
exit
```

+ 起動したDockerコンテナをいったん削除します

```
docker rm -f mynodered
```

+ WIP

```
docker run -d \
      -p 1880:1880 \
      -v $(pwd)/node_red_data:/data \
      --name mynodered-v2 \
      nodered/node-red-docker
```

# AKS(Azure Kubernetes Service)で動かしてみる

## 準備

+ 以下のサービスは事前に用意しているものとします
    + AKS(Azure Kubernetes Service)
        + リソースグループ(例)
            + iganari-aks-ws-rsg
        + レジストリネーム(例)
            + iganari-aks-ws 
    + ACR(Azure Container Registry)
        + リソースグループ(例)
            + iganari-acr-nodered-sample
        + レジストリネーム(例)
            + iganarinoderedsample 
    + ※ 上記はこの記事用に作成したリソースのため、適宜書き換えて読んでください


![img-02.png](https://qiita-image-store.s3.amazonaws.com/0/25582/8837bfdd-05a3-7746-624b-1735d43130c3.png)

![img-03.png](https://qiita-image-store.s3.amazonaws.com/0/25582/a7f1a3c8-e1fc-3306-1825-d9eace98d25c.png)



+ また、作業自体は先ほど用意したホストサーバを使用します


## デプロイしたいFLOWをローカルで起動したNode-RED上で作成する

+ ホストサーバ上でNode-RED( on Docker)を動かし、簡単なFLOWを作成します
+ 作成するFLOW
    + メッセージをフォームに入力 -> 入力したメッセージを出力
    + FLOW Source
        + https://gist.github.com/iganari/8759f53b4f982a954d36a786fc01f3fe

## Dockerコンテナをimage化する

+ 先ほどまで作業していたDockerコンテナを確認します

```
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                    NAMES
a85c39cf93d8        nodered/node-red-docker   "npm start -- --user…"   5 minutes ago       Up 5 minutes        0.0.0.0:1880->1880/tcp   mynodered-v2
```

+ Dockerfileを作成します
    + 最低限動くように作成していますので、適宜加筆して下さい

```
$ vim Dockerfile
$ cat Dockerfile
FROM nodered/node-red-docker

COPY node-red-data /data

EXPOSE 1880

# Environment variable holding file path for flows configuration
ENV FLOWS=flows.json
ENV NODE_PATH=/usr/src/node-red/node_modules:/data/node_modules

CMD ["npm", "start", "--", "--userDir", "/data"]
```

+ 作成したDockerfileを元にimageを作成します

```
$ docker build --tag my-nodered-container .
```

+ 作成したimageを確認します

```
$ docker images | grep my-nodered-container
my-nodered-container         latest              07b90f1cc4eb        21 seconds ago      722MB
```

+ 試しにimageから起動してみましょう

```
docker run -d \
      -p 1880:1880 \
      --name my-nodered-container-test \
      my-nodered-container
```

http://127.0.0.1:1880

上記を確認して、何も入っていないまっさらなNode-REDが出れば成功です


![images_img-01.png](https://qiita-image-store.s3.amazonaws.com/0/25582/e1261144-63fb-b2fb-1402-8f1a52a0a526.png)


+ FLOWを作成します
    + 用意しておいたFLOWを使用しましょう
    + FLOW Source
        + https://gist.github.com/iganari/8759f53b4f982a954d36a786fc01f3fe

+ 作成したFLOWを追加のレイヤーとして、imageに追加します
    + 識別のために `v2` というタグを付与します

```
docker stop my-nodered-container-test
docker commit my-nodered-container-test my-nodered-container:v2
```

+ imageを確認します

```
$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
my-nodered-container      v2                  274c9e4ea377        5 seconds ago       722MB
my-nodered-container      latest              4cdec9d035b4        8 minutes ago       722MB
nodered/node-red-docker   latest              0fbeede98949        5 weeks ago         722MB
```

+ ACRに登録出来るように、ACR用のタグも付与します
    + ※ 名前等に関しては、上述しています

```
docker tag my-nodered-container:v2 iganarinoderedsample.azurecr.io/my-nodered-container:v2
```

+ imageを確認します

```
$ docker images
REPOSITORY                                             TAG                 IMAGE ID            CREATED             SIZE
my-nodered-container                                   v2                  274c9e4ea377        35 hours ago        722MB
iganarinoderedsample.azurecr.io/my-nodered-container   v2                  274c9e4ea377        35 hours ago        722MB
my-nodered-container                                   latest              4cdec9d035b4        35 hours ago        722MB
nodered/node-red-docker                                latest              0fbeede98949        5 weeks ago         722MB

```

##  image化したコンテナをACRに登録する

+ ACRにログインします
    + ※ 名前等に関しては、上述しています

```
az acr login --name iganarinoderedsample
```

+ ACR用に作成したタグを指定して、imageをACRに登録します

```
docker push iganarinoderedsample.azurecr.io/my-nodered-container:v2
```

## AKSにて起動する

+ 先ほどのACR上に登録したimageを使用します
    + ※ ACR、AKSの名前等に関しては、上述しています

+ AKSに認証を通します

```
az aks get-credentials --resource-group iganari-aks-ws-rsg --name iganari-aks-ws
```

+ ACRとAKSの間の認証を通します
    + 参考URL
        + https://docs.microsoft.com/ja-jp/azure/container-registry/container-registry-auth-aks

```
AKS_RESOURCE_GROUP=iganari-aks-ws-rsg                                                                                                  
AKS_CLUSTER_NAME=iganari-aks-ws
ACR_RESOURCE_GROUP=iganari-acr-nodered-sample
ACR_NAME=iganarinoderedsample

# Get the id of the service principal configured for AKS
CLIENT_ID=$(az aks show --resource-group $AKS_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query "servicePrincipalProfile.clientId" --output tsv)

# Get the ACR registry resource id
ACR_ID=$(az acr show --name $ACR_NAME --resource-group $ACR_RESOURCE_GROUP --query "id" --output tsv)                                 

# Create role assignment
az role assignment create --assignee $CLIENT_ID --role Reader --scope $ACR_ID  
```

+ Kubernetes用のdeployment用のyamlを作成していきます
    + 設定ファイルの記載内容の説明はこの記事の内容とは離れるため割愛します

```
$ cat deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: my-nodered-container
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: my-nodered-container
    spec:
      containers:
      - name: my-nodered-container
        image: iganarinoderedsample.azurecr.io/my-nodered-container:v2
        ports:
        - containerPort: 1880
```

+ deploymentを作成します

```
kubectl create -f deployment.yaml
```

+ deploymentを確認します

```
$ kubectl get pod
NAME                             READY   STATUS             RESTARTS   AGE
nodered-sample-d98568545-m42pc   0/1     ImagePullBackOff   0          22s
$ kubectl get deployment                                                               
NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE                                                               
my-nodered-container   1         1         1            1           22s
```

+ Kubernetes用のservice用のyamlを作成していきます
    + 設定ファイルの記載内容の説明はこの記事の内容とは離れるため割愛します

```
$ cat service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nodered-container
spec:
  type: LoadBalancer
  ports:
  - port: 1880
  selector:
    app: my-nodered-container
```

+ serviceを作成します

```
kubectl create -f service.yaml
```

+ serviceを確認します
    + `EXTERNAL - IP` が `<pending>` となっているのでしばし待ちましょう :coffee:

```
$ kubectl get pod
NAME                                    READY   STATUS    RESTARTS   AGE
my-nodered-container-6b5bdfd567-jmdmv   1/1     Running   0          11h
$ kubectl get service
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
kubernetes             ClusterIP      10.0.0.1      <none>        443/TCP          3d
my-nodered-container   LoadBalancer   10.0.128.51   <pending>     1880:32329/TCP   26s
```

+ 時間を置いてから、再びserviceを確認してみます
    + 今度は、 `EXTERNAL - IP` が取れました

```
$ kubectl get service
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)          AGE
kubernetes             ClusterIP      10.0.0.1      <none>         443/TCP          3d
my-nodered-container   LoadBalancer   10.0.128.51   13.78.70.122   1880:32329/TCP   3m
```

+ 取得した  `EXTERNAL - IP` の port1880にアクセスしてみます
    + http://13.78.70.122:1880

![img-04.png](https://qiita-image-store.s3.amazonaws.com/0/25582/f8488a81-67db-e110-3c6a-6770e142af4c.png)


## 最後に

+ AKSのLoadBalancerは追加料金が発生するため、検証が終わったら削除しておきましょう :money_with_wings:

```
kubectl delete -f service.yaml
```