Dockerを使いMeshblu のIoTプラットフォームをローカルに構築します。GitHubのリポジトリ にはDockerfile もあるのですが、開発用にnode_modules
のキャッシュとdocker restart
がやりやすいように少し構成を変更します。最後にMosquitto のコンテナを2つ使いPub/Subのテストをしてみます。
プロジェクトの作成 Meshblu のリポジトリからcloneします。
$ cd ~/docker_apps $ git clone https://github.com/octoblu/meshblu meshblu-dev $ cd meshblu-dev
Dockerfile 最初にリポジトリ から修正したDockefileです。upstreamに頻繁に更新が入るのでディレクトリ構成などあまり変更しないようにしました。
~/docker_apps/meshblu-dev/Dockerfile FROM ubuntu:12.04 MAINTAINER Skynet https://skynet.im/ <chris+docker@skynet.im> RUN apt-get update -y --fix-missing RUN apt-get install -y python-software-properties RUN apt-get install -y build-essential RUN apt-get -y install libzmq-dev libavahi-compat-libdnssd-dev RUN add-apt-repository ppa:chris-lea/node.js RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 RUN echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | tee -a /etc/apt/sources.list.d/10gen.list RUN apt-get update -y --fix-missing RUN apt-get -y install redis-server apt-utils supervisor nodejs RUN apt-get -y install -o apt::architecture=amd64 mongodb-10gen RUN sed -i 's/daemonize yes/daemonize no/g' /etc/redis/redis.conf COPY ./package.json /var/www/ RUN mkdir -p /dist/node_modules && \ ln -s /dist/node_modules /var/www/node_modules && \ cd /var/www && npm install RUN mkdir /var/log /skynet EXPOSE 3000 5683 1883 CMD ["/usr/bin/supervisord" , "-n" , "-c" , "/etc/supervisor/supervisord.conf" ] VOLUME /etc/supervisor/conf.d COPY . /var/www
不足パッケージの追加 docker build
中にエラーが出たので、zmq.hとdns_sd.hをインストールします。
~/docker_apps/meshblu-dev/Dockerfile RUN apt-get -y install libzmq-dev libavahi-compat-libdnssd-dev
node_modulesのシムリンク 以下の方針でDockerfileを修正します。
node_modules
のディレクトリを/dist/node_modules
に作成して、アプリケーションディレクトリにはシムリンクを作る
Dockerfileの最後にCOPYコマンドでキャッシュして、コード変更後にnpmインストールが起きないようにする
~/docker_apps/meshblu-dev/Dockerfile COPY ./package.json /var/www/ RUN mkdir -p /dist/node_modules && \ ln -s /dist/node_modules /var/www/node_modules && \ cd /var/www && npm install
./dockerの設定ファイルはCOPYしない
カレントディレクトリのconfig.jsを修正してビルドする
~/docker_apps/meshblu-dev/Dockerfile
supervisor.confのボリューム作成と、最後にカレントディレクトリをイメージにキャッシュする
~/docker_apps/meshblu-dev/Dockerfile CMD ["/usr/bin/supervisord" , "-n" , "-c" , "/etc/supervisor/supervisord.conf" ] VOLUME /etc/supervisor/conf.d COPY . /var/www
MQTTのポート追加 exposeにMQTTの1883ポートを追加します。
~/docker_apps/meshblu-dev/Dockerfile
Dockerホストのシムリンク 前回node_modulesのシムリンクとボリュームを使って効率化 したように、node_modules
コンテナ内でのみ有効なシムリンクをDockerホスト上の作業ディレクトリ内に作成します。
$ ln -s /dist/node_modules ./node_modules
supervisor.conf Supervisorの設定ファイルは以下のように修正しました。
~/docker_apps/meshblu-dev/docker/supervisor.conf [program:redis] command =/usr/bin/redis-server /etc/redis/redis.confnumprocs=1 autostart=true autorestart=true [program:node] command =/usr/bin/node /var/www/server.js --http --coap --mqttnumprocs=1 directory=/var/www/ stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 redirect_stderr=true autostart=true autorestart=true
MQTT Serverを起動する server.js起動時に--mqtt
フラグを追加してMQTTサーバーを起動します。
~/docker_apps/meshblu-dev/docker/supervisor.conf [program:node] command =/usr/bin/node /var/www/server.js --http --coap --mqtt
MongoDBは起動しない 今回はRedisだけ使うのでMongoDBはSupervisorから起動させません。
~/docker_apps/meshblu-dev/docker/supervisor.conf
config.js 今回はリポジトリのディレクトリ構成を残す方針です。アプリケーション用にディレクトリを作成しないので、カレントディレクトリにnode_modules
も配置します。カレントディレクトリごとコンテナの配布ディレクトリにボリュームでアタッチします。docker/config.js.docker
でなく./config.js
を修正します。
~/docker_apps/meshblu-dev/config.js ... module .exports = { port: parseInt (process.env.PORT) || 80 , ... mqtt: { port: parseInt (process.env.MQTT_PORT), skynetPass: process.env.MQTT_PASSWORD },
MongoDBは使わない config.mqtt.databaseUrlはコメントアウトするとMondoDBは使われなくなります。Node.jsのコードを読むとMQTTのバックエンドはRedisが設定されていれば優先して使用されているようです。
~/docker_apps/meshblu-dev/config.js
TLS/SSLは保留 証明書をまだ用意していないのでTLS/SSLの設定はコメントアウトします。
~/docker_apps/meshblu-dev/config.js
MQTT Server用にskynetPassなど設定 システム内部のメッセージングにskynetユーザーを使っています。そのためMQTTサーバー用にskynetPassの指定は必須です。起動メッセージから送信できなくなりサーバーが起動しません。ランダムパスワードを作成して設定します。
irb(main):001:0> o = [('a' ..'z' ), ('A' ..'Z' )].map { |i| i.to_a }.flatten=> ["a" , "b" , "c" , "d" , "e" , "f" , "g" , "h" , "i" , "j" , "k" , "l" , "m" , "n" , "o" , "p" , "q" , "r" , "s" , "t" , "u" , "v" , "w" , "x" , "y" , "z" , "A" , "B" , "C" , "D" , "E" , "F" , "G" , "H" , "I" , "J" , "K" , "L" , "M" , "N" , "O" , "P" , "Q" , "R" , "S" , "T" , "U" , "V" , "W" , "X" , "Y" , "Z" ] irb(main):002:0> (0 ...44 ).map { o[rand(o.length)] }.join=> "ZFiLQKSmFmaDgFVrZyTCVwsBUCUpWhdHhjVBoGxNnHaa"
Dockerイメージのビルドと起動 Dockerイメージをビルドします。
$ docker build -t meshblu-dev .
環境変数にポート番号やskynetPassを指定します。カレントディレクトリやSupervisorの設定ファイルをボリュームでアタッチして起動します。
$ docker run -d --name meshblu-dev \ -p 3000:3000 \ -p 5683:5683 \ -p 1883:1883 \ -e PORT=3000 \ -e MQTT_PORT=1883 \ -e MQTT_PASSWORD=ZFiLQKSmFmaDgFVrZyTCVwsBUCUpWhdHhjVBoGxNnHaa \ -v $PWD :/var/www \ -v $PWD /docker:/etc/supervisor/conf.d \ meshblu-dev
ログを確認します。HTTP(3000), CoAP(5683), MQTT(1883)のサーバーが起動しました。
$ docker logs -f meshblu-dev ... MM MM hh bb lll MMM MMM eee sss hh bb lll uu uu MM MM MM ee e s hhhhhh bbbbbb lll uu uu MM MM eeeee sss hh hh bb bb lll uu uu MM MM eeeee s hh hh bbbbbb lll uuuu u sss Meshblu (formerly skynet.im) development environment loaded... Starting CoAP... done . Starting HTTP/HTTPS... done . ... Starting MQTT... done . CoAP listening at coap://localhost:5683 HTTP listening at http://0.0.0.0:3000 ... MQTT listening at mqtt://0.0.0.0:1883 ...
Node.jsファイルなどを修正したら、docker restartで反映ができます
$ docker restart meshblu-dev
デバイス/ノードの登録と簡単なPub/Sub REST APIからMeshbluのステータスを確認します。
$ curl http://localhost:3000/status {"meshblu" :"online" }
コネクテッドデバイスを用意する前に、ノードとしてDockerコンテナを使いテストします。Mosquitto のPublishとSubscribe用の2つのコンテナを起動します。
MQTTノードの登録 REST APIを使ってノードの登録をします。ボディには任意のkey/valueを登録できます。まずmqtt-pub
コンテナの登録をします。戻り値のJSONからuuid
とtoken
を取得できます。今後このデバイス/ノードとメッセージをやりとりする場合にはこのuuid
とtoken
が必要になります。
$ curl -X POST -d "name=mqtt-pub&type=container" "http://localhost:3000/devices" {"uuid" :"1f3bcae0-cc5b-11e4-80ac-dbe1b8e07fba" ,"online" :false ,"timestamp" :"2015-03-17T04:07:26.621Z" ,"name" :"mqtt-pub" ,"type" :"container" ,"ipAddress" :"172.17.42.1" ,"token" :"1fc767197e275a3cb79befbf38632e5c7ef8fbec" }
mqtt-sub
コンテナも同様に登録します。
$ curl -X POST -d "name=mqtt-sub&type=container" "http://localhost:3000/devices" {"uuid" :"38cc2270-cc5b-11e4-80ac-dbe1b8e07fba" ,"online" :false ,"timestamp" :"2015-03-17T04:08:09.496Z" ,"name" :"mqtt-sub" ,"type" :"container" ,"ipAddress" :"172.17.42.1" ,"token" :"86efacbbeb28dd56676df710cf4c1e7d2f05f762" }
登録したデバイスを確認します。HTTPヘッダにはuuid/token
の認証情報が必要です。今回はmqtt-sub
のuuidを認証に使いましたが、mqtt-pub
のデバイス情報も取得できています。uuidにはオーナーを指定すると所有しているデバイスは操作できるようになるのですが、この辺りは調査中です。プライベートなブローカーなので今のところすべてのデバイスが見えても問題ありません。
$ curl "http://localhost:3000/devices" \ --header "meshblu_auth_uuid: 38cc2270-cc5b-11e4-80ac-dbe1b8e07fba" \ --header "meshblu_auth_token: 86efacbbeb28dd56676df710cf4c1e7d2f05f762" {"devices" :[{"uuid" :"38cc2270-cc5b-11e4-80ac-dbe1b8e07fba" ,"online" :false ,"timestamp" :"2015-03-17T04:08:09.496Z" ,"name" :"mqtt-sub" ,"type" :"container" ,"ipAddress" :"172.17.42.1" },{"uuid" :"1f3bcae0-cc5b-11e4-80ac-dbe1b8e07fba" ,"online" :false ,"timestamp" :"2015-03-17T04:07:26.621Z" ,"name" :"mqtt-pub" ,"type" :"container" ,"ipAddress" :"172.17.42.1" }]}
Mosquittoコンテナを起動 Mosquittoのコンテナを2つ起動します。Dockerイメージはansi/mosquitto をpullして使います。1つ目はmqtt-pub
コンテナです。
$ docker pull ansi/mosquitto $ docker run --rm --name mqtt-pub -it ansi/mosquitto /bin/bash root@b0468fe6e67e:/usr/local /src/mosquitto-1.3
別のシェルを開き、もう一つmqtt-sub
コンテナを起動します。
$ docker run --rm --name mqtt-sub -it ansi/mosquitto /bin/bash root@12b53215b59a:/usr/local /src/mosquitto-1.3
このコンテナは/etc/ld.so.cache
が更新されていないようなのでmosquitto_sub
コマンドを実行するとエラーになります。
mosquitto_sub: error while loading shared libraries: libmosquitto.so.1: cannot open shared object file: No such file or directory
ldconfigを実行して共有ライブラリを変更します。
Subscribe (topicはデバイスのuuid) Publish側コンテナ(mqtt-sub
)でmosquitto_sub
コマンドを実行します。任意のtopicをsubscribeするのではなく、デバイスのuuidのtopicを指定することに注意します。
host: MeshbluのポートをマップしているDockerホスト
port: MQTT Serverのポート
topic: subscribeするデバイスのuuid
username: subscribeするデバイスのuuid
password: subscribeするデバイスのtoken
$ MQTT_SUB_UUID=38cc2270-cc5b-11e4-80ac-dbe1b8e07fba $ MQTT_SUB_PASS=86efacbbeb28dd56676df710cf4c1e7d2f05f762 $ mosquitto_sub -h 10.3.0.230 -p 1883 \ -t $MQTT_SUB_UUID \ -u $MQTT_SUB_UUID \ -P $MQTT_SUB_PASS \ -d Client mosqsub/17-12b53215b59a sending CONNECT Client mosqsub/17-12b53215b59a received CONNACK Client mosqsub/17-12b53215b59a sending SUBSCRIBE (Mid: 1, Topic: 38cc2270-cc5b-11e4-80ac-dbe1b8e07fba, QoS: 0) Client mosqsub/17-12b53215b59a received SUBACK Subscribed (mid: 1): 0 Client mosqsub/17-12b53215b59a sending PINGREQ Client mosqsub/17-12b53215b59a received PINGRESP
Publish (topicはmessage) Publish側コンテナ(mqtt-pub
)でmosquitto_pub
コマンドを実行します。メッセージを送信する場合のtopicはmessage
で固定になっています。
host: MeshbluのポートをマップしているDockerホスト
port: MQTT Serverのポート
topic: message
で固定
username: publishするデバイスのuuid
password: publishするデバイスのtoken
message: JSON形式のメッセージ、{“devices”: [“xxx”,”yyy”], “payload”: {“aaa”:”bbb”}}
publishするメッセージはJSON形式でフォーマットが決まっています。devices
キーには送信先デバイスのuuidを指定します。Arrayで複数指定も可能です。payload
もキーが固定です。これ以外ではメッセージが送信ができないので注意が必要です。
$ MQTT_SUB_UUID=38cc2270-cc5b-11e4-80ac-dbe1b8e07fba $ MQTT_PUB_UUID=1f3bcae0-cc5b-11e4-80ac-dbe1b8e07fba $ MQTT_PUB_PASS=1fc767197e275a3cb79befbf38632e5c7ef8fbec $ mosquitto_pub -h 10.3.0.230 -p 1883 \ -t message \ -m '{"devices": "38cc2270-cc5b-11e4-80ac-dbe1b8e07fba", "payload": {"red":"on"}}' \ -u 1f3bcae0-cc5b-11e4-80ac-dbe1b8e07fba \ -P 1fc767197e275a3cb79befbf38632e5c7ef8fbec \ -d Client mosqpub/19-b0468fe6e67e sending CONNECT Client mosqpub/19-b0468fe6e67e received CONNACK Client mosqpub/19-b0468fe6e67e sending PUBLISH (d0, q0, r0, m1, 'message' , ... (76 bytes)) Client mosqpub/19-b0468fe6e67e sending DISCONNECT
Subscribe側コンテナでメッセージを受信 Subscribe側コンテナ(mqtt-sub
)にメッセージが届き標準出力します。デバイスのuuidをtopicにしてsubscribeしているのですが、message
のtopicにpublishしています。ちょっとわかりずらいですがマルチプロトコルに対応するためこういった仕様になっているようです。
... Client mosqsub/17-12b53215b59a received PUBLISH (d0, q0, r0, m0, '38cc2270-cc5b-11e4-80ac-dbe1b8e07fba' , ... (150 bytes)) {"topic" :"message" ,"data" :{"devices" :"38cc2270-cc5b-11e4-80ac-dbe1b8e07fba" ,"payload" :{"red" :"on" },"fromUuid" :"1f3bcae0-cc5b-11e4-80ac-dbe1b8e07fba" }}