MQTT(Mosquitto)サーバの構築の話

今回はMQTTサーバをMosquittoで構築する話です。

対象情報

  • OS : Ubuntu 22.04 LTS (Jammy Jellyfish)
  • Docker : version 20.10.16, build aa7e414
  • mosquitto version 2.0.14

ネットワーク構成

登場ホスト

  • gitlab : IP : 201 MQTTを実行しているサーバ
  • k8s-master(Raspberry pi) : IP : 221 メッセージを発行する別サーバ
  • raspberrypi(Raspberry pi) : IP : 220 メッセージを購読する別サーバ

MQTTとは…

pub/subでのメッセージを送るプロトコルです。(ざっくり)

1つのメッセージを発行(pub)すると購読(sub)に全てへとメッセージを送ることができます。
発行者は購読者がいるかどうかなど判断しなくて処理が完了できます。
ま、完全なる疎結合が成立できます。

特にクライアントのプログラムにはトリッキーなことしなくてもサーバがあんじょう良くしてくれます。
クライアントはTopicを指定して読み込むだけでOKです

メッセージもテキストですが、オブジェクトをシリアライズすれば投げることもできますのでもうちょっと高尚になります

もう少し複雑なことしたければAMQPとか使うと良いかな…

pub/subでは未購読状態(ホストが落ちているなど)の場合にはメッセージが取得できない。
なので、未購読時をキューにするとか、ルーティングするとか結構色々AQMPではできます。
QoSはできる様です。

mosquittoとは

https://mosquitto.org/

本当はRabbitMQのMQTT pluginを使う予定でしたがどうにもうまくいかなかったのでmosquittoを使いした

mosquittoを構築する

プレーンにdockerで作ってもよいのですが、メンテするのがめんどくさいのと保存先がわかりづらくなるのでdocker-composeにて構築しました。

保存先は /usr/local/mosquitto/にて作業を行いました。

docker-compose.yamlファイルの内容はこちら

version: '3'

services:
  mosquitto:
    container_name: mosquitto
    image: eclipse-mosquitto
    ports:
      - '1883:1883'
    volumes:
      - ./conf:/mosquitto/config
      - ./log:/mosquitto/log
      - ./data:/mosquitto/data
    user: $UID:$GID

保存先のログ、データ、コンフィグのディレクトリを作成します

/usr/local/mosquitto$ mkdir conf log data

設定ファイル(conf/mosquitto.conf)を以下にします

persistence true
persistence_location /mosquitto/data
log_dest file /mosquitto/log/mosquitto.log
log_type all
listener 1883
## Authentication ##
allow_anonymous true

ま、シンプルですね。

※ ファイル・ディレクトリのパーミッション周りが結構ややこしかったのですが最終的にこれになりました。

これを実行すればOKです

/usr/local/mosquitto$ sudo docker-compose up -d --build

停止

/usr/local/mosquitto$ sudo docker-compose down

プロセスが動いてポートフォワードしていればOKです

wataru@gitlab:/usr/local/mosquitto$ sudo docker ps | grep mosquitto
e7ded2669a09   eclipse-mosquitto                              "/docker-entrypoint.…"   3 days ago     Up 3 days              0.0.0.0:1883->1883/tcp, :::1883->1883/tcp                                                  mosquitto

ログがサーバからアクセスできればOKですかね

wataru@gitlab:/usr/local/mosquitto$ sudo head log/mosquitto.log 
1653374686: mosquitto version 2.0.14 starting
1653374686: Config loaded from /mosquitto/config/mosquitto.conf.
1653374686: Opening ipv4 listen socket on port 1883.
1653374686: Opening ipv6 listen socket on port 1883.

動作確認

サーバがインストールされたので実際にクライアントを用いて動作を確認したいと思います。

以下の動作を考えたいと思います

  • ローカルの実行
  • 送信を別のサーバとする
  • 受信を別のサーバとする
  • 送信を別のサーバ、受信を別のサーバとする(ローカル受信も確認)

今回動作を確認したプログラムですが、mosquitooの標準コマンドを使います。

Raspberry piへのインストールは…

pi@raspberrypi:~ $ sudo apt-get install mosquitto-clients

です!

ローカル動作確認

mosquittoサーバのローカルにてクライアントを実行してメッセージがやりとりできるかを確認します。

送受信を確認するのでTerminalを2つで立ち上げるとよいでしょう

購読側のTerminal実行

はじめにサーバ(ubuntu)にsshで入ってdockerのmosquitto内部に入ります。

~$ sudo docker exec -it mosquitto sh

購読コマンドを実行します。
topic名をtestとしてみました。

/ # mosquitto_sub -t test -d

コマンドを実行すると待ち状態になります

-dは任意です。
あればデバグモードでコネクションとかでます。
なければ受信メッセージだけが表示されます

発行側のTerminalを実行

次に別のTerminalにてサーバ(ubuntu)からdockerのmosquittoに入ります

