インターネット上で最もアクセスの多いWebサイトの市場では、NGINXとNGINX Plusが圧倒的なシェアを占めています。実際、世界で最もアクセスの多い上位100万サイトのうち、NGINXを利用しているサイト数は他のどのWebサーバーよりも多くなっています。単一サーバーで100万を超える同時接続を処理できるため、Airbnb、Netflix、Uberなどの「ハイパースケール」サイトやアプリで広く採用されています。
NGINX Plusは、Webサーバー、HTTPリバースプロキシ、ロードバランサーとして広く知られていますが、TCPおよびUDPアプリケーションをサポートする、充実した機能を備えたアプリケーションデリバリーコントローラー(ADC)でもあります。その「イベント駆動型アーキテクチャ」をはじめ、HTTPのユースケースで成功を支えてきたその他の特性はすべて、モノのインターネット(IoT)にも同様に適用できます。
この記事では、NGINX Plusを使用してMQTTトラフィックを負荷分散する方法を紹介します。MQTTは、遠隔地の油田との通信を目的として1999年に初めて公開されました。2013年にはIoTのユースケース向けに更新され、それ以来、多くのIoT導入環境で選ばれるプロトコルとなっています。数百万台のデバイスを擁する本番IoT環境では、ロードバランサーに高いパフォーマンスと高度な機能が求められます。この2部構成のブログシリーズでは、以下の高度なユースケースについて説明します。
- MQTTトラフィックの負荷分散(本記事)
- MQTTトラフィックの暗号化と認証
テスト環境
NGINX Plusの機能を検証するため、MQTTブローカーのクラスターを含むIoT環境の主要コンポーネントを再現したシンプルなテスト環境を使用します。この環境のMQTTブローカーは、Dockerコンテナ内で実行されるHiveMQインスタンスです。
MQTTの負荷分散とセッション永続化のテスト環境
NGINX PlusはMQTTブローカーのリバースプロキシおよびロードバランサーとして機能し、デフォルトのMQTTポート1883でリッスンします。これにより、クライアントにはシンプルで一貫したインターフェースが提供される一方、クライアントに一切影響を与えることなく、バックエンドのMQTTノードをスケールアウトしたり、オフラインにしたりすることもできます。テスト環境では、IoTデバイスに相当するクライアントとして、Mosquittoのコマンドラインツールを使用します。
この記事とパート2で取り上げるすべてのユースケースでは、このテスト環境を使用します。また、すべての設定は図に示すアーキテクチャにそのまま適用できます。テスト環境の構築手順について詳しくは、付録1を参照してください。
アクティブヘルスチェックによるMQTTの負荷分散と高可用性の確保
A primary function of a ロードバランサーの主な機能は、バックエンドサーバーの追加、削除、オフライン化がクライアントに影響しないよう、アプリケーションの高可用性を確保することです。これを確実に実現するには、各バックエンドサーバーが利用可能かどうかをプロアクティブに確認するヘルスチェックが不可欠です。アクティブヘルスチェックを使用すると、NGINX Plusは実際のクライアントリクエストが到達する前に、障害が発生したサーバーを負荷分散グループから除外できます。
ヘルスチェックの有用性は、実際のアプリケーショントラフィックをどの程度正確にシミュレートし、応答を分析できるかによって決まります。pingなどによる単純なサーバーの稼働確認では、バックエンドサービスが稼働していることまでは保証できません。TCPポートが開いているかどうかを確認しても、アプリケーション自体が正常であることまでは保証できません。ここでは、各バックエンドサーバーが新しいMQTT接続を受け入れられることを確認するヘルスチェックを使用して、テスト環境向けの基本的な負荷分散を構成します。
2つの設定ファイルを変更します。
メインの「nginx.conf」ファイルに、以下の「stream」ブロックと「include」ディレクティブを記述します。これにより、NGINX Plusは、「stream_conf.d」サブディレクトリ内の1つ以上のファイルからTCPロードバランシングの設定を読み込みます。このサブディレクトリは「nginx.conf」と同じディレクトリにあります。これは、実際の設定を「nginx.conf」に直接記述しないようにするためです。
コード例を読み込み中…
次に、nginx.confと同じディレクトリに、TCPおよびUDPの設定ファイルを格納するためのstream_conf.dディレクトリを作成します。既存のconf.dディレクトリは使用しないでください。デフォルトではhttp設定コンテキスト用に予約されているため、そこにstream設定を追加すると失敗します。
コード例を読み込み中…
In stream_mqtt_healthcheck.confでは、まずMQTTトラフィックのアクセスログ形式を定義します(1~2行目)。生成されたログをログ分析ツールにインポートできるよう、意図的にHTTP共通ログ形式と似た形式にしています。
次に、3台のMQTTサーバーを含むupstreamグループ「hive_mq」(4~9行目)を定義します。テスト環境では、各サーバーにlocalhost上の固有のポート番号でアクセスできます。zoneディレクティブでは、負荷分散の状態とヘルス情報を維持するために、すべてのNGINX Plusワーカープロセス間で共有するメモリ容量を定義します。
The matchブロック(11~15行目)では、MQTTサーバーの可用性をテストするためのヘルスチェックを定義します。sendディレクティブは、クライアント識別子(ClientId)がCONNECTである完全なMQTTnginx health checkパケットを16進数で表したものです。ヘルスチェックが実行されるたびに、upstreamグループで定義された各サーバーにこのパケットが送信されます。対応するexpectディレクティブでは、NGINX Plusがサーバーを正常と判断するために、そのサーバーが返す必要のある応答を指定します。ここでは、4バイトの16進文字列20 02 00 00が完全なMQTTCONNACKパケットです。このパケットを受信できれば、MQTTサーバーが新しいクライアント接続を受け入れられることを確認できます。
The serverブロック(17~25行目)では、NGINX Plusがクライアントを処理する方法を設定します。NGINX PlusはデフォルトのMQTTポート1883でリッスンし、すべてのトラフィックをhive_mqアップストリームグループに転送します(19行目)。health_checkディレクティブでは、アップストリームグループに対してヘルスチェックを実行すること(デフォルトの実行間隔は5秒)と、mqtt_conn matchブロックで定義されたチェックを使用することを指定します。
設定の確認
この基本構成が正しく動作していることを確認するには、Mosquittoクライアントを使用して、テスト環境にテストデータをパブリッシュします。
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001"Client thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT
$ tail --lines=1 /var/log/nginx/mqtt_access.log
192.168.91.1 [23/Mar/2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831アクセスログの行から、NGINX Plusが合計23 bytesを受信し、4 bytes(CONNACKパケット)がクライアントに送信されたことがわかります。また、MQTTのnode1(ポート18831)が選択されたことも確認できます。アクセスログの以下の行が示すように、テストを繰り返すと、デフォルトのラウンドロビン負荷分散アルゴリズムによって、node1、node2、node3が順番に選択されます。
$ tail --lines=4 /var/log/nginx/mqtt_access.log192.168.91.1 [23/Mar/2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831
192.168.91.1 [23/Mar/2017:11:42:26 +0000] TCP 200 23 4 127.0.0.1:18832
192.168.91.1 [23/Mar/2017:11:42:27 +0000] TCP 200 23 4 127.0.0.1:18833
192.168.91.1 [23/Mar/2017:11:42:28 +0000] TCP 200 23 4 127.0.0.1:18831NGINX JavaScript Moduleを使用したセッション永続化のためのMQTT負荷分散
[編集者注 – 以下は、NGINX JavaScriptモジュールの数あるユースケースの1つです。完全な一覧については、「NGINX JavaScriptモジュールのユースケース」をご覧ください。]
このセクションのコードは、ブログの初回公開以降に行われたNGINX JavaScriptの実装変更を反映するため、次のように更新されています。
- Streamモジュール向けにリファクタリングされたセッション(
s)オブジェクトを使用するため。このオブジェクトはNGINX JavaScript 0.2.4で導入されました。 - To use the
js_importディレクティブを使用します。このディレクティブは、js_includeディレクティブに代わるもので、NGINX Plus R23以降で使用できます。詳細については、NGINX JavaScriptモジュールのリファレンスドキュメントを参照してください。「設定例」セクションでは、NGINX設定ファイルとJavaScriptファイルの正しい構文を確認できます。
ラウンドロビン方式の負荷分散は、クライアント接続を複数のサーバーに分散する効果的な仕組みです。ただし、いくつかの理由から、MQTT接続には適していません。
MQTTサーバーでは、多くの場合、クライアントとサーバー間の接続が長時間維持されることを前提としており、サーバー上に大量のセッション状態が蓄積されることがあります。しかし、IoTデバイスとそれらが使用するIPネットワークの性質上、接続が切断され、一部のクライアントは頻繁な再接続を余儀なくされます。NGINX Plusでは、ハッシュロードバランシングアルゴリズムを使用し、クライアントのIPアドレスに基づいてMQTTサーバーを選択できます。upstreamブロックにhash $remote_addr;を追加するだけで、セッション維持が有効になります。これにより、特定のクライアントIPアドレスから新しい接続が確立されるたびに、同じMQTTサーバーが選択されます。
ただし、IoTデバイスが同じIPアドレスから再接続するとは限りません。特に、GSMやLTEなどのセルラーネットワークを使用している場合はなおさらです。同じクライアントが同じMQTTサーバーに再接続するようにするには、MQTTクライアント識別子をハッシュアルゴリズムのキーとして使用する必要があります。
MQTT ClientIdに基づくセッション永続化
MQTT ClientIdは、最初の「CONNECT」パケットに必須の要素です。そのため、パケットがアップストリームサーバーにプロキシされる前にNGINX Plusで利用できます。NGINX JavaScriptを使用して「CONNECT」パケットを解析し、ClientIdを変数として抽出できます。この変数を「hash」ディレクティブで使用することで、MQTT固有のセッション永続化を実装できます。
NGINX JavaScriptは、「NGINXネイティブ」のプログラマティックな設定言語です。NGINXおよびNGINX Plus向けに独自に実装されたJavaScriptで、サーバーサイドのユースケースとリクエスト単位の処理に特化して設計されています。MQTTのセッション永続化の実装に適した、次の3つの主な特長があります。
- NGINX JavaScriptはNGINX Plusの処理フェーズと緊密に統合されているため、クライアントパケットがアップストリームグループに負荷分散される前に検査できます。
- NGINX JavaScriptは、文字列処理と数値処理にJavaScriptの組み込みメソッドを使用するため、レイヤー4プロトコルを効率的に解析できます。MQTTの「
CONNECT」パケットの実際の解析に必要なコードは20行未満です。 - NGINX JavaScriptでは、NGINX Plusの設定で使用できる変数を作成できます。
NGINX JavaScriptを有効にする手順については、「付録2」を参照してください。
セッション永続化のためのNGINX Plus設定
このユースケースのNGINX Plus構成は比較的シンプルです。以下の構成は、アクティブヘルスチェックを使用したロードバランシングの例を変更し、簡潔にするためにヘルスチェックを省略したものです。
コード例を読み込み中…
まず、js_importディレクティブでNGINX JavaScriptコードの場所を指定します。js_setディレクティブは、$mqtt_client_id変数を評価する必要があるときにsetClientId関数を呼び出すようNGINX Plusに指示します。5行目で、この変数をmqttのログ形式に追加し、アクセスログに詳細情報を記録します。
12行目では、hashディレクティブで$mqtt_client_idをキーに指定し、セッション維持を有効にします。consistentパラメーターを使用しているため、アップストリームサーバーに障害が発生した場合、そのサーバーが処理していたトラフィックは、残りのサーバーですでに確立されているセッションに影響を与えることなく、各サーバーに均等に分散されます。コンシステントハッシュの詳細については、ブログ記事「Webキャッシュのシャーディング」をご覧ください。ここでも同じ原則と利点が当てはまります。
The js_prereadディレクティブ(18行目)は、リクエスト処理の事前読み取りフェーズで実行されるNGINX JavaScript関数を指定します。事前読み取りフェーズは、双方向のすべてのパケットでトリガーされ、プロキシ処理の前に実行されます。これにより、$mqtt_client_idの値をupstreamブロックで必要なときに使用できます。
セッション永続化のためのNGINX JavaScriptコード
MQTT ClientIdを抽出するJavaScriptを、NGINX Plus構成ファイル(mqtt.js)内のjs_importディレクティブによって読み込まれるstream_mqtt_session_persistence.confファイルに定義します。
コード例を読み込み中…
主要な関数である「getClientId()」は4行目で宣言されています。この関数には、現在のTCPセッションを表す「s」というオブジェクトが渡されます。セッションオブジェクトには多数の「プロパティ」があり、そのうちのいくつかがこの関数で使用されます。
5~9行目では、現在のパケットがクライアントから最初に受信したパケットであることを確認します。以降のクライアントメッセージとサーバー応答は無視されるため、接続の確立後にトラフィックフローへ追加のオーバーヘッドが生じることはありません。
コード例を読み込み中…
10~24行目では、MQTTヘッダーを調べ、パケットがCONNECTタイプであることを確認するとともに、MQTTペイロードの開始位置を特定します。
コード例を読み込み中…
27~32行目では、ペイロードからClientIdを抽出し、その値をJavaScriptのグローバル変数「client_id_str」に格納します。次に、この変数を「setClientId」関数を使用してNGINX設定にエクスポートします(43~45行目)。
セッション永続性の確認
ここで、Mosquittoクライアントを再度使用し、3つの異なるClientId値(-iオプション)で一連のMQTTパブリッシュリクエストを送信して、セッションの永続性をテストできます。
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "foo"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "bar"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"
$ mosquitto_pub -h mqtt.example.com -t "topic/test" -m "test123" -i "baz"アクセスログを確認すると、ClientId「foo」は常に「node1」(ポート 18831)に接続し、ClientId「bar」は常に「node2」(ポート 18832)に接続し、ClientId「baz」は常に「node3」(ポート 18833)に接続していることがわかります。
$ tail /var/log/nginx/mqtt_access.log192.168.91.1 [23/Mar/2017:12:24:24 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:28 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:32 +0000] TCP 200 23 4 127.0.0.1:18833 baz
192.168.91.1 [23/Mar/2017:12:24:35 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:37 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:38 +0000] TCP 200 23 4 127.0.0.1:18831 foo
192.168.91.1 [23/Mar/2017:12:24:42 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:44 +0000] TCP 200 23 4 127.0.0.1:18832 bar
192.168.91.1 [23/Mar/2017:12:24:47 +0000] TCP 200 23 4 127.0.0.1:18833 baz
192.168.91.1 [23/Mar/2017:12:24:48 +0000] TCP 200 23 4 127.0.0.1:18833 bazセッション永続化を使用する場合でも、その他の負荷分散アルゴリズムを使用する場合でも、MQTT ClientIdがアクセスログの各行に表示されるという利点もあります。
まとめ
2部構成シリーズの第1回となる本記事では、NGINX Plusがアクティブヘルスチェックを使用してIoTアプリケーションの可用性と信頼性を向上させる仕組みと、NGINX JavaScriptがTCPトラフィックのセッション永続化などのレイヤー7負荷分散機能を提供し、NGINX Plusを拡張する仕組みについて説明しました。第2回では、TLS終端のオフロードとクライアント認証により、NGINX PlusがIoTアプリケーションのセキュリティを強化する仕組みについて説明します。
NGINX JavaScriptと組み合わせても、単独でも、NGINX Plusは本来備えている高いパフォーマンスと効率性により、IoTインフラストラクチャ向けのソフトウェアロードバランサーとして最適です。
NGINX PlusでNGINX JavaScriptをお試しいただくには、30日間の無料トライアルを開始するか、ユースケースについてご相談ください。
付録
テスト環境の作成
テスト環境は、分離された再現可能な環境となるよう、仮想マシン上にインストールしました。ただし、物理的な「ベアメタル」サーバーにも問題なくインストールできます。
NGINX Plusのインストール
手順については、「NGINX Plus Admin Guide」を参照してください。
HiveMQのインストール
任意のMQTTサーバーを使用できますが、このテスト環境ではHiveMQを使用します(こちらからダウンロード)。この例では、各ノードにDockerコンテナを使用し、単一のホストにHiveMQをインストールします。以下の手順は「Dockerを使用したHiveMQのデプロイ」を基にしています。
- HiveMQ用のDockerfileをhivemq.zipと同じディレクトリに作成します。
コード例を読み込み中…
- Working in the directory that contains hivemq.zipとDockerfileが格納されているディレクトリで、Dockerイメージを作成します。
$ docker build -t hivemq:latest .- 3つのHiveMQノードを作成し、それぞれを異なるポートで公開します。
$ docker run -p 18831:1883 -d --name node1 hivemq:latest
ff2c012c595a
$ docker run -p 18832:1883 -d --name node2 hivemq:latest
47992b1f4910
$ docker run -p 18833:1883 -d --name node3 hivemq:latest
17303b900b64- 3つのHiveMQノードがすべて実行中であることを確認します。(以下の出力例では、読みやすくするため、
COMMAND、CREATED、STATUSの各列を省略しています。)
$ docker psCONTAINER ID IMAGE ... PORTS NAMES
17303b900b64 hivemq:latest ... 0.0.0.0:18833->1883/tcp node3
47992b1f4910 hivemq:latest ... 0.0.0.0:18832->1883/tcp node2
ff2c012c595a hivemq:latest ... 0.0.0.0:18831->1883/tcp node1Mosquittoのインストール
Mosquittoコマンドラインクライアントは、プロジェクトのWebサイトからダウンロードできます。Homebrewをインストール済みのMacユーザーは、次のコマンドを実行できます。
$ brew install mosquittoいずれかのDockerイメージに簡単なパブリッシュメッセージを送信し、MosquittoクライアントとHiveMQのインストールをテストします。
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 18831
Client thing001 sending CONNECT
Client thing001 received CONNACK
Client thing001 sending PUBLISH (d0, q0, r0, m1, 'topic/test', ... (7 bytes))
Client thing001 sending DISCONNECT[ngx_snippet name=’njs-enable-instructions’]
著者について

関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります