mosquittoをnginxのstreamモジュールでラップしてみた

<font color="Red">2017/6/5 新規投稿
2017/6/10更新 項4を追記しました</font>

この記事ではAWS ec2上のCentOS7にインストールしたnginx 1.13.1とmosquitto 1.4.12を使っています。

nginxはstreamモジュールを使うことで、様々なtcp/udpサーバのproxyとして機能します。TLSの終端、負荷分散などが可能になり、tcp/udpサーバの機能を向上させる優れものです。

本記事では、mqttブローカーのデファクト実装であるmosquittoを、nginxのstreamモジュールでラップしてみました。mosquittoそのものにもTLS機能はありますが、nginxの方が機能豊富であり、本構成によりmosquittoの機能を向上させることが可能です。

論理構成は以下の形です。今回の記事では全て同じサーバ上にインストールします。

```
mosquitto_pub/sub（mqttクライアント）---nginx---mosquitto（mqttブローカー）
```

# 1. mosquittoのインストールと動作確認

mosqittoは、yumやバイナリパッケージからインストールするのが簡単ですが、ここでは、nginxと同様にソースからビルドしてみます。

```bash
$ sudo yum groupinstall 'Development Tools' -y
$ git clone https://github.com/eclipse/mosquitto
$ sudo yum install openssl-devel -y
$ sudo yum install c-ares-devel -y
$ sudo yum install libuuid-devel -y
$ cd mosquitto
$ make binary
$ sudo make install WITH_DOCS=no
$ sudo vi /etc/ld.so.conf.d/liblocal.conf
-- liblocal.confの内容 ---
/usr/local/lib64
/usr/local/lib
$ sudo ldconfig
```
これで、mosquittoのmqttブローカー本体と、mosquitto_pub、mosquitto_subのmqttクライアントがインストールできました。

ターミナルを３つ開いてテストします。

まずはmqttブローカーを起動します。

```bash:terminal1
$ mosquitto
1496929451: mosquitto version 1.4.12 (build date 2017-06-08 12:34:49+0000) starting
1496929451: Using default config.
1496929451: Opening ipv4 listen socket on port 1883.
1496929451: Opening ipv6 listen socket on port 1883.
```

次にmqttクライアントでワイルドカードトピックをsubscribeします。

```bash:terminal2
$ mosquitto_sub -h localhost -t "#"
```

```bash:terminal1（上記のterminal2操作後の反応）
1496929487: New connection from ::1 on port 1883.
1496929487: New client connected from ::1 as mosqsub/18562-ip-172-31 (c1, k60).
```

最後に別のmqttクライアントでトピック名topicに、メッセージ"hello"をpublishします。

```bash:terminal3
$ mosquitto_pub -h localhost -t topic -m "hello"
```

```bash:terminal1（上記のterminal3操作後の反応）
1496929554: New connection from ::1 on port 1883.
1496929554: New client connected from ::1 as mosqpub/18564-ip-172-31 (c1, k60).
1496929554: Client mosqpub/18564-ip-172-31 disconnected
```

```bash:terminal2（上記と同時の反応）
hello
```

subscribeしているクライアントにて、メッセージが受け取れました。

# 2. nginxのインストール

nginxでstreamモジュールを有効にするには、ソースからインストールすることが必須です。

```bash
$ sudo yum install wget -y
$ wget http://nginx.org/download/nginx-1.13.1.tar.gz
$ tar zxvf nginx-1.13.1.tar.gz
$ cd nginx-1.13.1
$ ./configure --with-stream
$ make
$ sudo make install

$ sudo useradd --shell /sbin/nologin nginx
```

nginx.confを修正して、streamモジュールの設定を行います。

```bash
$ sudo vi /usr/local/nginx/conf/nginx.conf
```

```nginx:nginx.conf
#user  nobody;
user nginx nginx;

中略

http {
    # こちらは変更しない
}

stream {
    upstream mqtt {
       server localhost:1883;
    }

    server {
       listen 1884;
       proxy_pass mqtt;
    }
}
```

nginxは外部に対してTCP/1884で待ち受け、来た接続をTCP/1883(mqtt)に中継する設定です。

最後にnginxを起動しておきます。

```bash
$ sudo /usr/local/nginx/sbin/nginx
```

# 3. nginxを経由したmosquittoの動作確認

項1と同様に、ターミナルを３つ開いてテストします。

まずはmqttブローカーを起動します。

```bash:terminal1
$ mosquitto
1496930971: mosquitto version 1.4.12 (build date 2017-06-08 12:34:49+0000) starting
1496930971: Using default config.
1496930971: Opening ipv4 listen socket on port 1883.
1496930971: Opening ipv6 listen socket on port 1883.
```

次にmqttクライアントでワイルドカードトピックをsubscribeします。今回はnginxの待ち受けポートである1884につなぎます。

```bash:terminal2
$ mosquitto_sub -h localhost -p 1884 -t "#"
```

```bash:terminal1（上記のterminal2操作後の反応）
1496931033: New connection from 127.0.0.1 on port 1883.
1496931033: New client connected from 127.0.0.1 as mosqsub/18691-ip-172-31 (c1, k60).
```

無事に接続できました。

なお、項1のテストでは、接続元がIPv6表記だったのですが、今回はIPv4表記になっています。理由はわかりませんが、nginxを介して接続すると、IPv4表記とIPv6表記が交互に繰り返すようです。項1の直結では必ずIPv6表記になるようです。

最後に別のmqttクライアントでトピック名topicに、メッセージ"hello"をpublishします。こちらもポート1884につなぎます。

```bash:terminal3
$ mosquitto_pub -h localhost -p 1884 -t topic -m "hello"
```

```bash:terminal1（上記のterminal3操作後の反応）
1496931088: New connection from ::1 on port 1883.
1496931088: New client connected from ::1 as mosqpub/18692-ip-172-31 (c1, k60).
1496931088: Client mosqpub/18692-ip-172-31 disconnected.
```

```bash:terminal2（上記と同時の反応）
hello
```

subscribeしているクライアントにて、nginxを介してメッセージが受け取れました。

なお、当然ながら、pub/subの一方をnginx経由で、一方を直結でつないでも、メッセージを送ることが可能です。

# 4. 中継TCPポートの枯渇対策

本構成をとる場合に、10万セッション級の接続にあたって、nginx〜mosquittoの中継において、TCPソースポートの上限数がボトルネックとなります。

その場合、以下のように設定すると、ローカルホスト上のターゲットIPを分散させることができるため、回避可能です。（TCP/IPの接続では、利用済ソースポートであっても、ターゲットIP:ポートの組み合わせが異なれば、同一のソースポートを利用して新規に接続することが可能です）

```nginx:nginx.conf
stream {
    upstream mqtt {
       server 127.0.0.1:1883;
       server 127.0.0.2:1883;
       server 127.0.0.3:1883;
       server 127.0.0.4:1883;
    }

    server {
       listen 1884;
       proxy_pass mqtt;
    }
}
```
分散させる数は、適切に選定してください。また、ファイルディスクリプタの上限数の拡大も必要に応じて行ってください。