Inleiding
LDAP (Lightweight Directory Access Protocol) is een alomtegenwoordige bron van directory-informatie, voor het eerst vastgelegd in 1993. Het wordt vaak gebruikt voor een breed scala aan toepassingen, waaronder het beheren van gebruikers-/groepsinformatie voor Linux-instances en het regelen van authenticatie voor VPN's en oudere applicaties.
Traditioneel draaide de LDAP-server van een bedrijf intern. vaak ofwel onderdeel van Microsoft's Active Directory ofwel een implementatie van het opensourceproject OpenLDAP. Tegenwoordig is LDAP-ondersteuning beschikbaar bij SaaS-providers, waaronder Foxpass, de eerste cloud LDAP-provider die vanaf de basis een multi-tenant, cloudgerichte LDAP-implementatie heeft ontwikkeld.
LDAP heeft doorgaans de volgende primitieven: bind, search, compare, en add. Na het tot stand brengen van een Transmission Control Protocol (TCP)-verbinding moeten applicaties zich eerst binden door een gebruikersnaam en wachtwoord te verzenden. Zodra de client succesvol is gebonden, geeft deze opdrachten aan de LDAP-server. Meestal is dit de zoek opdracht in combinatie met filters. De TCP-verbinding blijft bestaan totdat de client of server de verbinding verbreekt.
LDAP bij Foxpass
Bij Foxpass is onze LDAP-service gebouwd boven op Twisted, een populair eventgebaseerd serviceframework voor Python. De service wordt gehost op het ECS-platform van AWS en draait op tientallen containers (nodes). Omdat LDAP-verbindingen persistent zijn, moet het cluster honderdduizenden gelijktijdige TCP-sessies kunnen onderhouden.
We bewaren de gegevens van klanten in het RAM-geheugen. Daar zijn verschillende redenen voor: ten eerste is de dataset relatief klein, zelfs voor grote klanten. Ten tweede maakt de LDAP-querytaal het mogelijk om willekeurige velden te doorzoeken (wat belangrijk is, aangezien Foxpass aangepaste velden toestaat). Dit betekent dat een traditioneel RDBMS-systeem geen effectieve indexen kan opbouwen en dat we de gegevens moeten omzetten naar een andere in-memoryrepresentatie. Ten derde zorgt het bewaren van de gegevens in RAM voor de snelst mogelijke responstijd: latenties liggen doorgaans rond de 100 ms (zie grafiek a).

Een nadeel van deze aanpak is cache-invalidation. Wanneer de gegevens van een klant veranderen (bijvoorbeeld wanneer een gebruiker is toegevoegd of verwijderd), moeten de LDAP-knooppunten hun gegevens vernieuwen vanuit onze hoofd-RDBMS. In onze vorige architectuur was dit een relatief kostbare bewerking; wanneer elke node de gegevens van hetzelfde bedrijf vernieuwt, kan dat een merkbare piek in LDAP-latentie en belasting van de backing-store veroorzaken.
Uitdagingen rond gevoeligheid voor latentie
Zoals hierboven besproken slaat elke container, vanwege de latentievereisten, alle gegevens van een klant in het geheugen op (zie figuur a). De gegevens worden op aanvraag opgehaald wanneer het verzoek op een node binnenkomt (als ze daar nog niet aanwezig zijn) en blijven vervolgens beschikbaar zolang er ten minste één verbinding van die klant actief blijft.
Omdat een binnenkomend verbindingsverzoek op elke container terecht kan komen (via de load balancer), laadt en slaat de container die de query afhandelt ook alle gegevens van de klant op. Vervolgens registreert de container zich bij een redis pubsub -service om ongeldigverklaringsberichten te ontvangen. Wanneer de gegevens van het bedrijf worden bijgewerkt, wordt er een invalidatiesignaal uitgezonden naar de ontvangende nodes, die de cache met gegevens van dat bedrijf leegmaken en naar de database gaan om de gegevens van het bedrijf opnieuw op te halen.
Dit bracht de volgende uitdagingen met zich mee om onze LDAP-service op te schalen terwijl we bleven groeien en nieuwe klanten aan ons systeem toevoegden:
Alle gegevens voor de klant (M) over alle knooppunten (N) vereisen een herlading bij invalidaties. Hoewel in de praktijk niet elke node een verbinding van elke klant host, worden in het slechtste geval MxN-aanroepen naar de database gedaan om de gegevens te vernieuwen. Naarmate er meer klanten worden toegevoegd (M), kan het aantal DB-opvragingen met een factor N toenemen. Dit betekende ook dat, om de databasemachines niet te overbelasten, extra DB-readerinstanties nodig zijn om de piek in verzoeken op te vangen.
Omdat alle gegevens van klanten (M) in het geheugen over meerdere nodes (N) worden opgeslagen, blijft het geheugengebruik over alle nodes toenemen, evenredig met het aantal klanten (M). Dit betekent ook dat de geheugenvereisten van de container moeten toenemen om alle gegevens in het geheugen te kunnen opslaan, wat op zijn beurt de totale infrastructuurkosten verhoogt.
De bovenstaande uitdagingen waren heel duidelijk en brachten ons ertoe de gegevens van klanten over de nodes te verdelen in plaats van alle gegevens op elke node te repliceren. Dit bracht ons bij een oplossing voor gedistribueerd cachebeheer.

Beheer van gedistribueerde cache
We hebben een intelligente routeringslaag geïntroduceerd in onze LDAP-service die verzoeken doorstuurde naar de nodes waarop de gegevens van de klant werden gehost, als de container waarop de verbinding terechtkwam die gegevens niet hostte. Om dit te bereiken, hebben we ons gericht op de volgende ontwerpvereisten voor de routeringslaag:
Klanten moeten dynamisch over de nodes worden verdeeld wanneer deze worden toegevoegd.
De gegevens van klanten moeten dynamisch worden verdeeld naarmate de nodes krimpen, uitbreiden en bij uitval van nodes.
De mogelijkheid om het aantal partities voor een specifieke klant te vergroten en te verkleinen, zodat een onevenwicht in het verkeer geen enkele node overbelast.
We introduceerden Apache Helix, waarmee de resources over instances worden verdeeld. De Helix-controller, het brein van het helix-ecosysteem, neemt beslissingen over het toewijzen van resources over nodes heen wanneer nodes of klanten worden toegevoegd of verwijderd. We hebben de Apache Helix-controller en de REST-server gedockerized en op ECS geïmplementeerd. De Helix-controller is afhankelijk van Zookeeper om naar clusterwijzigingen te luisteren. We hebben een betrouwbare manier geïmplementeerd om Zookeeper op ECS uit te rollen, zodat nu de volledige infrastructuur van Helix en Zookeeper op ECS draait.
Elke LDAP-knooppunt communiceert met Zookeeper om zichzelf te registreren en deel te nemen aan het cluster. Elke LDAP-instantie wordt weergegeven als een Helix-deelnemer die, onder aansturing van de Helix-controller, deelneemt aan het cluster van klant-naar-node-toewijzingen. Wanneer er on-the-fly een nieuwe klant wordt aangemaakt door een LDAP-knooppunt, wordt het aantal partities (waarbij elke partitie een knooppunt vertegenwoordigt dat de gegevens host) voor die klant bepaald op basis van het aantal gebruikers.
Met deze integratie zijn onze LDAP-knooppunten op de hoogte van gebeurtenissen die plaatsvinden in het cluster (d.w.z. wanneer een nieuwe klant wordt toegevoegd of wanneer de set beschikbare knooppunten verandert).
Met dit clusterbewustzijn verzorgt de routeringslaag in onze LDAP-service de koppeling tussen de knooppunten en de klanten. Elke inkomende verbinding gaat nu via de routeringslaag om te bepalen naar welke node de verbinding moet worden gerouteerd. Hiermee worden de gegevens van de klant toegewezen aan specifieke nodes (zie figuur b).

De routeringslaag host een cache met route-informatie van klanten en nodes. De routeringslaag identificeert wijzigingen in toewijzingen van klanten aan nodes en werkt deze direct bij zodra de wijzigingen worden gedetecteerd. Op deze manier is elke LDAP-node direct op de hoogte van wijzigingen tussen klant en node zodra die plaatsvinden.
Met de bovenstaande cachebeheeroplossing zijn de schaalbaarheidsuitdagingen die worden genoemd in de sectie Uitdagingen rond latentiegevoeligheid aangepakt:
We hebben nu klanten verdeeld over de nodes. Elke node host slechts een subset van klanten en is alleen verantwoordelijk voor het ophalen van een subset van klantgegevens bij ontvangst van pubsub-invalidaties. Dit vermindert het aantal reads naar de DB aanzienlijk, waardoor het niet meer nodig is om DB reader nodes toe te voegen om een hoog volume aan DB-reads af te handelen. Het LDAP-cluster doet nu 75% minder DB-reads dan voorheen.
We hebben ook de geheugenefficiëntie verbeterd, omdat we niet alle klantgegevens (M) in het geheugen opslaan op alle nodes (N). Deze architectuur biedt de mogelijkheid om horizontaal op te schalen zonder de instancegrootte te vergroten. Elke LDAP-node verbruikt nu 40% minder geheugen dan voorheen.


Huidige uitdagingen
Met de bovenstaande implementatie is een van de uitdagingen dat nodes een ongelijk aantal TCP-verbindingen ontvangen. Deze ongelijke verdeling zorgt ervoor dat sommige nodes (hot nodes) meer CPU gebruiken dan andere. Maar de gemiddelde totale CPU-belasting over de nodes blijft nog steeds hetzelfde vergeleken met daarvoor.
Dankbetuigingen
Dank aan het hele team. Speciale dank aan Bryan Bojorque voor het helpen met het containeriseren en opzetten van het Helix- en Zookeeper-cluster en voor het inzichtelijk maken van metrics uit deze clusters in Datadog.
Upgrade je beveiliging
Is je netwerk beschermd tegen onbevoegde gebruikers? Klik hier om te ontdekken hoe Foxpass je kan helpen kostbare beveiligingsfouten te voorkomen:




