LoginSignup
1

More than 1 year has passed since last update.

SORACOM ArcとDockerで作る仮想デバイス

Posted at

はじめに

どうも、SORACOM研究家の1stshipです。今回はSORACOM Arcを使った新しい形のIoTデバイスを考えましたので、これについて書きたいと思います。ざっくりした構成図はこんな感じです。

スクリーンショット 2021-07-04 20.57.49.png

さて、IoT開発においてハードウェアの調達というのはなかなか大変なです。

対応するデバイスがないとクラウドにデータが送れない、となると、クラウド側の開発が滞ってしまいますし、開発が立て込んでくるとデバイスの数が足りなくなる、ということもあるのではないでしょうか?とはいえ、そのためにデバイスをたくさん作る、というのは予算やスケジュール上無理、ということもあるでしょう。そういう場合、デバイスっぽく動くソフトウェア、つまり仮想デバイスが欲しい、と思いませんか?

実際にそういう仮想デバイスを作って進めることもあるでしょう。ここで、SORACOMを使ったIoTサービスの場合、デバイスからの呼び出しは(ほぼ極限まで)シンプルなSORACOMエンドポイントの呼び出しと、そこから先のクラウドの呼び出しを分けることができます。そのため、デバイスからの呼び出しをシンプルにしつつ、多様なクラウドに対応させることができます。

ですが、仮想デバイスを作るとなると、普通はSORACOMエンドポイントを使えません(開発PCをSORACOM Airで通信させればいけるがそれはそれで大変)ので、クラウド呼び出しをそのために作らないといけないんですが、これが面倒なんですよね。また、そうやって作ったものがSORACOMからの呼び出しとどのくらい一致させられるか、というのは仮想デバイスを作る人の理解・実装レベルによります。

ということでどうしようかな〜、と思っていたところにSORACOM Arcが出ました。これを使えば普通のインターネット環境からSORACOMエンドポイントが使用できることとなりますので、開発機や、クラウドの開発、CI環境からデバイスと同じように通信することができます。また、仮想SIMは物理SIMと違って、ハードウェア料金がかからない上に調達も数秒です。(初期費用55円(税込))

さらにDockerなどの仮想環境と組み合わせるとより便利、ということが最近の研究でわかってきました。

開発機をSORACOM Airの回線に接続する、またはOSの上で直接SORACOM Arcの接続をすると、そのマシン内では同じSIMとしてSORACOMエンドポイントを共有することになります。これでは複数の仮想デバイスを1つのマシン内で動かすことはできませんね。

一方、Dockerでは生成するコンテナごとにネットワークが区切られています。従ってコンテナ内で作ったSORACOM Arcの接続はコンテナごとに分離されており、他のコンテナからは利用されません。そのため、1つのマシン内に複数のSORACOM Arcに繋がった仮想デバイスをいくらでも作って動作させることができる、というわけです。

では実際に仮想デバイスとして、SORACOM LTE-M Button for Enterpriseっぽいものと、GPSマルチユニットSORACOM Editionっぽいものを作ってみましょう。

事前準備1: Dockerのインストール

まず各環境ごとにDockerを動作させる環境を作りましょう。私はmacOS Big Sur(Apple M1)のDocker Desktop for Macにて準備しましたが、LinuxやWindowsなどで開発されている場合は、それぞれの環境での準備が必要となります。

インストール方法はDockerの公式ドキュメントを参照ください。(ひとまず日本語版の資料を出しますが、正確でない場合は英語資料をご参照ください)

Windows
https://docs.docker.jp/docker-for-windows/install.html#

macOS
https://docs.docker.jp/docker-for-mac/install.html

Linux
https://docs.docker.jp/engine/installation/linux/index.html

インストールとDockerの起動ができていることは以下のようにdocker versionコマンドで確認できます。サーバー側が見えない場合は起動されていないと思われるので、起動してから再度実行してみましょう。

1stship@MacBookAir ~ % docker version                                           
Client:
 Cloud integration: 1.0.17
 Version:           20.10.7
 API version:       1.41
 Go version:        go1.16.4
 Git commit:        f0df350
 Built:             Wed Jun  2 11:56:23 2021
 OS/Arch:           darwin/arm64
 Context:           desktop-linux
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       b0f5bc3
  Built:            Wed Jun  2 11:55:36 2021
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.4.6
  GitCommit:        d71fcd7d8303cbf684402823e425e9dd2e99285d
 runc:
  Version:          1.0.0-rc95
  GitCommit:        b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Apple M1チップだとClientがdarwin/arm64、Serverがlinux/arm64になるようですね。このアーキテクチャに合わせてコンテナ内のバイナリ(今回の場合はsoratun)を用意する必要があるところには注意が必要です。

事前準備2: SORACOM Arc接続ファイルの作成

今回はSORACOM Arcとの接続にはsoratunという公式のエージェントを用います。SORACOM Arcの接続をするのには一番お手軽です。

このsoratunではbootstrapにて接続設定を生成し、upにて指定した設定で接続する、という動作をします。コンテナ内でbootstrapしても良いのですが、下手に作るとコンテナ起動するたびに料金がかかることになりますので、今回はbootstrapはホストOSで実施し、生成された設定ファイルを使って接続することにしましょう。

bootstrapについては、ソラコムの公式ドキュメントをご参照ください。
https://users.soracom.io/ja-jp/docs/arc/bootstrap-authkey/

soratunをダウンロードして実行すると以下のようになります。ここで生成されたarc.jsonを利用します。

1stship@MacBookAir ~ % ./soratun bootstrap authkey
Not enough information to bootstrap. Launching wizard.
SORACOM API auth key ID (starts with "keyId-"): **************************************
SORACOM API auth key (starts with "secret-"): ***********************************************************************
✔ Japan coverage (api.soracom.io)
Virtual subscriber SIM ID: 890000XXXXXXXXXXXXX
Created/updated configuration file: /Users/1stship/soratun_1.0.0_darwin_arm64/arc.json

事前準備はこれで完了です。

SORACOM LTE-M Button for Enterprise仮想デバイスの作成

下図の構成で作成します。

スクリーンショット 2021-07-04 21.04.12.png

  • Dockerイメージの作成
  • SORACOMのSIMグループ設定
  • Dockerコンテナ(仮想デバイス)の起動

の順に作っていきましょう。

Dockerイメージの作成

Dockerfileを使って、ベースイメージにsoratunとボタン通信プログラムを入れて、ボタン仮想デバイスイメージを作ります。

Dockerイメージのビルドディレクトリは以下の構成とします。

.
├── Dockerfile
├── soratun
├── button.sh
└── arc.json

それぞれ簡単に説明します。

  • Dockerfile: Dockerイメージのビルド方法を記述したファイル
  • soratun: SORACOM Arcの接続エージェント。CPUアーキテクチャを合わせる必要あり(私の環境だとlinux_arm64。ラズパイだとlinux_armv7、 一般的なPC、クラウドだとlinux_amd64)
  • button.sh: ボタンの動作をするシェルスクリプト。soratunを立ち上げた後、netcatでボタンのデータを送信する
  • arc.json: SORACOM Arcの接続設定。ただしこれは起動時に指定するためイメージには含めない

Dockerfileは以下のようにします。soratunはUbuntu: 20.04にてサポートされているため、ベースのイメージはubuntu:20.04とします。(Dockerイメージでサポートするとは言われてないことに注意)

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y netcat
COPY soratun /usr/local/bin/soratun
COPY button.sh /usr/local/bin/button.sh
CMD ["/usr/local/bin/button.sh"]

button.shは以下のようにします。

#!/bin/bash

mkdir /dev/net
mknod /dev/net/tun c 10 200
chmod 666 /dev/net/tun
/usr/local/bin/soratun up --config /etc/arc.json &
sleep 3

echo -n -e "\x4d\x01\x03\x51" | nc -w 3 -u unified.soracom.io 23080

soratunで接続して、データをUnified EndpointにUDPで投げるだけの簡単なスクリプトです。
soratunで接続する際には、/dev/net/tunがOS上に存在することが条件となっているため、準備として作成しています。(コンテナ作成時に作っておいてもダメで、実行時に作成する必要があるようです。)

ボタンはUDPでデータを送信するため、同様にnetcatでUDPにてデータを送信します。
ボタンのフォーマットは調べても見つけられませんでしたが、簡単に調べたところ以下のようなものでした。

1byte目: 固定値(4d)
2byte目: クリックタイプ(1:SINGLE、2:DOUBLE、3:LONG)
3byte目: 電池残量(0: 0.25、1: 0.5、2: 0.75、3: 1)
4byte目: 1〜3byteのチェックサム

従ってSINGLEで電池残量1の場合は0x4d010351となりますので、これを送信します。今回は簡単にするために固定ですが、送りたいパターンに応じてデータを変えられるようにスクリプトを作ってもいいでしょう。

