Azure IoT Edge Runtime 1.2 新機能のご紹介 - Nested Edge


# 概要

> `rc3`バージョンが出たので更新しました。

弊社の[WeDXソリューション(IoT Edge Devices Management)](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/1604376875527.wedx-iot-edge-devices-management?tab=Overview)の機能アップグレードしているどころでAzure IoT Edge Runtime v1.2の新機能、Nested Edgeを紹介したいと思います。
現在、Public Previewであるため、GAになる時点で仕様が変わるかもしれないので、ご注意してください。

Private Preview時のブログです。
https://azure.microsoft.com/en-us/updates/iot-edge-nested-edge/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/8c027eb3-5a60-d656-6883-183da62f18ca.png)
上記の図のようにAzure IoT HubサービスではIoT Edgeゲートウェイという、ネットワーク上の他のデバイスとIoT Hub間でコネクションを提供していました。ダウンストリームデバイス(子デバイス)は直接IoT Hubに接続せずにIoT Edgeゲートウェイに介してIoT Hubと通信できる機能でした。
ただ、条件がありダウンストリームデバイスはEdge Deviceを使えないことがありました。

今回バージョンアップすることで下記の図のように変更できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/2436e9d0-f0d3-4e18-b51f-e8d8cfedbac6.png)
Edgeデバイスをダウンストリーデバイスとして使えないことが解除され、使えるようになりました。そしてレイヤー構造で5世代まで接続できるようになりました。これはAzure IoT Edge基盤のコンテナーをよく活用したと思います。

最上位レイヤーのエッジデバイスのみクラウドに接続して他の各レイヤーのダウンストリームデバイス(子デバイス)はクラウドに接続せずに上位レイヤーの中継(プロキシ)により、通信できる仕組みです。
このネットワーク環境は産業用ネットワークであるISA-95に準拠しているらしいので、ネットワークレイアにより分離されている現場で活用できそうです。

検証はレイヤーをフルで確認すると手順が複雑なのでレイヤーを二つ用意して親と子関係で説明していきたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b53f99a7-2028-1a97-d2c6-df6971f3692c.png)

# 前準備

- VM 2台 (ParallelsのVMで2台作成します)
- Azure IoT Hub

# Edge Device 2台 作成

Azure IoT HubにアクセスしてEdge Deviceを2台作成します。

## 親エッジデバイス

「edge-test-parent」名でデフォルトで作成します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/2c82ac07-e927-7e51-3248-f8441dfb8b1c.png" width="60%">

## 子エッジデバイス

「edge-test-child」名のデフォルトで作成しますが、Parent device項目で親エッジデバイスを指定します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/cd5e79d1-0fb8-db85-64ac-42f58d09f7b8.png" width="60%">

# VM作成とOSインストール