~$ sudo docker exec -it mosquitto sh

発行コマンドを実行します。
topic名をtest
メッセージを”Hello”を送ります

/ # mosquitto_pub -t test -m "Hello"

実行結果

発行側でメッセージを発行すると購読側にメッセージが表示されます

/ # mosquitto_sub -t test -d
Client null sending CONNECT
Client null received CONNACK (0)
Client null sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0, Options: 0x00)
Client null received SUBACK
Subscribed (mid: 1): 0


Client null sending PINGREQ
Client null received PINGRESP
Client null received PUBLISH (d0, q0, r0, m0, 'test', ... (5 bytes))
Hello

これで疎通が確認できました!

ポイントは最後の「Hello」です。
指定したメッセージが表示されました。

発行側を別サーバ(Raspberry pi)にて動作確認

次に発行側のクライアントを別のサーバにて実行してサーバが外部からアクセスできるかを確認します。

発行コマンドはローカルと同じですが、MQTT動作のホストを指定します。

wataru@k8s-master:~ $ mosquitto_pub -h 192.168.1.201 -t test -m "Test2" -d
Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending PUBLISH (d0, q0, r0, m1, 'test', ... (5 bytes))
Client (null) sending DISCONNECT

購読動作確認

サーバのローカルで購読コマンドを実行した状態で別サーバで発行すると以下の様になります

/ # mosquitto_sub -t test -d
Client null sending CONNECT
Client null received CONNACK (0)
Client null sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0, Options: 0x00)
Client null received SUBACK
Subscribed (mid: 1): 0
Client null received PUBLISH (d0, q0, r0, m0, 'test', ... (5 bytes))
Test2

発行側が指定したメッセージ「Test2」が無事に通知されることを確認できました。

受信側を別サーバにする

次に購読を別のサーバにしてローカルからメッセージを発行します。

別サーバ(raspberrypi)において購読コマンドを実行します…
MQTTホストを指定するだけですね

発行はローカルからの実行です

/ # mosquitto_pub -t test -m "Hello"

疎通が完了すると以下の様に購読側が表示されます。

pi@raspberrypi:~ $ mosquitto_sub -h 192.168.1.201 -t test -d
Client mosqsub|10394-raspberry sending CONNECT
Client mosqsub|10394-raspberry received CONNACK (0)
Client mosqsub|10394-raspberry sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0)
Client mosqsub|10394-raspberry received SUBACK
Subscribed (mid: 1): 0
Client mosqsub|10394-raspberry received PUBLISH (d0, q0, r0, m0, 'test', ... (5 bytes))
Hello

無事に購読動作を別のサーバで動くことを確認できました。

送受信を別のサーバにする

最終的な動作確認!

次に端末が別のサーバの場合に動作するかを確認します。

解説は省略しますが、購読側のコマンドを実行状態にします

別サーバ(発行サーバ : k8s-master)からメッセージを発行する

wataru@k8s-master:~ $ mosquitto_pub -h 192.168.1.201 -t test -m "Test2" -d
Client (null) sending CONNECT
Client (null) received CONNACK (0)
Client (null) sending PUBLISH (d0, q0, r0, m1, 'test', ... (5 bytes))
Client (null) sending DISCONNECT

購読用の別サーバ(raspberrypi)にて購読された実行結果…

pi@raspberrypi:~ $ mosquitto_sub -h 192.168.1.201 -t test -d
Client mosqsub|10409-raspberry sending CONNECT
Client mosqsub|10409-raspberry received CONNACK (0)
Client mosqsub|10409-raspberry sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0)
Client mosqsub|10409-raspberry received SUBACK
Subscribed (mid: 1): 0
Client mosqsub|10409-raspberry received PUBLISH (d0, q0, r0, m0, 'test', ... (5 bytes))
Test2

サーバローカルでの購読実行結果

/ # mosquitto_sub -t test -d
Client null sending CONNECT
Client null received CONNACK (0)
Client null sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0, Options: 0x00)
Client null received SUBACK
Subscribed (mid: 1): 0
Client null received PUBLISH (d0, q0, r0, m0, 'test', ... (5 bytes))
Test2

終いに

今回はMQTTサーバの構築をメモ的に残しておきました。

ってかMQTTサーバを何に使うかとちょっとだけ書きますと

ズバリ!IoTです。

クライアント側のプログラムが単純になりますのでRaspberry piやarduinoなどのマイクロコンピュータで購読することが可能になります。

発行と購読が1:多とできるので欲しい端末だけが実装することが可能になります。

別にプログラムが購読して、別のメッセージに発行すれば別のTopicとして処理が可能になります。

さらに!

Webページでも便利ですね。

静的ページ(html)を購読時に更新することにより通知ページなどのアクセス数が多くてもプログラム処理が発生しないので楽楽にできます。
ってかApacheとかnginxだけでWebサービスが完結できます

次回はこの発行・購読を標準コマンドではなくプログラムで実装してみたいと思います。