イメージとしてビルドします。

1stship@MacBookAir button % docker build -t button .
[+] Building 10.3s (9/9) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 220B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:20.04
 => [internal] load build context
 => => transferring context: 7.34MB
 => [1/4] FROM docker.io/library/ubuntu:20.04
 => [2/4] RUN apt-get update && apt-get install -y netcat
 => [3/4] COPY soratun /usr/local/bin/soratun
 => [4/4] COPY button.sh /usr/local/bin/button.sh
 => exporting to image
 => => exporting layers
 => => writing image sha256:2cbc57980627403ddbf9b102d465c1597834345da030f7b95e8f05f6abab907f
 => => naming to docker.io/library/button

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

これで仮想ボタンコンテナができました。
次にボタンを使うためのSORACOMグループ設定をします。

SORACOMのSIMグループ設定

物理ボタンの設定手順(ガジェット管理→LTE-M Button for Enterprise)ではできませんが、普通のSIMと同じように設定することができます。

SORACOM Consoleにログインして、対象の仮想SIMで検索します。(arc.jsonのsimIdで検索します)

スクリーンショット 2021-07-04 15.34.31.png

SIMグループを作成します。
スクリーンショット 2021-07-04 15.36.23.png

スクリーンショット 2021-07-04 15.38.09.png

スクリーンショット 2021-07-04 15.44.13.png

スクリーンショット 2021-07-04 15.45.34.png

SIMグループの設定をします。
スクリーンショット 2021-07-04 15.46.53.png

スクリーンショット 2021-07-04 15.48.45.png

スクリーンショット 2021-07-04 15.49.41.png

スクリーンショット 2021-07-04 15.51.08.png

ひとまずHarvestにデータがアップロードされることを確認しましょう。この設定をすると5円/日かかりますので、ご注意ください。

では仮想ボタンを動作させて結果を確認してみます。

Dockerコンテナ(仮想デバイス)の起動

起動するためのコマンドは以下のようになります。(Dockerイメージビルドのディレクトリ内で作業しています)

docker run --rm --cap-add NET_ADMIN -v "$(pwd)/arc.json":/etc/arc.json button

起動オプションの説明をします。

--rm: 起動後にコンテナを削除するオプション。起動ごとにコンテナが増えていくのを防止するために入れています

--cap-add: LinuxにはCapabilityという、特権を分類して必要な特権のみを与える仕組みがあります。Dockerコンテナ内部では、rootで実行されていても、必要なCapabilityが与えられていなければ実行エラーになります。SORACOM Arcのトンネルを作成するためにはNET_ADMINというCapabilityが必要なので、それを与えるオプションです。なお、全ての特権を与える--privilegedというオプションもありますが、コンテナ内でなんでもできてしまうのは問題が大きいので、できるだけ使わないようにしたいところです。(NET_ADMINでもまだ広いとは思いますが、今のところこれ以上細かく権限付与する方法が見つからなかった)

-v: コンテナの中にarc.jsonを入れてしまうと、そのコンテナを作った人の接続情報で固定されてしまい、複数の接続での起動ができません。そのため、ホストOSのファイルをコンテナに共有して使わせる方法をとります。-vの前半はホストOSでのファイル位置、後半はコンテナ内の位置です。

1stship@MacBookAir button % docker run --rm --cap-add NET_ADMIN -v "$(pwd)/arc.json":/etc/arc.json button
DEBUG: (soratun0) 2021/07/04 07:03:53 device started
...中略
DEBUG: (soratun0) 2021/07/04 07:03:53 assign IP address: 10.171.40.94
DEBUG: (soratun0) 2021/07/04 07:03:53 set link up: soratun0
DEBUG: (soratun0) 2021/07/04 07:03:53 add route: 100.127.0.0/16
DEBUG: (soratun0) 2021/07/04 07:03:53 Interface up requested
DEBUG: (soratun0) 2021/07/04 07:03:53 peer(QIuO…qBh4) - Received handshake response
201

soratunが起動してArcに接続され、そのあとunifiedエンドポイントを通じてHarvest Dataにデータがアップロードされて201(Created)応答が返ってきていることがわかりますね。netcatが応答を3秒待ち終わったら終了します。

データが上がっているか、Harvestのコンソールにて確認しましょう。
スクリーンショット 2021-07-04 16.07.27.png

スクリーンショット 2021-07-04 16.09.36.png

