Introduction
LDAP (Lightweight Directory Access Protocol) est une source d’informations d’annuaire omniprésente, normalisée pour la première fois en 1993. Il est couramment utilisé pour un large éventail d’applications, notamment pour gérer les informations utilisateur/groupe des instances Linux et contrôler l’authentification des VPN et des applications héritées.
Traditionnellement, le serveur LDAP d’une entreprise était hébergé en interne. souvent soit une partie de l’Active Directory de Microsoft, soit un déploiement du projet open source OpenLDAP. Aujourd’hui, la prise en charge de LDAP est proposée par des fournisseurs SaaS, notamment Foxpass, qui a été le premier fournisseur LDAP cloud à concevoir de A à Z une implémentation LDAP multitenant centrée sur le cloud.
LDAP a généralement les primitives suivantes : liaison, recherche, comparaison, et ajout. Après avoir créé une connexion Transmission Control Protocol (TCP), les applications doivent d’abord se lier en envoyant un nom d’utilisateur et un mot de passe. Une fois lié avec succès, le client émet des commandes au serveur LDAP. En général, il s’agit de la commande recherche utilisée avec des filtres. La connexion TCP reste active jusqu’à ce que le client ou le serveur se déconnecte.
LDAP chez Foxpass
Chez Foxpass, notre service LDAP est développé sur Twisted, un framework de services événementiels populaire pour Python. Le service est hébergé sur la plateforme ECS d'AWS et s’exécute sur des dizaines de conteneurs (nœuds). Étant donné que les connexions LDAP sont persistantes, le cluster doit être capable de maintenir des centaines de milliers de sessions TCP simultanées.
Nous conservons les données des clients en RAM. La raison est multiple : d’abord, l’ensemble de données est relativement petit, même pour les grands clients. Deuxièmement, le langage de requête LDAP permet de rechercher dans des champs arbitraires (ce qui est important, puisque Foxpass autorise les champs personnalisés). Cela signifie qu’un système RDBMS traditionnel ne sera pas en mesure de créer des index efficaces, et nous devons transformer les données en une autre représentation en mémoire. Troisièmement, conserver les données dans la RAM permet d’obtenir le temps de réponse le plus rapide possible : les latences sont généralement d’environ 100 ms (voir graphique a).

L’un des inconvénients de cette approche est l’invalidation du cache. Lorsque les données d’un client changent (par exemple, lorsqu’un utilisateur est ajouté ou supprimé), les nœuds LDAP doivent actualiser leurs données depuis notre RDBMS principal. Dans notre architecture précédente, il s’agissait d’une opération relativement coûteuse ; lorsque chaque nœud actualise les données de la même entreprise, cela peut provoquer un pic sensible de latence LDAP et de charge sur le stockage sous-jacent.
Défis liés à la sensibilité à la latence
Comme indiqué ci-dessus, en raison des exigences de latence, chaque conteneur stocke toutes les données d’un client en mémoire (voir figure a). Les données sont récupérées à la demande lorsque la requête arrive sur un nœud (si elles ne s’y trouvent pas déjà), puis elles y restent tant qu’au moins une connexion de ce client est maintenue.
Comme une demande de connexion entrante peut arriver sur n’importe quel conteneur (via l’équilibreur de charge), le conteneur qui traite une requête charge et stocke également toutes les données du client. Ensuite, le conteneur s’enregistre auprès d’un service redis pubsub pour recevoir des messages d’invalidation. Lorsque les données de l’entreprise sont mises à jour, un signal d’invalidation est diffusé aux nœuds récepteurs, qui effacent le cache de données de cette entreprise et interrogent la base de données pour récupérer à nouveau les données de l’entreprise.
Cela posait les défis suivants pour faire évoluer notre service LDAP à mesure que nous poursuivions notre croissance et ajoutions de nouveaux clients à notre système :
Toutes les données du client (M) sur l’ensemble des nœuds (N) nécessitent un rechargement lors des invalidations. Bien qu’en pratique, tous les nœuds n’hébergent pas une connexion de chaque client, dans le pire des cas, MxN appels sont effectués à la base de données pour actualiser les données. À mesure que de nouveaux clients sont ajoutés (M), le nombre de récupérations dans la base de données peut augmenter d’un facteur N. Cela signifiait également que, afin de ne pas surcharger les machines de base de données, des instances DB reader supplémentaires sont nécessaires pour gérer le pic de requêtes.
Comme toutes les données de l’ensemble des clients (M) sont stockées en mémoire sur de nombreux nœuds (N), la mémoire continue d ’augmenter sur tous les nœuds proportionnellement au nombre de clients (M). Cela signifie également que les besoins en mémoire du conteneur doivent augmenter pour accueillir toutes les données en mémoire, ce qui accroît à son tour le coût global de l'infrastructure.
Les défis ci-dessus étaient très clairs et nous ont poussés à répartir les données des clients entre les nœuds au lieu de répliquer toutes les données sur chaque nœud. Cela nous a conduits à une solution de gestion de cache distribué.

