インターネット上で最もアクセスの多い Web サイトに関しては、NGINX と NGINX Plus が市場を支配しています。 実際、 NGINX は他のどの Web サーバーよりも、世界で最もアクセスの多い 100 万以上のサイトを支えています。 単一のサーバーで 100 万を超える同時接続を処理できるため、Airbnb、Netflix、Uber などの「ハイパースケール」サイトやアプリで採用されています。
NGINX Plus は、Web サーバー、HTTP リバース プロキシ、ロード バランサーとして最もよく知られていますが、TCP および UDP アプリケーションをサポートするフル機能のアプリケーション配信コントローラー (ADC) でもあります。 イベント駆動型アーキテクチャと、HTTP ユースケースで成功を収めたその他のすべての属性は、モノのインターネット (IoT) にも同様に適用できます。
この記事では、NGINX Plus を使用してMQTTトラフィックの負荷を分散する方法を説明します。 MQTT はもともと、遠隔地の油田との通信を目的として 1999 年に公開されました。 2013 年に IoT ユースケース向けに更新され、それ以来、多くの IoT 展開で選択されるプロトコルになりました。 数百万台のデバイスを備えた本番環境の IoT 展開では、ロード バランサーに高いパフォーマンスと高度な機能が求められます。この 2 部構成のブログ投稿シリーズでは、次の高度なユース ケースについて説明します。
NGINX Plus の機能を調べるために、MQTT ブローカーのクラスターを備えた IoT 環境の主要コンポーネントを表すシンプルなテスト環境を使用します。 この環境の MQTT ブローカーは、Docker コンテナ内で実行されるHiveMQインスタンスです。
NGINX Plus は、MQTT ブローカーのリバース プロキシおよびロード バランサとして機能し、デフォルトの MQTT ポート 1883 をリッスンします。 これにより、クライアントに対してシンプルで一貫性のあるインターフェースが提供され、バックエンドの MQTT ノードはクライアントに何ら影響を与えることなくスケールアウト (さらにはオフラインに) できます。 テスト環境内の IoT デバイスを表すクライアントとして、 Mosquittoコマンドライン ツールを使用します。
この投稿とパート 2 のすべてのユースケースではこのテスト環境が使用され、すべての構成は図に示されているアーキテクチャに直接適用されます。 テスト環境の構築に関する詳細な手順については、付録 1 を参照してください。
ロード バランサの主な機能は、クライアントに影響を与えずにバックエンド サーバーを追加、削除、またはオフラインにできるように、アプリケーションに高可用性を提供することです。 これを確実に実行するには、各バックエンド サーバーの可用性をプロアクティブに調査するヘルス チェックが必要です。 アクティブ ヘルス チェックを使用すると、NGINX Plus は実際のクライアント要求が到達する前に、障害が発生したサーバーを負荷分散グループから削除できます。
ヘルスチェックの有用性は、実際のアプリケーション トラフィックをどれだけ正確にシミュレートし、応答を分析できるかによって決まります。 ping などの単純なサーバー稼働チェックでは、バックエンド サービスが実行されていることは保証されません。 TCP ポートオープン チェックでは、アプリケーション自体が正常であることは保証されません。 ここでは、各バックエンド サーバーが新しい MQTT 接続を受け入れることができることを確認するヘルス チェックを使用して、テスト環境の基本的な負荷分散を構成します。
2 つの構成ファイルに変更を加えます。
メインのnginx.confファイルには、次のstream
ブロックとinclude
ディレクティブを追加して、 nginx.confと同じディレクトリにあるstream_conf.dサブディレクトリ内の 1 つ以上のファイルから TCP ロード バランシングの設定を NGINX Plus が読み取るようにします。 実際の設定をnginx.confに含める代わりにこれを行います。
次に、 nginx.confと同じディレクトリに、TCP および UDP 構成ファイルを格納するディレクトリstream_conf.dを作成します。 既存のconf.dディレクトリは使用しないことに注意してください。これは、デフォルトでhttp
構成コンテキスト用に予約されているため、そこにストリーム
構成を追加すると失敗するためです。
stream_mqtt_healthcheck.confでは、まず MQTT トラフィックのアクセス ログ形式を定義します (1 行目から 2 行目)。 これは、結果のログをログ分析ツールにインポートできるように、意図的に HTTP 共通ログ形式に似ています。
次に、3 つの MQTT サーバーを含むhive_mq (行 4 ~ 9) というアップストリーム
グループを定義します。 私たちのテスト環境では、それぞれ固有のポート番号を使用してローカルホスト上でアクセスできます。 ゾーン
ディレクティブは、負荷分散の状態と健全性情報を維持するために、すべての NGINX Plus ワーカー プロセス間で共有されるメモリの量を定義します。
一致
ブロック (11 ~ 15 行目) は、MQTT サーバーの可用性をテストするために使用されるヘルス チェックを定義します。 send
ディレクティブは、 nginx
health
check
のクライアント識別子 (ClientId) を持つ完全な MQTT CONNECT
パケットの 16 進表現です。 これは、ヘルス チェックが実行されるたびに、アップストリーム グループで定義されている各サーバーに送信されます。 対応するexpect
ディレクティブは、NGINX Plus が正常であると判断するためにサーバーが返す必要がある応答を記述します。 ここで、4バイトの16進文字列20
02
00
00
完全な MQTT CONNACK
パケットです。 このパケットを受信すると、MQTT サーバーが新しいクライアント接続を受信できることを示します。
サーバー
ブロック (17 ~ 25 行目) は、NGINX Plus がクライアントを処理する方法を構成します。 NGINX Plus はデフォルトの MQTT ポート 1883 をリッスンし、すべてのトラフィックをhive_mqアップストリーム グループに転送します (行 19)。 health_check
ディレクティブは、アップストリーム グループに対してヘルス チェックが実行され (デフォルトの頻度は 5 秒)、 mqtt_connマッチ
ブロックによって定義されたチェックが使用されることを指定します。
この基本構成が機能していることをテストするには、Mosquitto クライアントを使用してテスト データをテスト環境に公開します。
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001"クライアント thing001 が CONNECT を送信中 クライアント thing001 が CONNACK を受信中 クライアント thing001 が PUBLISH を送信中 (d0, q0, r0, m1, 'topic/test', ...) (7 バイト)) クライアント thing001 が 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 バイトを受信し、4 バイトがクライアントに送信されたことを示しています ( CONNACK
パケット)。 また、MQTTノード 1 (ポート 18831) が選択されたこともわかります。 アクセス ログの次の行に示されているように、テストを繰り返すと、デフォルトのラウンド ロビン ロード バランシング アルゴリズムによって、 node1 、 node2 、 node3 が順番に選択されます。
$ tail --lines=4 /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 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 [2017年3月23日:11:42:28 +0000] TCP 200 23 4 127.0.0.1:18831
[編集者注– 以下の使用例は、NGINX JavaScript モジュールの多くの使用例の 1 つにすぎません。 完全なリストについては、 「NGINX JavaScript モジュールの使用例」を参照してください。
このセクションのコードは、ブログが最初に公開されてから NGINX JavaScript の実装に加えられた変更を反映するために、次のように更新されています。
s
) オブジェクトを使用します。js_インポート
指令は、 js_include
指令の NGINX プラス R23 そしてその後。 詳細については、 NGINX JavaScript モジュールのリファレンス ドキュメントを参照してください。構成例のセクションに、NGINX 構成と JavaScript ファイルの正しい構文が示されています。ラウンドロビン負荷分散は、サーバーのグループ全体にクライアント接続を分散するための効果的なメカニズムです。 ただし、MQTT 接続には適さない理由がいくつかあります。
MQTT サーバーは、多くの場合、クライアントとサーバー間の接続が長時間持続することを想定しており、サーバー上に大量のセッション状態が蓄積される可能性があります。 残念ながら、IoT デバイスとそれらが使用する IP ネットワークの性質上、接続が切断され、一部のクライアントは頻繁に再接続する必要が生じます。 NGINX Plus はハッシュロード バランシング アルゴリズムを使用して、クライアント IP アドレスに基づいて MQTT サーバーを選択できます。 アップストリーム ブロックにハッシュ
$remote_addr;
を追加するだけでセッションの永続性が有効になり、特定のクライアント IP アドレスから新しい接続が到着するたびに同じ MQTT サーバーが選択されます。
しかし、特にセルラー ネットワーク (GSM や LTE など) を使用している場合、IoT デバイスが同じ IP アドレスから再接続することを期待することはできません。 同じクライアントが同じ MQTT サーバーに再接続することを保証するには、ハッシュ アルゴリズムのキーとして MQTT クライアント識別子を使用する必要があります。
MQTT ClientId は初期CONNECT
パケットの必須要素です。つまり、パケットがアップストリーム サーバーにプロキシされる前に NGINX Plus で使用できます。 NGINX JavaScript を使用してCONNECT
パケットを解析し、ClientId を変数として抽出します。この変数はハッシュ
ディレクティブで使用して、MQTT 固有のセッション永続性を実装できます。
NGINX JavaScript は、「NGINX ネイティブ」のプログラム構成言語です。 これは、サーバー側のユースケースとリクエストごとの処理向けに特別に設計された、NGINX および NGINX Plus 用の独自の JavaScript 実装です。 MQTT のセッション永続性の実装に適した 3 つの重要な特性があります。
CONNECT
パケットの実際の解析には、20 行未満のコードが必要です。NGINX JavaScript を有効にする手順については、付録 2 を参照してください。
このユースケースの NGINX Plus 構成は比較的シンプルです。 次の設定は、簡潔にするためにヘルスチェックを削除した、 「アクティブ ヘルス チェックを使用した負荷分散」の例の修正バージョンです。
まず、 js_import
ディレクティブを使用して NGINX JavaScript コードの場所を指定します。 js_set
ディレクティブは、 $mqtt_client_id
変数を評価する必要がある場合に、NGINX Plus にsetClientId
関数を呼び出すように指示します。 この変数を 5 行目のmqttログ形式に追加することで、アクセス ログに詳細を追加します。
12 行目で、ハッシュ
ディレクティブで$mqtt_client_id を
キーとして指定して、セッションの永続性を有効にします。 上流サーバーに障害が発生した場合、そのサーバーで既に確立されているセッションに影響を与えることなく、そのサーバーに割り当てられたトラフィックが残りのサーバー間で均等に分散されるように、 consistent
パラメータを使用していることに注意してください。 一貫性のあるハッシュについては、 Web キャッシュのシャーディングに関するブログ投稿でさらに詳しく説明されています。ここでも原則と利点が同様に当てはまります。
js_preread
ディレクティブ (18 行目) は、リクエスト処理の事前読み取りフェーズで実行される NGINX JavaScript 関数を指定します。 事前読み取りフェーズは、すべてのパケット (両方向) に対してトリガーされ、プロキシの前に実行されるため、アップストリーム
ブロックで必要なときに$mqtt_client_id
の値が使用できるようになります。
MQTT ClientId を抽出するための JavaScript をmqtt.jsファイルで定義します。このファイルは、NGINX Plus 構成ファイル ( stream_mqtt_session_persistence.conf ) のjs_import
ディレクティブによってロードされます。
主な関数getClientId() は
4 行目に宣言されています。 現在の TCP セッションを表すs
というオブジェクトが渡されます。 セッション オブジェクトには多数のプロパティがあり、そのうちのいくつかがこの関数で使用されます。
5 行目から 9 行目は、現在のパケットがクライアントから最初に受信されるパケットであることを確認します。 後続のクライアント メッセージとサーバー応答は無視されるため、接続が確立されると、トラフィック フローに追加のオーバーヘッドは発生しません。
10 行目から 24 行目では、MQTT ヘッダーを調べて、パケットがCONNECT
タイプであることを確認し、MQTT ペイロードがどこから始まるかを判断します。
27 行目から 32 行目では、ペイロードから ClientId を抽出し、その値を JavaScript グローバル変数client_id_str
に格納します。 この変数は、 setClientId
関数 (43 ~ 45 行目) を使用して NGINX 構成にエクスポートされます。
ここで、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.log 192.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 [2017年3月23日:12:24:35 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017年3月23日:12:24:37 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017年3月23日:12:24:38 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017年3月23日:12:24:42 +0000] TCP 200 23 4 127.0.0.1:18832 バー 192.168.91.1 [2017 年 3 月 23 日:12:24:44 +0000] TCP 200 23 4 127.0.0.1:18832 バー 192.168.91.1 [2017 年 3 月 23 日:12:24:47 +0000] TCP 200 23 4 127.0.0.1:18833 バズ 192.168.91.1 [2017 年 3 月 23 日:12:24:48 +0000] TCP 200 23 4 127.0.0.1:18833 バズ
セッション永続性やその他の負荷分散アルゴリズムを使用するかどうかに関係なく、MQTT ClientId がアクセス ログ行に表示されるという利点もあることに注意してください。
2 部構成のシリーズの最初の投稿では、NGINX Plus がアクティブ ヘルス チェックを使用して IoT アプリケーションの可用性と信頼性を向上させる方法と、NGINX JavaScript が TCP トラフィックのセッション永続性などのレイヤー 7 ロード バランシング機能を提供することで NGINX Plus を拡張する方法を説明しました。 パート 2では、NGINX Plus が TLS 終了とクライアント認証をオフロードすることで、IoT アプリケーションをより安全にする方法について説明します。
NGINX JavaScript と組み合わせても、単独でも、NGINX Plus は本来持つ高いパフォーマンスと効率性により、IoT インフラストラクチャのソフトウェア ロードバランサーとして最適です。
NGINX Plus で NGINX JavaScript を試すには、 30 日間の無料トライアルを開始するか、お問い合わせの上、使用事例についてご相談ください。
付録
テスト環境は分離され、繰り返し実行できるように仮想マシンにインストールしました。 ただし、物理的な「ベアメタル」サーバーにインストールできない理由はありません。
NGINX Plus 管理者ガイドの手順を参照してください。
任意の MQTT サーバーを使用できますが、このテスト環境は HiveMQ (ここからダウンロード) に基づいています。 この例では、各ノードの Docker コンテナを使用して、単一のホストに HiveMQ をインストールします。 以下の手順は、「Docker を使用した HiveMQ のデプロイ」から抜粋したものです。
hivemq.zipと同じディレクトリに HiveMQ 用の Dockerfile を作成します。
hivemq.zipと Dockerfile が含まれるディレクトリで作業して、Docker イメージを作成します。
ビルドが成功しました。
それぞれ異なるポートで公開される 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 psコンテナ ID イメージ ... ポート名 17303b900b64 hivemq:latest ... 0.0.0.0:18833->1883/tcp node3 47992b1f4910 hivemq:最新... 0.0.0.0:18832->1883/tcp node2 ff2c012c595a hivemq:最新... 0.0.0.0:18831->1883/tcp ノード1
Mosquitto コマンドライン クライアントは、プロジェクトの Web サイトからダウンロードできます。 Homebrewがインストールされている Mac ユーザーは、次のコマンドを実行できます。
$ brew インストール mosquitto
簡単な公開メッセージを Docker イメージの 1 つに送信して、Mosquitto クライアントと HiveMQ のインストールをテストします。
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 18831クライアント thing001 が CONNECT を送信中 クライアント thing001 が CONNACK を受信中 クライアント thing001 が PUBLISH を送信中 (d0, q0, r0, m1, 'topic/test', ...) (7バイト)) クライアントthing001がDISCONNECTを送信
[ngx_snippet name=’njs-enable-instructions’]
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"