物理ボタンと同じようにデータがアップロードされていることがわかりますね。Unified Endpointを使っているため、Harvestだけではなく、FunkやFunnelを使ってクラウド連携できます。この仮想デバイスを使って開発を進めることができ、作成したSIMグループやクラウドの構成は実際のボタンにそのまま使うことができます。

GPSマルチユニットSORACOM Edition仮想デバイスの作成

ほぼ同様にGPSマルチユニットの仮想デバイスを作成できます。
こちらは一回通信して終わりではなく、定期的にデータをアップロードし続ける、という動作になります。

下図のような構成で作成します。
スクリーンショット 2021-07-04 21.12.43.png
1台のPCの中に2つの分離されたVPNができており、仮想SIMのIMSIで識別されて届く、というのがポイントです。またVPNはコンテナ内からしかアクセスできず、開発PCや他のコンテナには影響を及ぼしません。

Dockerイメージの作成

Dockerイメージのビルドディレクトリは以下の構成とします。

.
├── Dockerfile
├── soratun
├── gps_multi_unit.sh
├── measurement.pl
├── arc1.json
└── arc2.json

センサー値を適当に作るのにシェルスクリプトだと小数を扱うのが若干面倒だったので、その部分はPerlを使いました。Ubuntu20.04のコンテナイメージに入っていたためです。使えるプログラミング言語を入れたり、Go言語などで作ったバイナリを入れるなどの対処でもよいでしょう。
2台同時にアクセスできることを確認するため、別々の仮想SIMに結びつくarc.jsonを2つ用意しておきます。

Dockerfileは以下のようにします。

FROM ubuntu:20.04
RUN apt-get update && apt-get install -y netcat
COPY soratun /usr/local/bin/soratun
COPY gps_multi_unit.sh /usr/local/bin/gps_multi_unit.sh
COPY measurement.pl /usr/local/bin/measurement.pl
CMD ["/usr/local/bin/gps_multi_unit.sh"]

gps_multi_unit.shは以下のようにします。

#!/bin/bash

mkdir /dev/net
mknod /dev/net/tun c 10 200
chmod 666 /dev/net/tun
/usr/local/bin/soratun up --config /etc/arc.json &
sleep 3

while true
  do /usr/bin/perl /usr/local/bin/measurement.pl | nc -w 3 -u unified.soracom.io 23080
  sleep 7
done

継続して動作させるため、whileループに入れてスリープをさせています。ncのタイムアウト3秒とsleepの7秒を合わせて、10秒ごとにデータ送信されるようにしました。

measurement.plは以下のようにします。

#!/usr/bin/perl

my $lat, $lon, $bat, $rs, $temp, $humi, $x, $y, $z, $type;
$lat = 35.6809591 + rand(1) - 0.5;
$lon = 139.7673068 + rand(1) - 0.5;
$bat = 3;
$rs = 4;
$temp = 23.0 + rand(5) - 2.5;
$humi = 50.0 + rand(10) - 5;
$x = 0.0 + 64 * (int(rand(10)) - 5);
$y = 0.0 + 64 * (int(rand(10)) - 5);
$z = -960 + 64 * (int(rand(10)) - 5);
$type = 0;
printf("{\"lat\":%.7f,\"lon\":%.7f,\"bat\":$bat,\"rs\":$rs,\"temp\":%.2f,\"humi\":%.2f,\"x\":%.1f,\"y\":%.1f,\"z\":%.1f,\"type\":$type}", $lat, $lon, $temp, $humi, $x, $y, $z);

適当なランダム値を出力しています。GPSの中心位置は東京駅です。
センサーやGPSなどが使える場合は実際のデータを引っ張ってきても良いです。

同じようにイメージとしてビルドします。

1stship@MacBookAir gps % docker build -t gps_multi_unit .
[+] Building 0.1s (10/10) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 292B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/ubuntu:20.04
 => [1/5] FROM docker.io/library/ubuntu:20.04
 => [internal] load build context
 => => transferring context: 895B
 => CACHED [2/5] RUN apt-get update && apt-get install -y netcat
 => CACHED [3/5] COPY soratun /usr/local/bin/soratun
 => [4/5] COPY gps_multi_unit.sh /usr/local/bin/gps_multi_unit.sh
 => [5/5] COPY measurement.pl /usr/local/bin/measurement.pl
 => exporting to image
 => => exporting layers
 => => writing image sha256:8b2e5c4853619e1c0b8e5de4b770ec5ee568bb949ebd955cc6345ff92a0247c6
 => => naming to docker.io/library/gps_multi_unit

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