Parallelsで共有ネットワークベースでVMを二つ作成します。
[Ubuntu 18.04 LTS](https://releases.ubuntu.com/18.04/)をOSインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/a98b6e25-59eb-581a-22d3-6e2136d19c5b.png)

## 親エッジデバイスと子エッジデバイス

```bash
# 関連パッケージインストール
sudo apt-get update
sudo apt install git curl net-tools

# IP確認
ip addr show | grep inet
```

# 証明書作成とコピー

ゲートウェイとダウンストリームデバイス間はセキュリティで保護された公開キー基盤(PKI)が使用されるので、X.509証明書が必要ですが、テスト向け証明書を作成します。

## 親エッジデバイス

```bash
# IoT Edgeリポジトリクローンと作業用ディレクトリ作成
cd $HOME
mkdir -p WORK && cd WORK
git clone https://github.com/Azure/iotedge.git

# 構成ファイルとスクリプトファイルコピー
cp ./iotedge/tools/CACertificates/*.cnf .
cp ./iotedge/tools/CACertificates/certGen.sh .

# ルートCA証明書作成
./certGen.sh create_root_and_intermediate

# ルートCA証明書確認
ls -l ./certs/azure-iot-test-only.root.ca.cert.pem

# エッジデバイスCA証明書作成
./certGen.sh create_edge_device_ca_certificate "parent-device"
./certGen.sh create_edge_device_ca_certificate "child-device"

# エッジデバイスCA証明書確認
ls -l ./certs/iot-edge-device-*-full-chain.cert.pem
ls -l ./private/iot-edge-device-*.key.pem

# 証明書コピー(親エッジ向け)
cp ./certs/azure-iot-test-only.root.ca.cert.pem $HOME
cp ./certs/iot-edge-device-ca-parent-device-full-chain.cert.pem $HOME
cp ./private/iot-edge-device-ca-parent-device.key.pem $HOME

# 証明書コピー(子エッジ向け)
scp ./certs/azure-iot-test-only.root.ca.cert.pem <子エッジデバイスのユーザー名>@<子エッジデバイスのIP>:/home/<子エッジデバイスのユーザー名>
scp ./certs/iot-edge-device-ca-child-device-full-chain.cert.pem <子エッジデバイスのユーザー名>@<子エッジデバイスのIP>:/home/<子エッジデバイスのユーザー名>
scp ./private/iot-edge-device-ca-child-device.key.pem <子エッジデバイスのユーザー名>@<子エッジデバイスのIP>:/home/<子エッジデバイスのユーザー名>

# ルートCA証明書インストール
sudo cp $HOME/azure-iot-test-only.root.ca.cert.pem /usr/local/share/ca-certificates/azure-iot-test-only.root.ca.cert.pem.crt
sudo update-ca-certificates
```

## 子エッジデバイス

```bash
# ルートCA証明書インストール
sudo cp $HOME/azure-iot-test-only.root.ca.cert.pem /usr/local/share/ca-certificates/azure-iot-test-only.root.ca.cert.pem.crt
sudo update-ca-certificates
```

# IoT Edge Runtimeインストール

## 親エッジデバイスと子エッジデバイス

```bash
# リポジトリ構成をインストール
curl https://packages.microsoft.com/config/ubuntu/18.04/multiarch/prod.list > ./microsoft-prod.list
sudo mv ./microsoft-prod.list /etc/apt/sources.list.d/
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv ./microsoft.gpg /etc/apt/trusted.gpg.d/

# コンテナーエンジンインストール
sudo apt-get update
sudo apt-get install moby-engine

# Azure IoT Runtimeインストール
curl -L https://github.com/Azure/azure-iotedge/releases/download/1.2.0-rc1/libiothsm-std_1.2.0_rc1-1_ubuntu16.04_amd64.deb -o libiothsm-std.deb
sudo dpkg -i ./libiothsm-std.deb
curl -L https://github.com/Azure/azure-iotedge/releases/download/1.2.0-rc1/iotedge_1.2.0_rc1-1_ubuntu16.04_amd64.deb -o iotedge.deb
sudo dpkg -i ./iotedge.deb
```

# IoT Edge Runtime 構成

## 親エッジデバイス

```bash
sudo vi /etc/iotedge/config.yaml
```

「provisioning」のdevice_connection_stringをAzure IoT Hubで作成した親エッジデバイスのConnection Stringで書き換えします。


```yaml
# Manual provisioning configuration using a connection string
provisioning:
  source: "manual"
  device_connection_string: "HostName=********.azure-devices.net;DeviceId=edge-test-parent;SharedAccessKey=***************************"
  dynamic_reprovisioning: false
```

「certificates」の証明書セッションにホームディレクトリにコピーした証明書のパス情報で書き換えます。

```yaml
certificates:
  device_ca_cert: "/home/<親エッジデバイスのユーザー名>/iot-edge-device-ca-parent-device-full-chain.cert.pem"
  device_ca_pk: "/home/<親エッジデバイスのユーザー名>/iot-edge-device-ca-parent-device.key.pem"
  trusted_ca_certs: "/home/<親エッジデバイスのユーザー名>/azure-iot-test-only.root.ca.cert.pem"
```

「hostname」セッションは親エッジデバイスのIPで書き換えます。

```yaml
hostname: "<親エッジデバイスのIP>"
```

「agent」セッションのimage項目をIoT Edge エージェントイメージのPublic Previewバージョンを指定します。

```yaml
agent:
  name: "edgeAgent"
  type: "docker"
  env: {}
  config:
    image: "mcr.microsoft.com/azureiotedge-agent:1.2.0-rc3"
    auth: {}
```

デーモンを再起動します。

```bash
sudo systemctl restart iotedge
```

しばらく待ってから`iotedge list`のコマンドで確認すると`$edgeAgent`のシステムモジュールが動いていることを確認できます。

## 子エッジデバイス

```bash
sudo vi /etc/iotedge/config.yaml
```

「provisioning」のdevice_connection_stringをAzure IoT Hubで作成した子エッジデバイスのConnection Stringで書き換えします。

```yaml
# Manual provisioning configuration using a connection string
provisioning:
  source: "manual"
  device_connection_string: "HostName=********.azure-devices.net;DeviceId=edge-test-child;SharedAccessKey=***************************"
  dynamic_reprovisioning: false
```

「certificates」の証明書セッションにホームディレクトリにコピーした証明書のパス情報で書き換えます。

```yaml
certificates:
  device_ca_cert: "/home/<子エッジデバイスのユーザー名>/iot-edge-device-ca-child-device-full-chain.cert.pem"
  device_ca_pk: "/home/<子エッジデバイスのユーザー名>/iot-edge-device-ca-child-device.key.pem"
  trusted_ca_certs: "/home/<子エッジデバイスのユーザー名>/azure-iot-test-only.root.ca.cert.pem"
```

「hostname」セッションは子エッジデバイスのIPで書き換えます。

```yaml
hostname: "<子エッジデバイスのIP>"
```

「parent_hostname」セッションは親エッジデバイスのIPで書き換えます。

```yaml
parent_hostname: "<親エッジデバイスのIP>"
```

「agent」セッションのimage項目を親エッジデバイスに対してIoT Edge エージェントイメージのPublic Previewバージョンを指定します。

```yaml
agent:
  name: "edgeAgent"
  type: "docker"
  env: {}
  config:
    image: "<親エッジデバイスのIP>:8000/azureiotedge-agent:1.2.0-rc3"
    auth: {}
```

デーモンを再起動します。

```bash
sudo systemctl restart iotedge
```

デーモンを再起動してもまだ何も変わらないです。親デバイスのプロキシモジュールがデプロイされたら動き始めます。

# 親エッジデバイスへモジュールデプロイ

Azureポータル > IoT Hub > Automatic Device Management > IoT Edge > 親エッジデバイス > Set modules 順でクリックします。

## Runtime Settings

Runtime Settingsをクリックします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b03a4d43-b643-31e4-e219-ef9f62e6d2dd.png" width="50%">

Edge Hub の Image項目を下記のイメージで修正します。

```plaintext
mcr.microsoft.com/azureiotedge-hub:1.2.0-rc3
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/fac1cb92-b57f-af03-d57d-b36cd8eb8d47.png" width="80%">

Edge Hub の Environment Variables項目で環境変数二つを追加します。

```plaintext
experimentalFeatures__enabled
experimentalFeatures__nestedEdgeEnabled
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/36821cbb-c32a-775f-95e7-87e1647957c3.png" width="80%">

Edge Agent の Image項目を下記のイメージで修正します。

```plaintext
mcr.microsoft.com/azureiotedge-agent:1.2.0-rc3
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/5b5dd61e-883d-5a19-622d-51d3d0822077.png" width="80%">

Saveします。

## IoT Edge モジュール 追加

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b03a4d43-b643-31e4-e219-ef9f62e6d2dd.png" width="50%">

Add > IoT Edge Module して下記の図のように追加します。
下位レイヤーにあるすべてのIoT Edgeデバイスに代わってコンテナーイメージを取得してキャッシュする役割を担います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/88663e87-22e6-ab7e-423e-960424b91fae.png)

```plaintext
IoT Edge Module Name : registry
Image URI : registry:latest
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/2f4eea66-4699-85a1-8847-65dd7cda5445.png)

