Stefano’s notebook

my technical writings

Apache 2 e mod_rewrite come load balancer http

Ipotizziamo di voler bilanciare un’applicazione web su due backend, ma di non voler utilizzare uno storage condiviso.

Ipotizziamo anche che l’applicazione necessiti di accesso a disco solo per salvare le informazioni di sessione, mentre il database a cui accede sia su una macchina separata.

Ogni singolo utente dovrebbe quindi accedere solo ad uno dei server di backend (e sempre a quello), in modo da trovare intatte le sue informazioni di sessione. (Questa soluzione è solo per bilanciamento, non per alta disponibilità!)

Vediamo come è possibile realizzare il bilanciatore con apache, mod_proxy e qualche regola di rewrite.

<VirtualHost *:80>
ServerName lb.gnustile.lan
ServerAdmin [email protected]

ErrorLog /var/log/apache2/lb_error.log
LogLevel warn

proxyPreserveHost On

RewriteEngine On

RewriteMap SERVERS rnd:/etc/apache2/dat/lb_servers.conf
RewriteMap RNC rnd:/etc/apache2/dat/random_numbers.conf

# Controlla l'esistenza del cookie "_bes", che indica che
# una sessione di bilanciamento è già attiva.
# Redirige quindi al backend indicato nel cookie
# (per mascherare le informazioni il cookie è nella forma
# <stringa_random>-<nome_backend>-<numero_random>
# )
RewriteCond %{HTTP_COOKIE} "_bes=(.+)-(.+)-(.+)"
# recupera ora, a partire dal nome del backend, il nome e il server
# il primo elemento è il nome del BE, il secondo il BE
RewriteCond ${SERVERS:%2} (.+)::(.+)
# redirige via mod_proxy e imposta 2 variabili d'ambiente (per facilitare il logging)
RewriteRule /(.*) http://%2/$1 [P,L,E=BE:%1,E=BES:%2]

# Se non è presente il cookie di "sessione" (o se non è valido)
# usa un backend random e crea il cookie.

# determina l'host, in modo da renderlo unico e disponibile in %1 e %2
RewriteCond ${SERVERS:ALL} (.+)::(.+)
# il primo elemento è il nome del BE, il secondo il BE
# setta inoltre il cookie _bes per HTTP_HOST
RewriteRule /(.*) http://%2/$1 [P,L,E=BE:%1,E=BES:%2,CO=_bes:${RNC:C}-%1-${RNC:N}:%{HTTP_HOST}]

proxyPassReverse / http://app1.gnustile.lan/
proxyPassReverse / http://app2.gnustile.lan/
proxyPassReverse / http://app3.gnustile.lan/

CustomLog /var/log/apache2/lb_access.log "[BE::%{BE}e::%{BES}e::C::%{_bes}C] %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"

</VirtualHost>

Il file /etc/apache2/dat/lb_servers.conf contiene

BE1  BE1::app1.gnustile.lan
BE2  BE2::app2.gnustile.lan
BE3  BE3::app3.gnustile.lan
ALL  BE1::app1.gnustile.lan|BE2::app2.gnustile.lan|BE3::app3.gnustile.lan

Il file è così strutturato per recuperare, oltre al server di backend, anche il nome dello stesso.

Il file /etc/apache2/dat/random_numbers.conf è stato generato con uno script e contiene qualcosa del tipo

N  1122|1144|...altri numeri random...
C  z43rewrjcqw|irje8wr34|...altre stringhe random...

Se proprio vi interessa ecco lo script generatore: (ovviamente l’output andrà rediretto al file usato da mod_rewrite)

#!/usr/bin/perl

sub generate_random_string
{
  my $length_of_randomstring=shift;
  my @chars=('a'..'z','A'..'Z','0'..'9','.','_');
  my $random_string;
  foreach (1..$length_of_randomstring) 
  {
    $random_string.=$chars[rand @chars];
  }
  return $random_string;
}

my $seq = 10000;
my $strlen = 25;
my $range = 99999999;

@els = ();

for ($i = 0; $i < $seq; $i++) {
  my $rn = &generate_random_string($strlen);
  push(@els, $rn);
}

$res = join('|', @els);

print "C\t" . $res . "\n";

@els = ();

for ($i = 0; $i < $seq; $i++) {
  my $rn = int(rand($range));
  push(@els, $rn);
}

$res = join('|', @els);

print "N\t" . $res . "\n";