Gestion du cache distribué
Nous avons introduit une couche de routage intelligente dans notre service LDAP qui transférait les requêtes vers les nœuds hébergeant les données du client, si le conteneur sur lequel la connexion arrivait n’hébergeait pas ces données. Pour y parvenir, nous nous sommes concentrés sur les exigences de conception suivantes pour la couche de routage :
Les clients doivent être répartis dynamiquement entre les nœuds à mesure qu’ils sont ajoutés.
Les données des clients doivent être réparties dynamiquement à mesure que les nœuds se réduisent, s’étendent et en cas de défaillance d’un nœud.
La possibilité d’augmenter et de diminuer le nombre de partitions pour un client spécifique afin qu’un déséquilibre du trafic ne submerge aucun nœud unique.
Nous avons introduit Apache Helix, qui distribue les ressources entre les instances. Le contrôleur Helix, le cerveau de l’écosystème helix, prend des décisions d’affectation des ressources entre les nœuds lorsque des nœuds ou des clients sont ajoutés ou supprimés. Nous avons dockerisé le contrôleur Apache Helix, le serveur Rest, et les avons déployés sur ECS. Le contrôleur Helix dépend de Zookeeper pour écouter les modifications du cluster. Nous avons mis en place une méthode fiable pour déployer Zookeeper sur ECS, de sorte que toute l’infrastructure de Helix et Zookeeper fonctionne désormais sur ECS.
Chaque nœud LDAP interagit avec Zookeeper pour s’enregistrer afin de participer au cluster. Chaque instance LDAP apparaît comme un participant Helix qui prend part au cluster d’attributions client-nœud géré par le contrôleur Helix. Lorsqu’un nouveau client est créé à la volée par un nœud LDAP, le nombre de partitions (chaque partition représentant un nœud qui héberge les données) pour ce client est déterminé en fonction du nombre d’utilisateurs.
Avec cette intégration, nos nœuds LDAP sont informés des événements qui se produisent dans le cluster (c.-à-d. lorsqu’un nouveau client est ajouté ou lorsque l’ensemble des nœuds disponibles change).
Grâce à cette connaissance du cluster, la couche de routage de notre service LDAP assure la correspondance entre les nœuds et les clients. Chaque connexion entrante passera désormais par la couche de routage afin de déterminer vers quel nœud la connexion doit être acheminée. Ainsi, les données du client sont attribuées à des nœuds spécifiques (voir figure b).

La couche de routage héberge un cache qui contient les informations de routage des clients et des nœuds. La couche de routage identifie toute modification de l’attribution client-nœud et la met à jour immédiatement dès que ces changements sont détectés. De cette manière, chaque nœud LDAP est informé des changements client-nœud dès qu’ils se produisent.
Grâce à la solution de gestion du cache ci-dessus, les défis de scalabilité mentionnés dans la section Défis liés à la sensibilité à la latence ont été relevés :
Nous avons désormais des clients répartis sur les nœuds. Chaque nœud n’hébergera qu’un sous-ensemble de clients et sera chargé de récupérer uniquement un sous-ensemble des données des clients lors de la réception des invalidations pubsub. Cela réduit considérablement le nombre de lectures dans la DB, supprimant ainsi la nécessité d'ajouter des nœuds de lecture DB pour gérer un volume élevé de lectures DB. Le cluster LDAP effectue désormais 75 % de lectures de base de données en moins qu’auparavant.
Nous avons également amélioré l’efficacité de la mémoire, car nous ne stockons pas toutes les données client (M) en mémoire sur tous les nœuds (N). Cette architecture permet une mise à l’échelle horizontale sans augmenter la taille de l’instance. Chaque nœud LDAP consomme désormais 40 % de mémoire en moins qu’auparavant.


Défis actuels
Avec la mise en œuvre ci-dessus, l’un des défis est que les nœuds reçoivent un nombre inégal de connexions TCP. Ce déséquilibre de répartition entraîne une utilisation plus élevée du processeur sur certains nœuds (nœuds chauds) par rapport à d’autres. Mais l’utilisation moyenne globale du CPU sur l’ensemble des nœuds reste la même qu’auparavant.
Remerciements
Merci à toute l’équipe. Mention spéciale à Bryan Bojorque pour son aide dans la conteneurisation et la mise en place du cluster Helix et Zookeeper, ainsi que pour avoir rendu les métriques de ces clusters visibles dans Datadog.
Renforcez votre sécurité
Votre réseau est-il protégé contre les utilisateurs non autorisés ? Cliquez ici pour découvrir comment Foxpass peut vous aider à éviter des erreurs de sécurité coûteuses :