```plaintext
REGISTRY_PROXY_REMOTEURL https://mcr.microsoft.com
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/a33cf4e5-c7dc-fcb5-b3f3-598da0b1096f.png)

```json
{
  "HostConfig": {
    "PortBindings": {
      "5000/tcp": [
        {
          "HostPort": "5000"
        }
      ]
    }
  }
}
```

Add > Marketplace Module > IoT Edge API Proxyを追加します。
このモジュールはNginxリバースプロキシを使用し、ネットワークレイヤーを介してHTTPデータをルーティングします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/d55123d5-9f19-bccb-92d8-b60e5b7d585a.png)

「Review + Create」して親エッジデバイスにデプロイします。

デプロイ後、親エッジデバイスでモジュールを確認してみます。

```bash
user@edge-test-parent:~$ iotedge list
NAME             STATUS           DESCRIPTION      CONFIG
IoTEdgeAPIProxy  running          Up a minute      mcr.microsoft.com/azureiotedge-api-proxy:latest
edgeAgent        running          Up 11 minutes    mcr.microsoft.com/azureiotedge-agent:1.2.0-rc3
edgeHub          running          Up a minute      mcr.microsoft.com/azureiotedge-hub:1.2.0-rc3
registry         running          Up a minute      registry:latest
```

子デバイスで`iotedge list`で確認してみると`$edgeAgent`のシステムモジュールが動いていることを確認できます。

# 子エッジデバイスへモジュールデプロイ

Azureポータル > IoT Hub > Automatic Device Management > IoT Edge > 親エッジデバイス > Set modules 順でクリックします。

## Runtime Settings

Runtime Settingsをクリックします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b03a4d43-b643-31e4-e219-ef9f62e6d2dd.png" width="50%">

Edge Hub の Image項目を下記のイメージで修正します。

```plaintext
$upstream:8000/azureiotedge-hub:1.2.0-rc3
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/058d2c04-eade-979b-cc9c-cf269a3377ad.png" width="80%">


