NGINX Plus pour l’IoT : équilibrage de charge MQTT

NGINX | 23 mars 2017

Sur le marché des sites web les plus fréquentés d’Internet, NGINX et NGINX Plus occupent une place dominante. En effet, NGINX propulse plus de sites parmi le million de sites les plus fréquentés au monde que tout autre serveur web. Sa capacité à gérer plus d’un million de connexions simultanées sur un seul serveur lui a valu d’être adopté par des sites et applications à très grande échelle tels qu’Airbnb, Netflix et Uber.

Bien que NGINX Plus soit principalement connu comme serveur web, proxy inverse HTTP et équilibreur de charge, il s’agit également d’un contrôleur de mise à disposition d’applications (ADC) complet prenant en charge les applications TCP et UDP. Son architecture événementielle et toutes les autres caractéristiques qui ont fait son succès dans les cas d’utilisation HTTP s’appliquent tout autant à l’Internet des objets (IoT).

Dans cet article, nous expliquons comment utiliser NGINX Plus pour répartir la charge du trafic MQTT. MQTT a été publié pour la première fois en 1999 afin de permettre les communications avec des champs pétrolifères isolés. Il a été adapté aux cas d’usage de l’IoT en 2013 et est depuis devenu le protocole de prédilection pour de nombreux déploiements IoT. Les déploiements IoT en production comptant des millions d’appareils exigent d’un répartiteur de charge des performances élevées et des fonctionnalités avancées. Dans cette série de deux articles de blog, nous aborderons les cas d’usage avancés suivants.

L’environnement de test

Pour explorer les fonctionnalités de NGINX Plus, nous utiliserons un environnement de test simple qui reproduit les principaux composants d’un environnement IoT comprenant un cluster de brokers MQTT. Dans cet environnement, les brokers MQTT sont des instances de HiveMQ exécutées dans des conteneurs Docker.

Environnement de test pour l’équilibrage de charge MQTT et la persistance des sessions

NGINX Plus fait office de proxy inverse et d’équilibreur de charge pour le broker MQTT, en écoutant sur le port MQTT par défaut 1883. Le client bénéficie ainsi d’une interface simple et cohérente, tandis que les nœuds MQTT backend peuvent être mis à l’échelle (et même hors ligne) sans aucune incidence sur celui-ci. Nous utilisons l’outil en ligne de commande Mosquitto command line tool comme client, lequel représente les appareils IoT dans l’environnement de test.

Tous les cas d’utilisation présentés dans cet article et dans la deuxième partie reposent sur cet environnement de test, et toutes les configurations s’appliquent directement à l’architecture illustrée dans la figure. Pour obtenir des instructions complètes sur la création de l’environnement de test, consultez l’annexe 1.

Équilibrage de charge MQTT avec des contrôles d’intégrité actifs pour une haute disponibilité

L’une des principales fonctions d’un répartiteur de charge est d’assurer la haute disponibilité de l’application afin que des serveurs backend puissent être ajoutés, supprimés ou mis hors ligne sans incidence sur le client. Pour garantir cette fiabilité, des contrôles d’intégrité sondent de manière proactive chaque serveur backend afin de vérifier sa disponibilité. Grâce aux contrôles d’intégrité actifs, NGINX Plus peut retirer les serveurs défaillants du groupe d’équilibrage de charge avant que les requêtes réelles des clients ne leur parviennent.

L’utilité d’un contrôle d’intégrité dépend de la précision avec laquelle il simule le trafic réel de l’application et analyse la réponse. Les contrôles élémentaires de disponibilité du serveur, tels qu’un ping, ne garantissent pas que le service backend est opérationnel. Les contrôles d’ouverture des ports TCP ne garantissent pas non plus le bon fonctionnement de l’application elle-même. Nous configurons ici un équilibrage de charge de base pour l’environnement de test, avec un contrôle d’intégrité qui vérifie que chaque serveur backend est en mesure d’accepter de nouvelles connexions MQTT.

Nous modifions deux fichiers de configuration.

Dans le fichier principal nginx.conf, nous ajoutons le bloc stream suivant ainsi que la directive include afin que NGINX Plus lise la configuration de l’équilibrage de charge TCP à partir d’un ou de plusieurs fichiers situés dans le sous-répertoire stream_conf.d, qui se trouve dans le même répertoire que nginx.conf. Nous procédons ainsi plutôt que d’inclure la configuration proprement dite dans nginx.conf.

Chargement de l’exemple de code…

Ensuite, dans le même répertoire que nginx.conf, nous créons le répertoire stream_conf.d destiné à contenir nos fichiers de configuration TCP et UDP. Notez que nous n’utilisons pas le répertoire conf.d préexistant, car il est réservé par défaut au contexte de configuration http ; l’ajout d’une configuration stream dans ce répertoire échouerait donc.

Chargement de l’exemple de code…

Dans stream_mqtt_healthcheck.conf, nous définissons d’abord le format du journal d’accès pour le trafic MQTT (lignes 1 à 2). Celui-ci est volontairement similaire au format de journal commun HTTP afin que les journaux obtenus puissent être importés dans des outils d’analyse de journaux.

Nous définissons ensuite le groupe upstream nommé hive_mq (lignes 4 à 9), qui contient trois serveurs MQTT. Dans notre environnement de test, chacun d’eux est accessible sur localhost avec un numéro de port unique. La directive zone définit une quantité de mémoire partagée entre tous les processus de travail NGINX Plus afin de conserver l’état de l’équilibrage de charge et les informations d’intégrité.

Le bloc match (lignes 11 à 15) définit le contrôle d’intégrité utilisé pour vérifier la disponibilité des serveurs MQTT. La directive send est une représentation hexadécimale d’un paquet MQTT CONNECT complet dont l’identifiant client (ClientId) est nginx health check. Celui-ci est envoyé à chacun des serveurs définis dans le groupe en amont chaque fois que le contrôle d’intégrité est déclenché. La directive expect correspondante décrit la réponse que le serveur doit renvoyer pour que NGINX Plus le considère comme opérationnel. Ici, la chaîne hexadécimale de 4 octets 20 02 00 00 constitue un paquet MQTT CONNACK complet. La réception de ce paquet démontre que le serveur MQTT est capable d’accepter de nouvelles connexions clientes.

Le bloc server (lignes 17 à 25) configure la manière dont NGINX Plus gère les clients. NGINX Plus écoute sur le port MQTT par défaut, 1883, et transfère tout le trafic vers le groupe en amont hive_mq (ligne 19). La directive health_check indique que des contrôles d’intégrité sont effectués sur le groupe en amont (à la fréquence par défaut de cinq secondes) et que le contrôle défini par le bloc mqtt_conn match est utilisé.

Vérification de la configuration

Pour vérifier que cette configuration de base fonctionne, nous pouvons utiliser le client Mosquitto afin de publier des données de test dans notre environnement de test.

$ 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

La ligne du journal d’accès indique que NGINX Plus a reçu 23 octets au total et que 4 octets ont été envoyés au client (le paquet « CONNACK »). Nous pouvons également constater que le nœud MQTT node1 a été choisi (port 18831). Comme l’indiquent les lignes suivantes du journal d’accès, lorsque nous répétons le test, l’algorithme d’équilibrage de charge Round Robin par défaut sélectionne successivement node1, node2 et 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:18831

Équilibrage de charge MQTT pour la persistance des sessions avec le module JavaScript NGINX

[Note de la rédaction – Le cas d’utilisation suivant n’est que l’un des nombreux cas possibles pour le module JavaScript de NGINX. Pour obtenir la liste complète, consultez Cas d’utilisation du module JavaScript de NGINX.]

Le code de cette section a été mis à jour comme suit afin de tenir compte des modifications apportées à l’implémentation de NGINX JavaScript depuis la publication initiale de l’article de blog :

  • Pour utiliser l’objet session (s) remanié pour le module Stream, introduit dans NGINX JavaScript 0.2.4.
  • Pour utiliser la directive js_import, qui remplace la directive js_include dans NGINX Plus R23 et les versions ultérieures. Pour plus d’informations, consultez la documentation de référence du module JavaScript NGINX : la section « Exemple de configuration » présente la syntaxe correcte pour les fichiers de configuration NGINX et les fichiers JavaScript.

L’équilibrage de charge Round Robin est un mécanisme efficace pour répartir les connexions clientes entre un groupe de serveurs. Toutefois, il n’est pas idéal pour les connexions MQTT, et ce pour plusieurs raisons.

Les serveurs MQTT nécessitent souvent une connexion durable entre le client et le serveur, et un volume important d’informations d’état de session peut s’accumuler sur le serveur. Malheureusement, en raison de la nature des appareils IoT et des réseaux IP qu’ils utilisent, les connexions sont parfois interrompues, ce qui oblige certains clients à se reconnecter fréquemment. NGINX Plus peut utiliser son algorithme d’équilibrage de charge Hash pour sélectionner un serveur MQTT en fonction de l’adresse IP du client. Il suffit d’ajouter hash $remote_addr; au bloc upstream pour activer la persistance de session. Ainsi, chaque fois qu’une nouvelle connexion provient d’une adresse IP client donnée, le même serveur MQTT est sélectionné.

Mais nous ne pouvons pas compter sur le fait que les appareils IoT se reconnectent depuis la même adresse IP, surtout s’ils utilisent des réseaux cellulaires (par exemple, GSM ou LTE). Pour garantir qu’un même client se reconnecte au même serveur MQTT, nous devons utiliser l’identifiant client MQTT comme clé de l’algorithme de hachage.

Persistance de session basée sur le ClientId MQTT

Le ClientId MQTT est un élément obligatoire du paquet CONNECT initial, ce qui signifie qu’il est accessible à NGINX Plus avant que le paquet ne soit transmis au serveur en amont. Nous pouvons utiliser NGINX JavaScript pour analyser le paquet CONNECT et extraire le ClientId sous forme de variable, qui peut ensuite être utilisée par la directive hash afin de mettre en œuvre une persistance de session propre à MQTT.

NGINX JavaScript est le langage de configuration programmatique « natif de NGINX ». Il s’agit d’une implémentation unique de JavaScript pour NGINX et NGINX Plus, spécialement conçue pour les cas d’utilisation côté serveur et le traitement de chaque requête. Il présente trois caractéristiques clés qui le rendent adapté à la mise en œuvre de la persistance des sessions pour MQTT :

  • NGINX JavaScript est étroitement intégré aux phases de traitement de NGINX Plus, ce qui nous permet d’inspecter les paquets clients avant qu’ils ne soient répartis entre les serveurs du groupe en amont.
  • NGINX JavaScript utilise les méthodes JavaScript intégrées pour le traitement des chaînes de caractères et des nombres, ce qui permet d’analyser efficacement les protocoles de couche 4. L’analyse proprement dite du paquet MQTT CONNECT nécessite moins de 20 lignes de code.
  • NGINX JavaScript peut créer des variables accessibles à la configuration de NGINX Plus.

Pour savoir comment activer NGINX JavaScript, consultez l’annexe 2.

Configuration de NGINX Plus pour la persistance des sessions

La configuration de NGINX Plus pour ce cas d’usage reste relativement simple. La configuration suivante est une version modifiée de l’exemple présenté dans « Répartition de charge avec contrôles d’intégrité actifs », dont les contrôles d’intégrité ont été supprimés par souci de concision.

Chargement de l’exemple de code…

Nous commençons par indiquer l’emplacement du code JavaScript NGINX à l’aide de la directive « js_import ». La directive « js_set » indique à NGINX Plus d’appeler la fonction « setClientId » lorsqu’il doit évaluer la variable « $mqtt_client_id ». Nous ajoutons davantage de détails au journal d’accès en ajoutant cette variable au mqtt format de journalisation à la ligne 5.

Nous activons la persistance des sessions à la ligne 12 à l’aide de la directive hash qui spécifie $mqtt_client_id comme clé. Notez que nous utilisons le paramètre consistent afin que, si un serveur en amont tombe en panne, sa part du trafic soit répartie uniformément entre les serveurs restants sans affecter les sessions déjà établies sur ces serveurs. Le hachage cohérent est abordé plus en détail dans notre article de blog consacré au partitionnement d’un cache web – les principes et les avantages s’appliquent également ici.

La directive « js_preread » (ligne 18) spécifie la fonction JavaScript NGINX exécutée lors de la phase de prélecture du traitement des requêtes. La phase de prélecture est déclenchée pour chaque paquet (dans les deux sens) et intervient avant le proxy afin que la valeur de $mqtt_client_id soit disponible lorsqu’elle est requise dans le bloc upstream.

Code JavaScript NGINX pour la persistance des sessions

Nous définissons le code JavaScript permettant d’extraire le ClientId MQTT dans le fichier mqtt.js, qui est chargé par la directive js_import dans le fichier de configuration de NGINX Plus (stream_mqtt_session_persistence.conf).

Chargement de l’exemple de code…

La fonction principale, « getClientId() », est déclarée à la ligne 4. Elle reçoit en argument l’objet appelé « s », qui représente la session TCP actuelle. L’objet de session possède de nombreuses propriétés, dont plusieurs sont utilisées dans cette fonction.

Les lignes 5 à 9 vérifient que le paquet actuel est bien le premier reçu du client. Les messages suivants du client et les réponses du serveur sont ignorés afin qu’une fois la connexion établie, aucun traitement supplémentaire ne vienne alourdir le flux de trafic.

