Introduzione
LDAP (Lightweight Directory Access Protocol) è una fonte onnipresente di informazioni di directory, codificata per la prima volta nel 1993. Viene comunemente utilizzato per un'ampia gamma di applicazioni, tra cui la gestione delle informazioni su utenti/gruppi per le istanze Linux e il controllo dell'autenticazione per le VPN e le applicazioni legacy.
Tradizionalmente, il server LDAP di un’azienda veniva gestito internamente. spesso parte del Active Directory di Microsoft o di una distribuzione del progetto open source OpenLDAP. Oggigiorno, il supporto LDAP è disponibile presso i provider SaaS, tra cui Foxpass, che è stato il primo provider LDAP cloud a sviluppare da zero un'implementazione LDAP multi-tenant e cloud-centric.
LDAP in genere ha le seguenti primitive: bind, search, compare, e add. Dopo aver creato una connessione Transmission Control Protocol (TCP), le applicazioni devono prima eseguire il bind inviando un nome utente e una password. Una volta associato correttamente, il client invia comandi al server LDAP. In genere, si tratta del comando search in combinazione con i filtri. La connessione TCP rimane attiva finché il client o il server non si disconnette.
LDAP con Foxpass
In Foxpass, il nostro servizio LDAP è sviluppato su Twisted, un popolare framework di servizi event-based per Python. Il servizio è ospitato sulla piattaforma ECS di AWS ed è eseguito su decine di container (nodi). Poiché le connessioni LDAP sono persistenti, il cluster deve essere in grado di mantenere centinaia di migliaia di sessioni TCP simultanee.
Conserviamo i dati dei clienti nella RAM. Il motivo è molteplice: innanzitutto, il set di dati è relativamente piccolo, anche per i clienti più grandi. In secondo luogo, il linguaggio di query LDAP consente di cercare in campi arbitrari (il che è importante, poiché Foxpass consente campi personalizzati). Ciò significa che un sistema RDBMS tradizionale non sarà in grado di creare indici efficaci e dovremo trasformare i dati in una diversa rappresentazione in memoria. In terzo luogo, mantenere i dati nella RAM consente il tempo di risposta più rapido possibile: le latenze sono in genere intorno ai 100 ms (vedi grafico a).

Uno svantaggio di questo approccio è l'invalidazione della cache. Quando i dati di un cliente cambiano (ad esempio, quando un utente viene aggiunto o eliminato), i nodi LDAP devono aggiornare i propri dati dal nostro RDBMS principale. Nella nostra architettura precedente, questa era un'operazione relativamente costosa; quando ogni nodo aggiorna i dati della stessa azienda, può causare un picco evidente nella latenza LDAP e nel carico sul backing-store.
Sfide legate alla sensibilità alla latenza
Come illustrato sopra, a causa dei requisiti di latenza, ogni contenitore memorizza in memoria tutti i dati di un cliente (vedi figura a). I dati vengono recuperati on demand quando la richiesta arriva a un nodo (se non sono già presenti) e poi restano disponibili finché rimane attiva almeno una connessione di quel cliente.
Poiché una richiesta di connessione in ingresso potrebbe arrivare a qualsiasi container (tramite il load balancer), il container che gestisce una query carica e memorizza anche tutti i dati del cliente. Successivamente, il contenitore si registra con un servizio redis pubsub per ricevere messaggi di invalidazione. Quando i dati dell'azienda vengono aggiornati, viene trasmesso un segnale di invalidazione ai nodi riceventi, che cancellano la cache dei dati di quell'azienda e accedono al database per recuperare nuovamente i dati dell'azienda.
Questo ha comportato le seguenti difficoltà nel scalare il nostro servizio LDAP mentre continuavamo a crescere e ad aggiungere nuovi clienti al nostro sistema:
Tutti i dati del cliente (M) su tutti i nodi (N) richiedono un ricaricamento in caso di invalidazione. Sebbene in pratica non tutti i nodi ospitino una connessione da parte di ogni cliente, nel caso peggiore vengono effettuate MxN chiamate al database per aggiornare i dati. Man mano che vengono aggiunti più clienti (M), il numero di recuperi dal DB può aumentare di un fattore N. Questo significava anche che, per non sovraccaricare i server del database, erano necessarie istanze DB reader aggiuntive per gestire il picco di richieste.
Poiché tutti i dati dei clienti (M) sono memorizzati in memoria su molti nodi (N), la memoria continua ad aumentare su tutti i nodi in proporzione al numero di clienti (M). Ciò significa anche che i requisiti di memoria del container devono aumentare per poter contenere tutti i dati in memoria, il che a sua volta incrementa il costo complessivo dell'infrastruttura.
Le sfide sopra descritte erano molto chiare e ci hanno portato a orientare la distribuzione dei dati dei clienti tra i nodi invece di replicare tutti i dati su ogni nodo. Questo ci ha portato a una soluzione distribuita per la gestione della cache.