Edge Hub の Environment Variables項目で環境変数二つを追加します。

```plaintext
experimentalFeatures__enabled
experimentalFeatures__nestedEdgeEnabled
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/36821cbb-c32a-775f-95e7-87e1647957c3.png" width="80%">

Edge Agent の Image項目を下記のイメージで修正します。

```plaintext
$upstream:8000/azureiotedge-agent:1.2.0-rc3
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/32429667-32f3-5f0b-941a-6288b0f70824.png" width="80%">

Saveします。

## IoT Edge モジュール 追加

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b03a4d43-b643-31e4-e219-ef9f62e6d2dd.png" width="50%">


Add > Marketplace Module > Simulated Temperature Sensorを追加し、イメージURLを変更します。

```plaintext
$upstream:8000/azureiotedge-simulated-temperature-sensor:1.0
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/58c7b512-e95e-e4ac-dc88-95ef7b13c622.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/dee7e94c-cfe4-3b2f-eb9a-e8126bcdc390.png)

「Review + Create」して親エッジデバイスにデプロイします。

デプロイ後、子エッジデバイスでモジュールを確認してみます。

```bash
user@edge-test-child:~$ iotedge list
NAME                        STATUS           DESCRIPTION      CONFIG
SimulatedTemperatureSensor  running          Up 2 hours       10.211.55.2:8000/azureiotedge-simulated-temperature-sensor:1.0
edgeAgent                   running          Up 2 hours       10.211.55.2:8000/azureiotedge-agent:1.2.0-rc3
edgeHub                     running          Up 2 hours       10.211.55.2:8000/azureiotedge-hub:1.2.0-rc3
```

ここまですると、子エッジデバイスのテレメトリ情報が親エッジデバイスを経由してAzure IoT Hubに流れていることが確認できます。

# ネットワーク構造変更

親エッジデバイスの既存NICをホストオンリーネットワーク(Private Network)に変更してブリッジネットワーク(Public Network)向けNICを追加します。
子エッジデバイスの既存NICをホストオンリーネットワーク(Private Network)に変更します。
この構成で子エッジデバイスはPrivate Netowork内で位置しているため、クラウドに接続できない状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/ea35148a-7082-ac2f-b0e3-9005e360c129.png)

# IoT Edge Runtime 構成修正

## 親エッジデバイス

```bash
sudo vi /etc/iotedge/config.yaml
```

「hostname」セッションは親エッジデバイスのIP(PrivateNetwork)で書き換えます。

```yaml
hostname: "<親エッジデバイスのIP>"
```

デーモンを再起動します。

```bash
sudo systemctl restart iotedge
```

## 子エッジデバイス

```bash
sudo vi /etc/iotedge/config.yaml
```

「hostname」セッションは子エッジデバイスのIP(PrivateNetwork)で書き換えます。

```yaml
hostname: "<子エッジデバイスのIP>"
```

「parent_hostname」セッションは親エッジデバイスのIP(PrivateNetwork)で書き換えます。

```yaml
parent_hostname: "<親エッジデバイスのIP>"
```

「agent」セッションのimage項目を親エッジデバイスに対してIoT Edge エージェントイメージのPublic Previewバージョンを指定します。

```yaml
agent:
  name: "edgeAgent"
  type: "docker"
  env: {}
  config:
    image: "<親エッジデバイスのIP(PrivateNetwork)>/azureiotedge-agent:1.2.0-rc3"
    auth: {}
```

デーモンを再起動します。

```bash
sudo systemctl restart iotedge
```

Visual Studio Codeでモニタリングすると子エッジデバイスからのテレメトリ情報が問題なく流れていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/b8f0e7c5-6f46-5ae7-a58e-dcb41e30d683.png)

# 完成図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287622/4457b5c2-eba9-54c4-2597-9195108e6b6b.png)

# まとめ

色々手順を実行しなければいけないですが、セイキュリティーで保護された接続でレイヤー構造でダイレクトメッセージのようなIoT Hubプリミティブとやモジュール管理ができるようになったので、期待している機能です。そして弊社の[WeDXソリューション(IoT Edge Devices Management)](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/1604376875527.wedx-iot-edge-devices-management?tab=Overview)にも簡単に制御できるように機能搭載して更新予定ですので、ご確認いただけますと幸いです。