イメージの作成はこれで完了です。

SORACOMのSIMグループ設定

SIMグループの設定は先ほど作成したグループで、バイナリパーサーをオフにしたものを作成します。
SIMグループのコピー機能がありますので、それを使うといい感じです。

スクリーンショット 2021-07-04 19.26.45.png

スクリーンショット 2021-07-04 19.27.34.png

スクリーンショット 2021-07-04 19.31.22.png

スクリーンショット 2021-07-04 19.31.03.png

スクリーンショット 2021-07-04 19.33.03.png

作成した仮想SIMをSIMグループに所属させます。

スクリーンショット 2021-07-04 19.35.14.png

スクリーンショット 2021-07-04 19.37.46.png

SIMグループ設定はこれで終わりです。

Dockerコンテナ(仮想デバイス)の起動

それでは2台分起動しましょう。

docker run -d --name gps1 --rm --cap-add NET_ADMIN -v "$(pwd)/arc1.json":/etc/arc.json gps_multi_unit
docker run -d --name gps2 --rm --cap-add NET_ADMIN -v "$(pwd)/arc2.json":/etc/arc.json gps_multi_unit

ボタンの時から追加したオプションを説明します。

-d: バックグラウンド処理。複数同時に動作させるためにバックグラウンドにしています。ちなみに-dはdaemonではなくdetachのdです。
--name: 名前をつけます。必須ではないですが、-dで動かすと止める時に明示的にコンテナを指定しないといけないので、名前がついていると便利です。

また、-vで指定するarc.jsonがそれぞれ違うことに注意しましょう。あとは同じです。

しばらく待ってからHarvest Dataのコンソールを見てみましょう。

スクリーンショット 2021-07-04 20.17.40.png
スクリーンショット 2021-07-04 20.18.11.png

ちゃんと2台分のデータがアップロードされています。
複数デバイスを同時に、かつ別々のSIMに紐づいた形でアップロードできることが確認できました。これはSORACOM Arcがないとできない構成ですね。

ちなみにGPSマルチユニットをよく知っている人は、設定をメタデータ経由で取得するのはどこいった?と思うかもしれません。GPSマルチユニットの設定のフォーマットが不明であるためこのコンテナには入れませんでしたが、もちろんSORACOM Airメタデータサービスに設定を入れて取得する、という構成は仮想デバイスにおいても有効です。興味があれば試してみるとよいでしょう。

おわりに

冒頭の図を再掲します。
スクリーンショット 2021-07-04 20.57.49.png

この図で示したかったことはいくつかあります。

  • 同じデバイス内でもコンテナを使うことによって複数のSORACOM Arc接続を作ることができ、それぞれ独立した仮想デバイスとしてSORACOMにアクセスでき、仮想SIMのSIM IDやIMSIで識別することができる。
  • コンテナ間は独立しているのでコンテナ開発者は他のコンテナに与える影響を考えなくてよく、コンテナ開発の負担が格段に下げられる。(物理インタフェースは干渉するのでその点は考慮が必要)
  • SORACOMに到達してからの動作は仮想SIMによって決まる。仮想SIMはコンテナに渡す接続設定で決まるので、たとえば送信データフォーマットを公開してエンドユーザーに自由に使わせることも、サービス事業者が接続設定を渡してSaaSとして利用させることもできる
  • データアップロードだけでなく、コンテナ内の設定プロセスにBeam経由でコマンドを送信したり、Napter経由でアクセスするなどして、コンテナ内部を外部から設定させることもできる。

といったように、このコンテナは仮想デバイスとして、ほぼ物理デバイスと変わらない動きができます。もちろんセンサーなどにアクセスするためには、コンテナと物理インタフェースを共有して使わせる、などが必要になるため、完全に同じではないのですが、1つの物理デバイスの中に複数の仮想デバイスを動作させる新しいアーキテクチャが構築できるようになったと言っても良いでしょう。

仮想環境を構築するDockerと、仮想SIMを使ってSORACOMにアクセスするSORACOM Arcの相性が非常に良いのですね。動作環境をAgnosticにするDocker、Connectivity Agnosticを実現するSORACOM Arc、Cloud Agnosticの入口となるSORACOMエンドポイントとの組み合わせといっても良いです。

SORACOM Arcはまだまだいろんな可能性がありそうですね。引き続き研究を進めていきます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1