ブログ | NGINX

IoT 向け NGINX Plus: 負荷分散 MQTT

NGINX-F5 水平黒タイプ RGB の一部
リアム・クリリー サムネイル
リアム・クリリー
2017 年 3 月 23 日公開

インターネット上で最もアクセスの多い 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インスタンスです。

MQTTロードバランシングとセッション持続性のテスト環境では、NGINX PlusをMQTTクライアントとDockerコンテナ内の3つのHiveMQサーバー間のTCPロードバランサーとして配置します。
MQTT 負荷分散とセッション持続性のテスト環境

NGINX Plus は、MQTT ブローカーのリバース プロキシおよびロード バランサとして機能し、デフォルトの MQTT ポート 1883 をリッスンします。 これにより、クライアントに対してシンプルで一貫性のあるインターフェースが提供され、バックエンドの MQTT ノードはクライアントに何ら影響を与えることなくスケールアウト (さらにはオフラインに) できます。 テスト環境内の IoT デバイスを表すクライアントとして、 Mosquittoコマンドライン ツールを使用します。

この投稿とパート 2 のすべてのユースケースではこのテスト環境が使用され、すべての構成は図に示されているアーキテクチャに直接適用されます。 テスト環境の構築に関する詳細な手順については、付録 1 を参照してください。

高可用性のためのアクティブヘルスチェックによる MQTT のロードバランシング

ロード バランサの主な機能は、クライアントに影響を与えずにバックエンド サーバーを追加、削除、またはオフラインにできるように、アプリケーションに高可用性を提供することです。 これを確実に実行するには、各バックエンド サーバーの可用性をプロアクティブに調査するヘルス チェックが必要です。 アクティブ ヘルス チェックを使用すると、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進文字列20020000完全な 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) が選択されたこともわかります。 アクセス ログの次の行に示されているように、テストを繰り返すと、デフォルトのラウンド ロビン ロード バランシング アルゴリズムによって、 node1node2node3 が順番に選択されます。

$ 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 モジュールを使用したセッション持続のための MQTT のロード バランシング

[編集者注– 以下の使用例は、NGINX JavaScript モジュールの多くの使用例の 1 つにすぎません。 完全なリストについては、 「NGINX JavaScript モジュールの使用例」を参照してください。

このセクションのコードは、ブログが最初に公開されてから NGINX JavaScript の実装に加えられた変更を反映するために、次のように更新されています。

  • NGINX JavaScript 0.2.4で導入された Stream モジュールのリファクタリングされたセッション ( 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 クライアント ID に基づくセッションの永続性

MQTT ClientId は初期CONNECTパケットの必須要素です。つまり、パケットがアップストリーム サーバーにプロキシされる前に NGINX Plus で使用できます。 NGINX JavaScript を使用してCONNECTパケットを解析し、ClientId を変数として抽出します。この変数はハッシュディレクティブで使用して、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変数を評価する必要がある場合に、NGINX Plus にsetClientId関数を呼び出すように指示します。 この変数を 5 行目のmqttログ形式に追加することで、アクセス ログに詳細を追加します。

12 行目で、ハッシュディレクティブで$mqtt_client_id をキーとして指定して、セッションの永続性を有効にします。 上流サーバーに障害が発生した場合、そのサーバーで既に確立されているセッションに影響を与えることなく、そのサーバーに割り当てられたトラフィックが残りのサーバー間で均等に分散されるように、 consistentパラメータを使用していることに注意してください。 一貫性のあるハッシュについては、 Web キャッシュのシャーディングに関するブログ投稿でさらに詳しく説明されています。ここでも原則と利点が同様に当てはまります。

js_prereadディレクティブ (18 行目) は、リクエスト処理の事前読み取りフェーズで実行される NGINX JavaScript 関数を指定します。 事前読み取りフェーズは、すべてのパケット (両方向) に対してトリガーされ、プロキシの前に実行されるため、アップストリームブロックで必要なときに$mqtt_client_idの値が使用できるようになります。

セッション持続のための NGINX JavaScript コード

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のインストール

NGINX Plus 管理者ガイドの手順を参照してください。

HiveMQのインストール

任意の MQTT サーバーを使用できますが、このテスト環境は HiveMQ (ここからダウンロード) に基づいています。 この例では、各ノードの Docker コンテナを使用して、単一のホストに HiveMQ をインストールします。 以下の手順は、「Docker を使用した HiveMQ のデプロイ」から抜粋したものです。

  1. hivemq.zipと同じディレクトリに HiveMQ 用の Dockerfile を作成します。

     

  2. hivemq.zipと Dockerfile が含まれるディレクトリで作業して、Docker イメージを作成します。

    ビルドが成功しました。
    
  3. それぞれ異なるポートで公開される 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
    
  4. 3 つの HiveMQ ノードがすべて実行されていることを確認します。 (次のサンプル出力では、読みやすくするためにCOMMANDCREATED 、および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のインストール

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 コンテンツにリダイレクトされます。"