Chargement de l’exemple de code…

Les lignes 10 à 24 examinent l’en-tête MQTT afin de vérifier que le paquet est de type « CONNECT » et de déterminer où commence la charge utile MQTT.

Chargement de l’exemple de code…

Les lignes 27 à 32 extraient le ClientId de la charge utile et stockent sa valeur dans la variable globale JavaScript « client_id_str ». Cette variable est ensuite exportée vers la configuration NGINX à l’aide de la fonction « setClientId » (lignes 43 à 45).

Vérification de la persistance des sessions

Nous pouvons maintenant utiliser à nouveau le client Mosquitto pour tester la persistance des sessions en envoyant une série de requêtes de publication MQTT avec trois valeurs ClientId différentes (l’option « -i »).

$ 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"

L’examen du journal d’accès montre que le ClientId foo se connecte toujours à node1 (port 18831), le ClientId bar se connecte toujours à node2 (port 18832) et le ClientId baz se connecte toujours à node3 (port 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

Notez que nous bénéficions également de l’affichage du ClientId MQTT dans les lignes du journal d’accès, que nous utilisions ou non la persistance de session ou tout autre algorithme de répartition de charge.

Conclusion

Dans ce premier article d’une série en deux parties, nous avons montré comment NGINX Plus utilise des contrôles d’intégrité actifs pour améliorer la disponibilité et la fiabilité des applications IoT, et comment NGINX JavaScript peut étendre les fonctionnalités de NGINX Plus en offrant une capacité d’équilibrage de charge de couche 7, telle que la persistance de session pour le trafic TCP. Dans la seconde partie, nous verrons comment NGINX Plus peut renforcer la sécurité de vos applications IoT en prenant en charge la terminaison TLS et l’authentification des clients.

Associées à NGINX JavaScript ou utilisées seules, les performances élevées et l’efficacité intrinsèques de NGINX Plus en font un équilibreur de charge logiciel idéal pour votre infrastructure IoT.

Pour essayer NGINX JavaScript avec NGINX Plus, commencez votre essai gratuit de 30 jours ou contactez-nous pour discuter de vos cas d’utilisation.

Annexes

Création de l’environnement de test

Nous avons installé l’environnement de test sur une machine virtuelle afin qu’il soit isolé et reproductible. Toutefois, rien ne vous empêche de l’installer sur un serveur physique « bare metal ».

Installation de NGINX Plus

Consultez les instructions du guide d’administration de NGINX Plus.

Installation de HiveMQ

N’importe quel serveur MQTT peut être utilisé, mais cet environnement de test repose sur HiveMQ (à télécharger ici). Dans cet exemple, nous installons HiveMQ sur un hôte unique en utilisant un conteneur Docker pour chaque nœud. Les instructions suivantes sont adaptées de l’article « Déployer HiveMQ avec Docker ».

  1. Créez un Dockerfile pour HiveMQ dans le même répertoire que « hivemq.zip ».

    Chargement de l’exemple de code…

  2. Depuis le répertoire contenant hivemq.zip et le Dockerfile, créez l’image Docker.
$ docker build -t hivemq:latest .
  1. Créez trois nœuds HiveMQ, chacun étant accessible sur un port différent.
$ 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
  1. Vérifiez que les trois nœuds HiveMQ sont en cours d’exécution. (Dans l’exemple de sortie suivant, les colonnes « COMMAND », « CREATED » et « STATUS » ont été omises pour faciliter la lecture.)
$ 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   node1

Installation de Mosquitto

Le client en ligne de commande Mosquitto peut être téléchargé depuis le site web du projet. Les utilisateurs de Mac ayant installé Homebrew peuvent exécuter la commande suivante.

$ brew install mosquitto

Testez le client Mosquitto et l’installation de HiveMQ en envoyant un simple message de publication à l’une des images Docker.

$ 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’]


Partager

À propos de l’auteur

Liam Crilly
Liam CrillyDirecteur senior de la gestion des produits

Autres articles de Liam Crilly

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
NGINX | 13/10/2025

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA

Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
NGINX | 03/11/2023

Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?

Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
NGINX | 11/03/2020

Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX

Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
NGINX | 11/06/2019

Un testeur d’expressions régulières pour NGINX et NGINX Plus

Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.

Caches partagés avec les clusters de cache NGINX Plus, partie 1
NGINX | 24/01/2017

Caches partagés avec les clusters de cache NGINX Plus, partie 1

Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.

Introduction aux microservices
NGINX | 19/05/2015

Introduction aux microservices

Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.