Gestione della cache distribuita
Abbiamo introdotto un livello di instradamento intelligente nel nostro servizio LDAP che inoltrava le richieste ai nodi che ospitavano i dati del cliente, se il container su cui arrivava la connessione non ospitava i dati. Per raggiungere questo obiettivo, abbiamo individuato i seguenti requisiti di progettazione per il livello di routing:
I clienti devono essere distribuiti dinamicamente tra i nodi man mano che vengono aggiunti.
I dati dei clienti devono essere distribuiti dinamicamente man mano che i nodi si riducono, si espandono e in caso di guasti dei nodi.
La possibilità di aumentare e diminuire il numero di partizioni per un cliente specifico, in modo che gli squilibri nel traffico non sovraccarichino un singolo nodo.
Abbiamo introdotto Apache Helix, che distribuisce le risorse tra le istanze. Il controller Helix, il cervello dell'ecosistema helix, prende decisioni di assegnazione delle risorse tra i nodi quando vengono aggiunti o rimossi nodi o clienti. Abbiamo containerizzato con Docker il controller Apache Helix e il server Rest, e li abbiamo distribuiti su ECS. Il controller Helix dipende da Zookeeper per rilevare le modifiche del cluster. Abbiamo implementato un metodo affidabile per distribuire Zookeeper su ECS, quindi ora l'intera infrastruttura di Helix e Zookeeper viene eseguita su ECS.
Ogni nodo LDAP interagisce con Zookeeper per registrarsi e partecipare al cluster. Ogni istanza LDAP viene avviata come partecipante Helix e partecipa al cluster di assegnazioni cliente-nodo tramite il controller Helix. Quando un nuovo cliente viene creato al volo da un nodo LDAP, il numero di partizioni (ognuna delle quali rappresenta un nodo che ospita i dati) per quel cliente viene determinato in base al numero di utenti.
Con questa integrazione, i nostri nodi LDAP sono a conoscenza degli eventi che si verificano nel cluster (ad esempio quando viene aggiunto un nuovo cliente o quando cambia l'insieme dei nodi disponibili).
Grazie a questa consapevolezza del cluster, il livello di instradamento del nostro servizio LDAP fornisce la mappatura tra i nodi e i clienti. Ogni connessione in entrata passerà ora attraverso il livello di instradamento per decidere a quale nodo instradare la connessione. In questo modo, i dati del cliente vengono assegnati a nodi specifici (vedi figura b).

Il livello di instradamento ospita una cache che contiene le informazioni di instradamento di clienti e nodi. Il livello di instradamento identifica eventuali modifiche nell’assegnazione cliente-nodo e si aggiorna immediatamente quando le modifiche vengono rilevate. In questo modo, ogni nodo LDAP è a conoscenza delle modifiche tra cliente e nodo non appena si verificano.
Con la soluzione di gestione della cache sopra descritta, sono state affrontate le sfide di scalabilità menzionate nella sezione Sfide legate alla sensibilità alla latenza:
Ora abbiamo clienti distribuiti tra i nodi. Ogni nodo ospiterà solo un sottoinsieme di clienti e sarà responsabile del recupero solo di un sottoinsieme dei dati dei clienti alla ricezione di invalidazioni pubsub. Questo riduce significativamente il numero di letture del DB, eliminando la necessità di aggiungere nodi reader del DB per gestire un volume elevato di letture del DB. Il cluster LDAP ora effettua il 75% in meno di letture del DB rispetto a prima.
Abbiamo anche migliorato l’efficienza della memoria perché non memorizziamo tutti i dati dei clienti (M) nella memoria su tutti i nodi (N). Questa architettura offre la possibilità di scalare orizzontalmente senza aumentare le dimensioni dell'istanza. Ogni nodo LDAP ora consuma il 40% di memoria in meno rispetto a prima.


Sfide attuali
Con l'implementazione sopra descritta, una delle difficoltà è che i nodi ricevono un numero diseguale di connessioni TCP. Questo squilibrio nella distribuzione causa un maggiore utilizzo della CPU su alcuni nodi (hot nodes) rispetto ad altri. Ma l'utilizzo medio complessivo della CPU sui nodi rimane comunque invariato rispetto a prima.
Ringraziamenti
Grazie a tutto il team. Un ringraziamento speciale a Bryan Bojorque per averci aiutato con la containerizzazione e la creazione del cluster Helix e Zookeeper e per aver reso disponibili le metriche di questi cluster in Datadog.
Migliora la tua sicurezza
La tua rete è protetta dagli utenti non autorizzati? Fai clic qui per scoprire come Foxpass può aiutarti a evitare costosi errori di sicurezza:




