LVS Load Balancing Cluster - Introduzione

In questo infobox verranno illustrati alcuni concetti sulla configurazione di un cluster load balancing, ovvero un cluster costituito da una serie di nodi posti dietro ad un bilanciatore, utilizzando il progetto Linux Virtual Server. Questo infobox ha solo lo scopo di introdurre i concetti di base di LVS, mentre i dettagli della configurazione verranno illustrati in seguito.  
In una configurazione di questo tipo sono presenti diversi nodi dove e' in esecuzione il servizio che si vuole fornire, ed almeno un sistema con il ruolo di bilanciatore, che ha il compito di distribuire le richieste dei client ai diversi nodi secondo il metodo di instradamento e l'algoritmo di bilanciamento prescelto.
In particolare, la configurazione che verra' presa come riferimento e' costituita da un piccolo cluster che ho configurato qualche settimana fa ed attualmente in produzione, costituito da un bilanciatore e due nodi che svolgono funzionalita' di proxy http.

Per quanto riguarda i metodi di instradamento (forwarding) delle richieste che e' possibile utilizzare con LVS esistono tre possibilita':

- LVS-NAT (Network Address Translation): le request dai client vengono ricevute dall director sull'ip virtuale, il quale le inoltra all'IP reale del nodo. A sua volta il nodo risponde alle request inviando i pacchetti al director, che effettuera' un source nat degli stessi al fine di cambiare l'ip sorgente dall'ip del nodo fisico a quello virtuale.  
In pratica il Director fa da gateway per i nodi del cluster, e tutti i pacchetti sia in entrata che in uscita passano da esso.  
- LVS-DR (Direct Routing): in questo caso il Director inoltra tutte le request ricevute ai nodi del cluster, che a differenza del precedente metodo rispondono direttamente ai client. Le resposte alla request ricevute. dunque, non passano piu' per il Director che si limita ad inoltrare il traffico in entrata.
- LVS-TUN (IP Tunneling): questo metodo puo' essere utilizzato quando il director ed i nodi del cluster si trovino su reti differenti. Le request vengono ricevute dal director, incapsulate ed inoltrate ai nodi del cluster, i quali a loro volta rispondono direttamente al client.

Poiche' la configurazione presa come esempio utilizza il metodo di forwarding LVS-DR si illustra in maggiore dettaglio il suo funzionamento:

- Il client invia la request all'indirizzo IP virtuale assegnato al bilanciatore. Il pacchetto inviato avra' quindi come destinazione l'indirizzo IP virtuale ed il mac address della scheda di rete del director.
- Il director inoltra i pacchetti ricevuti ai nodi del cluster, lasciando inalterato l'indirizzo IP di destinazione e modificando il MAC address di destinazione con quello del nodo.
- Il nodo del cluster riceve quindi i pacchetti (poiche' destinati al proprio mac-address) aventi un indirizzo IP di destinazione non corrispondente a quello della propria scheda di rete. I nodi hanno infatti associato un indirizzo ip differente dall'indirizzo virtuale assegnato al director.
Servira' quindi un meccanismo per fare in modo che le request ricevute dal nodo fisico vengano effettivamente passate al servizio interessato. La documentazione disponibile propone alcuni metodi che e' possibile adottare dall'utilizzo di un alias con l'indirizzo virtuale sull'interfaccia di loopback del nodo ed un rotta statica per inoltrarvi il traffico fino all'utilizzo di iproute.
Poiche' i nodi del cluster nel mio caso erano basati su una distribuzione linux parecchio customizzata che si voleve modificare il meno possibile si e' optato per un soluzione differente, ovvero un destination nat tramite iptables: una volta ricevuto sul nodo un pacchetto con ip di destinazione uguale all'ip virtuale del director, si effettua un destination nat modificando l'ip di destinazione con l'ip reale del nodo, dove il servizio e' effettivamente in ascolto.
- Ricevuta la request il servizio risponde direttamente ai client, senza piu' passare per il director. Le risposte in oggetto avranno comunque come indirizzo IP sorgente l'IP virtuale del director: esso viene infatti modificato automaticamente nelle risposte da netfilter, poiche' ricondotte alla connessione per la quale e' stato precendentemente applicato il destination nat.

Una volta stabilito il metodo di forwarding, altro aspetto da considerare e' il paradigma di bilanciamento. In particolare il progetto linux LVS supporta i seguenti algoritmi:

- Non dinamici

      Round Robin (RR)
      Weighted Round Robin (WRR)
      Destination Hashing
      Source Hashing

- Dinamici

      Least-connection (LC)
      Weighted least-connection (WLC)  
      Shortest expected delay (SED)
      Never queue (NQ)  
      Locality-based least-connection (LBLC)
      Locality-based least-connection with replication scheduling (LBLCR)

Vista l'ampiezza dell'argomento non verranno illustrati tutti i paradigmi disponibili, ma solo quello adottato per la configurazione descritta.  
In teoria, dovendo bilanciare dei proxy http il paradigma piu' indicato dovrebbe essere LBLC (Locality-Based Least-Connection).
L'algoritmo LBLC cerca di inviare tutte le request per un certo indirizzo IP di destinazione (nel caso un determinato sito web) al medesimo nodo del cluster, perche' avendo quest'ultimo funzionalita' di proxy si presume che gia' abbia in cache le risorse relative al sito in oggetto.
La prima connessione viene bilanciata con un criterio simile ad un Weighted Least Connection, mentre le successive request verso quell'indirizzo IP continueranno ad essere inoltrate allo stesso nodo finche' un altro nodo del cluster non abbia un numero di connessioni (pesate) minore della meta' di quelle del nodo in uso.  
Nel mio caso l'utilizzo di questo algoritmo di scheduling, abbinato alla persistenza ed al fatto che molti client arrivavano al bilanciatore nattati da un apparato, portava ad un cluster decisamente sbilanciato con frequenti situazioni di Denial of Service sul proxy piu' carico.  
Ho invece verificato un cluster piu' bilanciato ed un miglior feedback da parte degli utenti utilizzando l'algoritmo LC (Least Connection). Alla ricezione di una nuova request il bilanciatore verifica il numero di connessioni attive ed inattive sui diversi nodi al fine di determinare a chi di essi inoltrarla. In particolare, per ogni nodo, il bilanciatore calcola un valore di overhead moltiplicando le connessioni attive per 256 ed aggiungendo il numero di connessioni inattive.  
Una volta ricevuta la request viene quindi inoltrata al nodo con valore di overhead piu' basso.

Un aspetto ulteriore da tenere in considerazione e' quello della persistenza, ovvero fare in modo che un client che abbia iniziato ad essere bilanciato su un nodo continui ad esserlo sul medesimo. Questo aspetto e' particolarmente importante nel caso della configurazione che ho dovuto realizzare, poiche' indispensabile per il meccanismo di content filter adottato dai proxy. Il meccanismo di content filter in oggetto prevedeva che ogni qualvolta un utente cercasse di scaricare un file di grosse dimensioni, questo venisse copiato su un sito temporaneo, scansionato tramite antivirus, e fornito all'utente un nuovo url sul sito temporaneo da cui scaricare il file controllato.  
Poiche' il file da scaricare veniva riconosciuto tramite un id di sessione, il meccanismo non potrebbe funzionare se l'utente tentasse di scaricarlo dal sito temporaneo utilizzando un proxy diverso da quello che ha effettuato la richiesta originaria. Da qui la necessita' di ricorrere ad un meccanismo di persistenza, per fare in modo che i client continuino a contattare il medesimo nodo durante la sua sessione.  
Per ottenere questo comportamento e' possibile impostare un timeout, il persistence timeout appunto, che indica l'arco di tempo per il quale, un client debba essere matenuto bilanciato sul medesimo nodo.  
Alla prima request di un client il director impostera' quindi un timer per tenere traccia di questo timeout.  
Una volta che il timer abbia raggiunto il valore zero, se la connessione e' ancora attiva esso verra' resettato ad un valore di default pari a 2 minuti. Finche' il timer in questione sara' attivo il client verra' bilanciato sul medesimo nodo.
L'utilizzo della persistenza ha pero' un effetto collaterale: forzando il bilanciamento dei client sui nodi dove e'stata instanziata la loro prima connessione aumentano le possibilita' di avere un cluster sbilanciato. Ad esempio, nonostante l'utiilizzo di paradigmi di bilanciamento dinamici si potrebbe avere uno dei nodi sovraccarico semplicemente perche' tutti i client che stiano facendo piu' traffico continuino ad essere bilanciati sul nodo in oggetto a causa della persistenza, anche se gli altri nodi dovessero comunque avere maggiori risorse a disposizione.

Privacy Policy