Wenn es um die am häufigsten genutzten Websites im Internet geht, dominieren NGINX und NGINX Plus den Markt. Tatsächlich betreibt NGINX mehr der über eine Million meistgenutzten Websites der Welt als jeder andere Webserver. Seine Fähigkeit, über eine Million gleichzeitige Verbindungen auf einem einzigen Server zu verarbeiten, hat zu seiner Verbreitung durch „Hyperscale“-Websites und -Apps wie Airbnb, Netflix und Uber geführt.
Obwohl NGINX Plus am häufigsten als Webserver, HTTP-Reverse-Proxy und Load Balancer bekannt ist, ist es auch ein voll ausgestatteter Application Delivery Controller (ADC) mit Unterstützung für TCP- und UDP-Anwendungen. Seine ereignisgesteuerte Architektur und alle anderen Eigenschaften, die ihn für HTTP-Anwendungsfälle erfolgreich gemacht haben, sind gleichermaßen auf das Internet der Dinge (IoT) anwendbar.
In diesem Artikel zeigen wir, wie NGINX Plus zum Lastenausgleich des MQTT- Verkehrs verwendet werden kann. MQTT wurde ursprünglich 1999 für die Kommunikation mit entfernten Ölfeldern veröffentlicht. Es wurde 2013 für IoT-Anwendungsfälle aktualisiert und ist seitdem das Protokoll der Wahl für viele IoT-Bereitstellungen. Produktions-IoT-Bereitstellungen mit Millionen von Geräten erfordern von einem Load Balancer hohe Leistung und erweiterte Funktionalität. In dieser zweiteiligen Blog-Beitragsserie besprechen wir die folgenden erweiterten Anwendungsfälle.
Um die Funktionen von NGINX Plus zu erkunden, verwenden wir eine einfache Testumgebung, die die Schlüsselkomponenten einer IoT-Umgebung mit einem Cluster von MQTT-Brokern darstellt. Die MQTT-Broker in dieser Umgebung sind HiveMQ- Instanzen, die in Docker-Containern ausgeführt werden.
NGINX Plus fungiert als Reverse-Proxy und Load Balancer für den MQTT-Broker und lauscht auf dem MQTT-Standardport 1883. Dies stellt eine einfache und konsistente Schnittstelle zum Client bereit, während die MQTT-Knoten im Backend skaliert (und sogar offline genommen) werden können, ohne den Client in irgendeiner Weise zu beeinträchtigen. Als Client verwenden wir das Kommandozeilentool Mosquitto , welches die IoT-Geräte in der Testumgebung darstellt.
Alle Anwendungsfälle in diesem Beitrag und Teil 2 verwenden diese Testumgebung und alle Konfigurationen gelten direkt für die in der Abbildung gezeigte Architektur. Vollständige Anweisungen zum Erstellen der Testumgebung finden Sie in Anhang 1 .
Eine Hauptfunktion eines Load Balancers besteht darin, eine hohe Verfügbarkeit für die Anwendung bereitzustellen, sodass Backend-Server hinzugefügt, entfernt oder offline geschaltet werden können, ohne dass dies Auswirkungen auf den Client hat. Um dies zuverlässig zu gewährleisten, sind Integritätsprüfungen erforderlich, die jeden einzelnen Backend-Server proaktiv auf seine Verfügbarkeit prüfen. Durch aktive Integritätsprüfungen kann NGINX Plus ausgefallene Server aus der Lastausgleichsgruppe entfernen, bevor tatsächliche Clientanforderungen sie erreichen.
Die Nützlichkeit einer Integritätsprüfung hängt davon ab, wie genau sie den tatsächlichen Anwendungsverkehr simuliert und die Antwort analysiert. Einfache Server-Aktivitätsprüfungen wie ein Ping stellen nicht sicher, dass der Backend-Dienst ausgeführt wird. Durch die Prüfung geöffneter TCP-Ports wird nicht sichergestellt, dass die Anwendung selbst fehlerfrei ist. Hier konfigurieren wir einen grundlegenden Lastausgleich für die Testumgebung mit einem Integritätscheck, der sicherstellt, dass jeder Backend-Server neue MQTT-Verbindungen akzeptieren kann.
Wir nehmen Änderungen in zwei Konfigurationsdateien vor.
In der Hauptdatei nginx.conf fügen wir den folgenden Stream-
Block und die folgende Include
-Direktive ein, damit NGINX Plus die Konfiguration für den TCP-Lastausgleich aus einer oder mehreren Dateien im Unterverzeichnis stream_conf.d einliest, das sich im selben Verzeichnis wie nginx.conf befindet. Wir tun dies, anstatt die eigentliche Konfiguration in nginx.conf aufzunehmen.
Dann erstellen wir im selben Verzeichnis wie nginx.conf das Verzeichnis stream_conf.d, um unsere TCP- und UDP-Konfigurationsdateien zu enthalten. Beachten Sie, dass wir das bereits vorhandene Verzeichnis conf.d nicht verwenden, da es standardmäßig für den HTTP
-Konfigurationskontext reserviert ist und das Hinzufügen der Stream
-Konfiguration dort daher fehlschlägt.
In stream_mqtt_healthcheck.conf definieren wir zunächst das Zugriffsprotokollformat für MQTT-Verkehr (Zeilen 1–2). Dies ähnelt absichtlich dem allgemeinen HTTP-Protokollformat, sodass die resultierenden Protokolle in Protokollanalysetools importiert werden können.
Als nächstes definieren wir die Upstream-
Gruppe namens hive_mq (Zeilen 4–9), die drei MQTT-Server enthält. In unserer Testumgebung sind sie jeweils auf dem lokalen Host mit einer eindeutigen Portnummer erreichbar. Die Zonendirektive
definiert eine Speichermenge, die von allen NGINX Plus-Arbeitsprozessen gemeinsam genutzt wird, um den Status und die Integritätsinformationen des Lastausgleichs aufrechtzuerhalten.
Der Match-
Block (Zeilen 11–15) definiert den Health-Check, mit dem die Verfügbarkeit von MQTT-Servern getestet wird. Die Sendedirektive
ist eine hexadezimale Darstellung eines vollständigen MQTT CONNECT-
Pakets mit einer Clientkennung (ClientId) des Nginx
-
Integritätschecks
. Dies wird an jeden der in der Upstream-Gruppe definierten Server gesendet, wenn die Integritätsprüfung ausgelöst wird. Die entsprechende „Expect
“-Direktive beschreibt die Antwort, die der Server zurückgeben muss, damit NGINX Plus ihn als fehlerfrei betrachtet. Hier die 4-Byte-Hexadezimalzeichenfolge20
02
00
00
ist ein vollständiges MQTT- CONNACK-
Paket. Der Empfang dieses Pakets zeigt, dass der MQTT-Server neue Client-Verbindungen empfangen kann.
Der Serverblock
(Zeilen 17–25) konfiguriert, wie NGINX Plus mit Clients umgeht. NGINX Plus lauscht auf dem MQTT-Standardport 1883 und leitet den gesamten Datenverkehr an die Upstream-Gruppe hive_mq weiter (Zeile 19). Die Direktive „health_check“
gibt an, dass Integritätsprüfungen für die Upstream-Gruppe durchgeführt werden (mit der Standardfrequenz von fünf Sekunden) und dass die durch den Match-
Block „mqtt_conn“ definierte Prüfung verwendet wird.
Um zu testen, ob diese Grundkonfiguration funktioniert, können wir den Mosquitto-Client verwenden, um einige Testdaten in unserer Testumgebung zu veröffentlichen.
$ mosquitto_pub -d -h mqtt.example.com -t „topic/test“ -m „test123“ -i „thing001“ Client thing001 sendet CONNECT. Client thing001 hat CONNACK empfangen. Client thing001 sendet PUBLISH (d0, q0, r0, m1, „topic/test“, …) (7 Bytes)) Client thing001 sendet 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
Aus der Zeile des Zugriffsprotokolls geht hervor, dass NGINX Plus insgesamt 23 Bytes empfangen hat und 4 Bytes an den Client gesendet wurden (das CONNACK-
Paket). Wir können auch sehen, dass MQTT- Knoten1 ausgewählt wurde (Port 18831). Wie die folgenden Zeilen aus dem Zugriffsprotokoll zeigen, wählt der standardmäßige Round-Robin-Lastausgleichsalgorithmus bei der Wiederholung des Tests nacheinander Knoten1 , Knoten2 und Knoten3 aus.
$ tail --lines=4 /var/log/nginx/mqtt_access.log 192.168.91.1 [23.03.2017:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831 192.168.91.1 [23.03.2017:11:42:26 +0000] TCP 200 23 4 127.0.0.1:18832 192.168.91.1 [23.03.2017:11:42:27 +0000] TCP 200 23 4 127.0.0.1:18833 192.168.91.1 [23/Mär/2017:11:42:28 +0000] TCP 200 23 4 127.0.0.1:18831
[ Herausgeber – Der folgende Anwendungsfall ist nur einer von vielen für das NGINX JavaScript-Modul. Eine vollständige Liste finden Sie unter Anwendungsfälle für das NGINX-JavaScript-Modul .]
Der Code in diesem Abschnitt wird wie folgt aktualisiert, um Änderungen an der Implementierung von NGINX JavaScript seit der ursprünglichen Veröffentlichung des Blogs widerzuspiegeln:
die Sitzungen
) für das Stream-Modul zu verwenden, das in NGINX JavaScript 0.2.4 eingeführt wurde.js_import
, die die Direktive js_include
in NGINX Plus R23 und höher ersetzt. Weitere Informationen finden Sie in der Referenzdokumentation für das NGINX-JavaScript-Modul . Im Abschnitt „Beispielkonfiguration“ wird die richtige Syntax für die NGINX-Konfiguration und JavaScript-Dateien gezeigt.Round-Robin-Lastausgleich ist ein effektiver Mechanismus zum Verteilen von Clientverbindungen auf eine Gruppe von Servern. Es gibt jedoch mehrere Gründe, warum es für MQTT-Verbindungen nicht ideal ist.
MQTT-Server erwarten häufig eine langlebige Verbindung zwischen Client und Server und auf dem Server können umfangreiche Sitzungszustände aufgebaut werden. Leider kommt es aufgrund der Beschaffenheit von IoT-Geräten und der von ihnen genutzten IP-Netzwerke immer wieder zu Verbindungsabbrüchen, sodass manche Clients häufig eine neue Verbindung herstellen müssen. NGINX Plus kann seinen Hash -Lastausgleichsalgorithmus verwenden, um einen MQTT-Server basierend auf der Client-IP-Adresse auszuwählen. Durch einfaches Hinzufügen des Hashs
„$remote_addr;“
zum Upstream-Block wird die Sitzungspersistenz aktiviert, sodass jedes Mal, wenn eine neue Verbindung von einer bestimmten Client-IP-Adresse eingeht, derselbe MQTT-Server ausgewählt wird.
Wir können uns jedoch nicht darauf verlassen, dass IoT-Geräte von derselben IP-Adresse aus die Verbindung wiederherstellen, insbesondere wenn sie Mobilfunknetze (z. B. GSM oder LTE) verwenden. Um sicherzustellen, dass derselbe Client die Verbindung zum selben MQTT-Server wiederherstellt, müssen wir die MQTT-Clientkennung als Schlüssel für den Hashing-Algorithmus verwenden.
Die MQTT-Client-ID ist ein obligatorisches Element des anfänglichen CONNECT
-Pakets. Dies bedeutet, dass sie NGINX Plus zur Verfügung steht, bevor das Paket an den Upstream-Server weitergeleitet wird. Wir können NGINX JavaScript verwenden, um das CONNECT
-Paket zu analysieren und die Client-ID als Variable zu extrahieren, die dann von der Hash
-Direktive verwendet werden kann, um MQTT-spezifische Sitzungspersistenz zu implementieren.
NGINX JavaScript ist die „native“ programmierbare Konfigurationssprache von NGINX. Es handelt sich um eine einzigartige JavaScript-Implementierung für NGINX und NGINX Plus, die speziell für serverseitige Anwendungsfälle und die Verarbeitung einzelner Anfragen entwickelt wurde. Es verfügt über drei Hauptmerkmale, die es für eine Implementierung der Sitzungspersistenz für MQTT geeignet machen:
CONNECT-
Pakets erfordert weniger als 20 Codezeilen.Anweisungen zum Aktivieren von NGINX JavaScript finden Sie in Anhang 2 .
Die NGINX Plus-Konfiguration für diesen Anwendungsfall bleibt relativ einfach. Die folgende Konfiguration ist eine geänderte Version des Beispiels im Abschnitt „Lastenausgleich mit aktiven Integritätsprüfungen“ , wobei die Integritätsprüfungen der Übersichtlichkeit halber entfernt wurden.
Wir beginnen, indem wir den Speicherort des NGINX-JavaScript-Codes mit der Direktive js_import
angeben. Die js_set
-Direktive weist NGINX Plus an, die Funktion setClientId
aufzurufen, wenn die Variable $mqtt_client_id
ausgewertet werden muss. Wir fügen dem Zugriffsprotokoll weitere Details hinzu, indem wir diese Variable in Zeile 5 an das MQTT- Protokollformat anhängen.
Wir aktivieren die Sitzungspersistenz in Zeile 12 mit der Hash-
Direktive, die $mqtt_client_id
als Schlüssel angibt. Beachten Sie, dass wir den konsistenten
Parameter verwenden, damit bei einem Ausfall eines Upstream-Servers sein Anteil des Datenverkehrs gleichmäßig auf die verbleibenden Server verteilt wird, ohne dass dies Auswirkungen auf die auf diesen Servern bereits hergestellten Sitzungen hat. Konsistentes Hashing wird in unserem Blogbeitrag über das Sharding eines Web-Caches ausführlicher erläutert – die Prinzipien und Vorteile gelten hier gleichermaßen.
Die Direktive js_preread
(Zeile 18) gibt die NGINX-JavaScript-Funktion an, die in der Preread-Phase der Anforderungsverarbeitung ausgeführt wird. Die Preread-Phase wird für jedes Paket (in beide Richtungen) ausgelöst und erfolgt vor dem Proxying, sodass der Wert von $mqtt_client_id
verfügbar ist, wenn er im Upstream-
Block benötigt wird.
Wir definieren das JavaScript zum Extrahieren der MQTT-ClientId in der Datei mqtt.js , die von der Direktive js_import
in der NGINX Plus-Konfigurationsdatei ( stream_mqtt_session_persistence.conf ) geladen wird.
Die primäre Funktion getClientId()
wird in Zeile 4 deklariert. Ihm wird das Objekt mit dem Namen s
übergeben, das die aktuelle TCP-Sitzung darstellt. Das Sitzungsobjekt hat zahlreiche Eigenschaften , von denen einige in dieser Funktion verwendet werden.
Die Zeilen 5–9 stellen sicher, dass das aktuelle Paket als erstes vom Client empfangen wird. Nachfolgende Client-Nachrichten und Server-Antworten werden ignoriert, so dass nach dem Herstellen einer Verbindung kein zusätzlicher Overhead im Datenverkehr entsteht.
In den Zeilen 10–24 wird der MQTT-Header untersucht, um sicherzustellen, dass es sich bei dem Paket um einen CONNECT
-Typ handelt, und um zu bestimmen, wo die MQTT-Nutzlast beginnt.
Die Zeilen 27–32 extrahieren die Client-ID aus der Nutzlast und speichern den Wert in der globalen JavaScript-Variable client_id_str
. Diese Variable wird dann mit der Funktion setClientId
(Zeilen 43–45) in die NGINX-Konfiguration exportiert.
Jetzt können wir den Mosquitto-Client erneut verwenden, um die Sitzungspersistenz zu testen, indem wir eine Reihe von MQTT-Veröffentlichungsanforderungen mit drei verschiedenen ClientId-Werten senden (die Option -i
).
$ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Foo" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Bar" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test " -m "Test123" -i "Baz" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Foo" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Foo" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Bar" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Bar" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Baz" $ mosquitto_pub -h mqtt.example.com -t "Thema/Test" -m "Test123" -i "Baz"
Die Untersuchung des Zugriffsprotokolls zeigt, dass ClientId foo immer eine Verbindung zu Knoten1 (Port 18831) herstellt, ClientId bar immer eine Verbindung zu Knoten2 (Port 18832) herstellt und ClientId baz immer eine Verbindung zu Knoten3 (Port 18833) herstellt.
$ tail /var/log/nginx/mqtt_access.log 192.168.91.1 [23.03.2017:12:24:24 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [23.03.2017:12:24:28 +0000] TCP 200 23 4 127.0.0.1:18832 bar 192.168.91.1 [23.03.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
Beachten Sie, dass wir auch den Vorteil haben, dass die MQTT-Client-ID in den Zugriffsprotokollzeilen angezeigt wird, unabhängig davon, ob wir Sitzungspersistenz oder einen anderen Lastausgleichsalgorithmus verwenden.
In diesem ersten Beitrag einer zweiteiligen Reihe haben wir gezeigt, wie NGINX Plus aktive Integritätsprüfungen nutzt, um die Verfügbarkeit und Zuverlässigkeit von IoT-Anwendungen zu verbessern, und wie NGINX JavaScript NGINX Plus erweitern kann, indem es eine Layer-7-Lastausgleichsfunktion wie Sitzungspersistenz für TCP-Verkehr bereitstellt. Im zweiten Teil untersuchen wir, wie NGINX Plus Ihre IoT-Anwendungen durch Auslagerung der TLS-Terminierung und Authentifizierung von Clients sicherer machen kann.
In Kombination mit NGINX JavaScript oder allein eignet sich NGINX Plus aufgrund seiner inhärenten hohen Leistung und Effizienz ideal als Software-Load Balancer für Ihre IoT-Infrastruktur.
Um NGINX JavaScript mit NGINX Plus auszuprobieren, starten Sie Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen .
Anhänge
Wir haben die Testumgebung auf einer virtuellen Maschine installiert, damit sie isoliert und wiederholbar ist. Es gibt jedoch keinen Grund, warum Sie es nicht auf einem physischen „Bare-Metal“-Server installieren können.
Anweisungen finden Sie im NGINX Plus-Administratorhandbuch .
Jeder MQTT-Server kann verwendet werden, aber diese Testumgebung basiert auf HiveMQ ( hier herunterladen ). In diesem Beispiel installieren wir HiveMQ auf einem einzelnen Host und verwenden Docker-Container für jeden Knoten. Die folgenden Anweisungen wurden aus „Bereitstellen von HiveMQ mit Docker“ übernommen.
Erstellen Sie eine Dockerfile für HiveMQ im selben Verzeichnis wie hivemq.zip .
Erstellen Sie das Docker-Image, indem Sie im Verzeichnis arbeiten, das hivemq.zip und die Docker-Datei enthält.
$ docker build -t hivemq:latest.
Erstellen Sie drei HiveMQ-Knoten, die jeweils auf einem anderen Port verfügbar sind.
$ 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
Überprüfen Sie, ob alle drei HiveMQ-Knoten ausgeführt werden. (In der folgenden Beispielausgabe wurden die Spalten COMMAND
, CREATED
und STATUS
zur besseren Lesbarkeit weggelassen.)
$ docker ps CONTAINER-ID-BILD … PORTNAMEN 17303b900b64 hivemq:neueste … 0.0.0.0:18833->1883/tcp node3 47992b1f4910 hivemq:neueste … 0.0.0.0:18832->1883/tcp node2 ff2c012c595a hivemq:neueste … 0.0.0.0:18831->1883/tcp-Knoten1
Der Mosquitto-Befehlszeilenclient kann von der Projektwebsite heruntergeladen werden. Mac-Benutzer mit installiertem Homebrew können den folgenden Befehl ausführen.
$ brew install mosquitto
Testen Sie den Mosquitto-Client und die HiveMQ-Installation, indem Sie eine einfache Veröffentlichungsnachricht an eines der Docker-Images senden.
$ mosquitto_pub -d -h mqtt.example.com -t „topic/test“ -m „test123“ -i „thing001“ -p 18831 Client thing001 sendet CONNECT. Client thing001 hat CONNACK empfangen. Client thing001 sendet PUBLISH (d0, q0, r0, m1, „topic/test“, …) (7 Bytes)) Client thing001 sendet DISCONNECT
[ngx_snippet name=’njs-enable-instructions’]
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."