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

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

# 対象情報
- OS : Ubuntu 22.04 LTS (Jammy Jellyfish)
- Docker : version 20.10.16, build aa7e414
- mosquitto version 2.0.14
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/60c00efa-09d3-2280-8165-627353607532.png)

# ネットワーク構成
## 登場ホスト

- gitlab : IP : 201 MQTTを実行しているサーバ
- k8s-master(Raspberry pi) : IP : 221 メッセージを発行する別サーバ
- raspberrypi(Raspberry pi) : IP : 220 メッセージを購読する別サーバ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/a5b9c31b-9984-1193-ec64-22dd0fc3881b.png)

# MQTTとは…
pub/subでのメッセージを送るプロトコルです。（ざっくり）

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

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

メッセージもテキストですが、オブジェクトをシリアライズすれば投げることもできますのでもうちょっと高尚になります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/a3721220-8357-64de-be7e-58f23da748f0.png)


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

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

# mosquittoとは
https://mosquitto.org/

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

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

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

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

```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
```
保存先のログ、データ、コンフィグのディレクトリを作成します

```bash
/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です

```bash
/usr/local/mosquitto$ sudo docker-compose up -d --build
```
停止

```bash
/usr/local/mosquitto$ sudo docker-compose down
```
プロセスが動いてポートフォワードしていればOKです

```bash
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ですかね

```bash
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の標準コマンドを使います。

- mosquitoo_pub : 発行側クライアント : https://mosquitto.org/man/mosquitto_pub-1.html
- mosquitto_sub : 購読側クライアント : https://mosquitto.org/man/mosquitto_sub-1.html

Raspberry piへのインストールは…
```bash
pi@raspberrypi:~ $ sudo apt-get install mosquitto-clients
```
です！

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/4675ca46-f41b-2084-b186-1566548ebba5.png)

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

### 購読側のTerminal実行
はじめにサーバ(ubuntu)にsshで入ってdockerのmosquitto内部に入ります。

```bash
~$ sudo docker exec -it mosquitto sh
```

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

```bash
/ # mosquitto_sub -t test -d
```
コマンドを実行すると待ち状態になります

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

### 発行側のTerminalを実行
次に別のTerminalにてサーバ(ubuntu)からdockerのmosquittoに入ります

```bash
~$ sudo docker exec -it mosquitto sh
```
発行コマンドを実行します。
topic名をtest
メッセージを”Hello”を送ります

```bash
/ # mosquitto_pub -t test -m "Hello"
```

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

```bash
/ # 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)にて動作確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/5e308b42-7ef9-9185-8e62-96c0f42c0199.png)

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

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

```bash
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
```

### 購読動作確認

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

```bash
/ # 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」が無事に通知されることを確認できました。

### 受信側を別サーバにする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/45ec67eb-f607-e011-80cc-ee1f1be1025c.png)

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

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

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

```bash
/ # mosquitto_pub -t test -m "Hello"
```
疎通が完了すると以下の様に購読側が表示されます。

```bash
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
```
無事に購読動作を別のサーバで動くことを確認できました。

## 送受信を別のサーバにする
最終的な動作確認！

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2481837/0416238e-3378-cee9-743e-392188163671.png)

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

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

```bash
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)にて購読された実行結果…

```bash
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
```

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

```bash
/ # 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サービスが完結できます

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

ソース : https://www.mmpp.org/archives/2906