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に頻繁に更新が入るのでディレクトリ構成などあまり変更しないようにしました。
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
#ADD . /var/www
#RUN cd /var/www && npm install
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
#ADD ./docker/config.js.docker /var/www/config.js
#ADD ./docker/supervisor.conf /etc/supervisor/conf.d/supervisor.conf
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をインストールします。
RUN apt-get -y install libzmq-dev libavahi-compat-libdnssd-dev
node_modulesのシムリンク
以下の方針でDockerfileを修正します。
-
node_modules
のディレクトリを/dist/node_modules
に作成して、アプリケーションディレクトリにはシムリンクを作る - Dockerfileの最後にCOPYコマンドでキャッシュして、コード変更後にnpmインストールが起きないようにする
#ADD . /var/www
#RUN cd /var/www && npm install
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を修正してビルドする
#ADD ./docker/config.js.docker /var/www/config.js
#ADD ./docker/supervisor.conf /etc/supervisor/conf.d/supervisor.conf
- supervisor.confのボリューム作成と、最後にカレントディレクトリをイメージにキャッシュする
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
VOLUME /etc/supervisor/conf.d
COPY . /var/www
MQTTのポート追加
exposeにMQTTの1883ポートを追加します。
EXPOSE 3000 5683 1883
Dockerホストのシムリンク
前回node_modulesのシムリンクとボリュームを使って効率化したように、node_modules
コンテナ内でのみ有効なシムリンクをDockerホスト上の作業ディレクトリ内に作成します。
$ ln -s /dist/node_modules ./node_modules
supervisor.conf
Supervisorの設定ファイルは以下のように修正しました。
[program:redis]
command=/usr/bin/redis-server /etc/redis/redis.conf
numprocs=1
autostart=true
autorestart=true
#[program:mongodb]
#command=/usr/bin/mongod --config /etc/mongodb.conf
#numprocs=1
#autostart=true
#autorestart=true
[program:node]
#command=/usr/bin/node /var/www/server.js --http --coap
command=/usr/bin/node /var/www/server.js --http --coap --mqtt
numprocs=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サーバーを起動します。
[program:node]
#command=/usr/bin/node /var/www/server.js --http --coap
command=/usr/bin/node /var/www/server.js --http --coap --mqtt
MongoDBは起動しない
今回はRedisだけ使うのでMongoDBはSupervisorから起動させません。
#[program:mongodb]
#command=/usr/bin/mongod --config /etc/mongodb.conf
#numprocs=1
#autostart=true
#autorestart=true
config.js
今回はリポジトリのディレクトリ構成を残す方針です。アプリケーション用にディレクトリを作成しないので、カレントディレクトリにnode_modules
も配置します。カレントディレクトリごとコンテナの配布ディレクトリにボリュームでアタッチします。docker/config.js.docker
でなく./config.js
を修正します。
...
module.exports = {
/*
mongo: {
databaseUrl: process.env.MONGODB_URI
},
*/
port: parseInt(process.env.PORT) || 80,
/*
tls: {
sslPort: parseInt(process.env.SSL_PORT) || 443,
cert: process.env.SSL_CERT,
key: process.env.SSL_KEY
},
*/
...
mqtt: {
//databaseUrl: process.env.MQTT_DATABASE_URI,
port: parseInt(process.env.MQTT_PORT),
skynetPass: process.env.MQTT_PASSWORD
},
MongoDBは使わない
config.mqtt.databaseUrlはコメントアウトするとMondoDBは使われなくなります。Node.jsのコードを読むとMQTTのバックエンドはRedisが設定されていれば優先して使用されているようです。
module.exports = {
/*
mongo: {
databaseUrl: "mongodb://localhost:27017/skynet",
},
*/
TLS/SSLは保留
証明書をまだ用意していないのでTLS/SSLの設定はコメントアウトします。
/*
tls: {
sslPort: parseInt(process.env.SSL_PORT) || 443,
cert: process.env.SSL_CERT,
key: process.env.SSL_KEY
},
*/
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を実行して共有ライブラリを変更します。
$ ldconfig
Subscribe (topicはデバイスのuuid)
Subscribe側コンテナ(mosquitto_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"}}