djbdns dns server: dnscache

Il mondo dei server DNS è sicuramente dominato da bind in ambito linux ma esistono altri prodotti interessanti soprattutto se la necessità non è quella di dare il servizio DNS su internet.

Trovo particolarmente interessante djbdns. Questo daemon è stato sviluppato da D.J. Bernstein, autore, tra l’altro, anche di qmail. La caratteristica più interessante di djbdns è quella di separare in tre daemon differenti tre funzionalità differenti generalmente unificate sotto un’unico daemon:

  • caching DNS: dnscache
  • authoriting delle zone: tinydns
  • trasferimento di zone: axfrdns and axfr-get

Di questi il più utile secondo me è dnscache che, essendo un sistema estremamente leggero è ideale per essere istallato e ridurre il carico di lavoro di tutti quei server che hanno la necessità di gestire un elevato numero di connessioni che giungono da svariati IP.

Djbdns richiede due software: daemontools e ucspi-tcp. Questi li istalliamo da pacchetto.

aptitude install daemontools
aptitude install ucspi-tcp

djbdns non è però presente sulla distribuzione debian. Scarichiamo quindi i sorgenti. La compilazione richiede i pacchetti make, gcc

 wget http://cr.yp.to/djbdns/djbdns-1.05.tar.gz
tar -zxvf djbdns-1.05.tar.gz
cd djbdns-1.05
echo gcc -O2 -include /usr/include/errno.h > conf-cc
make
make setup check

L’autore richiede poi di segnalare l’avvenuta istallazione con

( echo 'First M. Last'; cat `cat SYSDEPS` ) \
| mail djb-sysdeps@cr.yp.to

Passiamo ora alla configurazione del servizio. Come sempre non è il caos di di far girare dei daemon come root; aggiungiamo quindi due utenti al sistema inserendo nel file */etc/passwd*

dnscache:x:999:999::/dev/null:/usr/sbin/nologin
dnslog:x:998:999::/dev/null:/usr/sbin/nologin

e al file /etc/group

dnscache:x:999:

Prepariamo la configurazione di base

dnscache-conf dnscache dnslog /etc/dnscache 127.0.0.1

Con questo comando abbiamo generato la configurazione in /etc/dnscache specificando che l’utente che dovrà eseguire il daemon sarà dnscache, che i log saranno gestiti dall’utente dnslog e che il daemone sarà in ascolto sull’IP 127.0.0.1.

La directory creata conterrà 5 sottodirectory: env, log, root, run e seed. In particolare env e root contengono le configurazioni vere e proprie e log i log. Vedremo qualche dettaglio in seguito.

Occupiamoci ora dell’avvio del sistema. Vogliamo sfrutatre i daemontools e per farlo dovremo far si che svscanboot venga avviato all’avvio del sistema attraverso i meccanismi della distribuzione che stiamo utilizzando. Per le finalità di questo tutorial ci accontentiamo di lanciarlo a mano:

mkdir /etc/service/
cd /tmp/
nohup svcscanboot &

Il sistema dei daemontools si aspetta una directory in /etc/service per ogni servizio da gestire e monitorare. Aggiungiamo quindi la nostra cache dns

ln -s /etc/dnscache /etc/service/dnscache

Dopo qualche secondo il daemon sarà avviato

svstat /etc/service/dnscache/

ci restituirà il tempo di esecuzione e il PID del demone.

Interessanti un paio di *ps*

root@debian:/tmp# ps aux|grep sv
root      7605  0.0  0.0   4180   580 pts/0    S    22:24   0:00 /bin/sh /usr/bin/svscanboot
root      7607  0.0  0.0   4120   460 pts/0    S    22:24   0:00 svscan /etc/service
root@debian:/tmp# ps aux|grep dns
root      7609  0.0  0.0   3948   316 pts/0    S    22:24   0:00 supervise dnscache
dnslog    7611  0.0  0.0   4092   468 pts/0    S    22:24   0:00 multilog t ./main
dnscache  7612  0.0  0.1   5484  1492 pts/0    S    22:24   0:00 /usr/local/bin/dnscache

che mostrano tutti i processi coinvolti. Notare che la gestione dei log è fatta attraverso un daemon separato e infatti si troverà all’interno della directory log in /etc/dnscache una directory *service* esattamente come in */etc/dnscache*.

Per fermare e riavviare dnscahe si può ricorrere a

svc -d /etc/service/dnscache
svc -u /etc/service/dnscache

o anche a un più brutale kill del daemon dnscache: ci penseranno i daemontools a riavviarlo.

Per meglio gestire le zone interne o se si vuole sfruttare qualche dns interno si consideri che in /etc/dnscache/root/servers si possono aggiungere file, denominati come un dominio e contenenti una lista di DNS interrogabili per quel dominio. Ad esempio con:

cd /etc/dnscache/root/servers
echo 213.251.188.150 > linuxandcompany.it
echo 213.251.128.150 > linuxandcompany.it
svc -d /etc/service/dnscache
svc -u /etc/service/dnscache

posso dire alla cache dove di preferenza deve rivolgersi per le risoluzioni del dominio di questo sito.

Questa directory contiene di base un file denominato @ in cui è contenuta la lista dei root dns di internet.

Awstats e analisi dei log

Generalmente l’analisi del traffico sui siti web viene fatto strumenti sul genere di google analytics che si basano su chiamate dei client, generate da javascript, a siti di terze parti che si occupano della raccolta e della reportistica dei dati.

I server web generano però enormi quantità di log che contengono molti dettagli su tutte le attività del sito web. Nel caso di problemi specifici gli strumenti per analizzarli sono i vari tool della shell e il magari il perl ma se quello che si vuole è monitorare l’andamento del sito la cosa migliore è ricorrere a tool specifici. Uno dei tool storici per questa attività in ambiente LAMP è awstats.

Awstats è un insieme di scripts che si occupa di analizzare i log che gli vengono serviti e genera dei report fruibili poi attraverso un server web. Awstats è in grado di lavorare molti tipi di log che spaziano da quelli di svariati tipi di web server (come Apache o anche IIS) a quelli di ftp server o mail server.

Awstats non è il solo tool e loro stessi riportano una tabella di comparazione con altri prodotti analoghi.

Passiamo ora ad esaminare come configurare awstats. Come iare sempre non troverete qui una guida completa ma un punto di partenza per poter iniziare ad esplorare le potenzialità di questo prodotto.

La prima cosa è ovviamente istallarlo. Anche awstats è presente in gran parte delle distribuzioni. Su debian/ubuntu potrete quindi procedere con:

aptitude update
aptitude search awstats
aptitude install awstats apache2

Alternativamente dal sito di awstats è possibile effettuare il download dell’ultima versione stabile.

Se si procede da pacchetto si avranno a questo punto nella directory

/usr/share/doc/awstats/examples

molti file di esempio utili.

Iniziamo ora dalla configurazione di apache necessaria per la visulizzazione dei report: bisogna infatti istruire apache a mostrare i report aggiungendo ad un vhost le seguenti direttive:

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
    AllowOverride None
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    Order allow,deny
    Allow from all
</Directory>

Alias /awstatsclasses "/usr/share/awstats/lib/"
Alias /awstats-icon "/usr/share/awstats/icon/"
Alias /awstatscss "/usr/share/doc/awstats/examples/css"
ScriptAlias /awstats/ /usr/lib/cgi-bin/
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch

E’ necessario poi riavviare apache.

Passiamo ora all’anailsi dei file. Cominciamo facendo una copia del file /etc/awstats/awstats.conf rinominandolo in awstats.linuxandcompany.it.conf.
Le righe fondamentali da editare al suo interno sono

  • LogFile: che dovrà contenere il nome completo di path dei log da esaminare
  • SiteDomain: che conterrà il nome del dominio
  • HostAliases: per indicare, separati da spazi, eventuali nomi alternativi del dominio
  • LoadPlugin: vorremo probabilmente aggiungere almeno “ipv6”
  • LogFormat: permette di descrivere in dettaglio il formato dei log se non sio è utilizzato quello standard di apache o squid.

I parametri configurabili sono veramente molti e, se non la documentazione, il file merita almeno una scorsa per individuare elementi di interesse.

A questo punto non rimane che scaricare il file di log nella directory e far parsare il file ad awstats. Da sottolineare che i vari file di log devono essere processati nell’ordine in cui vengono generati e che il nome nella configurazione è univoco e bisognerà quindi o copiare giorno per giorno il file di log nella directory designata sovrascrivendo il precedente o sfruttare un link simbolico. Si tratta comunque di un’attività facilmente automatizzabile.

Procediamo quindi con il parse

/usr/lib/cgi-bin/awstats.pl -config=linuxandcompany.it -update

Se apache è stato configurato sulla propria macchina e se l’elaborazione è stata fatta localmente sarà possibile vedere il report sull’url

http://localhost/awstats/awstats.pl?output=main&config=linuxandcompany.it&framename=index

MongoDB replicaset test drive

Segnalo un’interessante iniziativa realizzata dal team di MongoDB con il supporto di amazon:

Accedendo a  test drive è possibile accedere ad un’istallazione di test di mongodb su cui provare il sistema di replica e testarne l’ottimizzazione.

squid come reverse proxy

Squid è un prodotto eccellente per fare da web cache; sia che si voglia alleggerire il carico sulla connessione, sia che si voglia fare del filtraggio a livello applicativo sia che si abbia la necessità di fare del monitoraggio molto dettagliato dell’uso del web su di una rete, la flessibilità di questo applicativo permette di intervenire praticamente su ogni aspetto del protocollo http e non solo.

Segnalo fin da subito che squid, come tutti i prodotti di web cache, richiede un’ attenzione particolare alla configurazione, in particolare per l’aspetto dei permessi; si corre il rischio, infatti, di mettere su internet un server proxy che può essere usato da terzi per mascherare le proprie attività.

Oggi voglio mostrare come configurare squid per usarlo come reverse-proxy. Si parla di proxy quando la cache viene messa davanti ai client e risolve al posto di questi le chiamate su internet. Si parla di reverse-proxy quando il server viene posto davanti ai web server e raccoglie al posto dei server web le chiamate dei client.

La ragione base per cui si fa questo è migliorare le prestazioni incidendo il meno possibile sui costi. I server cache infatti mantengono in memoria e/o su disco le risposte alle richieste più frequenti dei client e rispondono direttamente senza convolgere i server web; infatti  sono in genere in grado si rispondere ad un numero di richieste molto più alto di un server web che sempre più spesso deve essere configurato per gestire elaborazioni pesanti.

Squid è un prodotto molto consolidato ed è presente oramai sui tutte le distribuzioni linux. Per istallarlo quindi in genere la cosa più semplice è ricorrere ai tool della propria distribuzione. Su Debian/Ubuntu:

sudo aptitude update
sudo aptitude search squid
sudo aptitude install squid

La configurazione di default di squid è molto ben fatta e contiene così tanti commenti da essere quasi completamente autoesplicativa; merita un’esame se si vuole utilizzare questo prodotto. Nel nostro caso però la costruiremo da zero. Come risulta dalla documentazione ufficiale ci sono più possibilità per la configurazione in modalità Reverse Proxy; ne analizzeremo una di uso ragionevolmente generale.

http_port 80 accel defaultsite=[server name] vhost

cache_peer [IP sorgente 1] parent 80 0 no-query originserver name=RevPro1
acl web1 dstdomain [accelerated domains list 1]

cache_peer [IP sorgente 2] parent 80 0 no-query originserver name=RevPro2
acl web2 dstdomain [accelerated domains list 2]

http_access allow web1
http_access allow web2
cache_peer_access RevPro1 allow web1
cache_peer_access RevPro2 allow web2

cache_dir diskd /var/cache/squid3 1000 32 32 Q1=64 Q2=72
cache_mem 1024 MB

cache_replacement_policy heap LFUDA
memory_replacement_policy heap GDSF
cache_swap_low  90
cache_swap_high 95

Vediamo ora questa configurazione in maggiore dettaglio:

http_port 80 accel defaultsite=[server name] vhost

In questo modo si dice a squid che lavorerà in modalità reverse-proxy e con che tipo di configurazione. Defaultsite conviene valorizarlo con il nome del server web, quali sono i siti di cui si fa reverse proxy viene specificato più avanti. Vhost specifica che la discriminante per la scelta del server di origine sarà il dominio.

cache_peer [IP sorgente 1] parent 80 0 no-query originserver name=RevPro1

Definiamo qui un server web di cui fare reverse proxy e assegniamo un nome a questa definizione (RevPro1).

acl web1 dstdomain [accelerated domains list 1]

Definiamo una lista di siti web (separati da spazi) di cui si vuole fare reverse proxy e le assegnamo un nome (web1).

cache_peer [IP sorgente 2] parent 80 0 no-query originserver name=RevPro2
acl web2 dstdomain [accelerated domains list 2]

Ripeto per un secondo server origin e una seconda lista di siti.

http_access allow web1
http_access allow web2

dico a squid che deve rispondere alle richieste (dei client) relative alle due liste di siti web1 e web2.

cache_peer_access RevPro1 allow web1
cache_peer_access RevPro2 allow web2

Associo le origin alle liste dei siti supponendo che un dato server web di origine serve alcuni dei siti mentre l’altro serve gli altri.

cache_dir diskd /var/cache/squid3 1000 32 32 Q1=64 Q2=72
cache_mem 1024 MB

cache_replacement_policy heap LFUDA
memory_replacement_policy heap GDSF
cache_swap_low  90
cache_swap_high 95

Infine queste righe configurano la cache.

Come quasi sempre su squid si definiscono degli oggetti per definire attività da permettere o vietare; si definiscono delle acl per definire il “chi” ed infine si associano questi due tipi di informazione in righe dove si definisce chi può fare cosa.

Come sempre prima di mettere in produzione un server con squid è bene leggerne la documentazione ufficiale.

gmail – cambiare il sender

La posta di gmail offre uno strumento poco noto ma molto utile. E’ possibile inviare posta con un sender differente dall’account con cui si è fatto login.

Questa feature di gmail può tornare utile in almeno due casi:

  1. se si hanno diverse caselle di posta sfruttando il cambio di sender e la possibilià di scaricare posta da altre caselle, per concentrare la gestione della propria posta in un unico punto.
  2. se si gestisce un dominio con centinaia di caselle su cui con ogni probabilità verranno richieste caselle non personali di cui è fin troppo facile perdere traccia, da la possibilità di limitare gli utenti alle caselle personali e usare i gruppi per tutte le caselle funzionali come le varie webmaster, postmaster etc.. Il vantaggio è doppio: igruppi non si pagano e  l’amministratore può ricostruire facilmente da chi vengono letti.

Per sfruttare questa feature bisogna:

  1. Fare login sulla webmail
  2. entrare nei settings del proprio account
  3. Selezionare il tab Accounts
  4. selezionare “Add another address you own”
  5. Sulla finestra di pop-up inserire il nome che si vuole mostrare accanto all’indirizzo nell’header From:”
  6. Sulla finestra di pop-up inserire l’email che si vuole nel campo From
  7. Sulla finestra di pop-up togliere il check “Treat as an alias box”
  8. Selezionare Next Step
  9. Si riceverà sull’altro account (o gruppo) un’email con un link di autorizzazione
  10. Confermare selezionando il link nell’email
  11. Tornare alla finestra di pop-up e concludere la procedura.

Da questo momento è possibile mandare la posta con unb altro Sender.

Howto – ambiente di test – mysql – replica master-master

Vediamo ora come implementare una replica master-master per MySQL.

Per prima cosa bisogna scaricare  il database MySQL. Per questo ambiente di test scarichiamo il pacchetto binario dal sito MySQL,

Ad esempio su di una distribuzione debian-like procederemo come al solito con

aptitude search mysql 
aptitude install mysql-server

Andiamo ora a creare due istanze del db.

Creiamo come root le datadir

mkdir /var/lib/mysql-mm-1
mkdir /var/lib/mysql-mm-2

Sempre come root prepariamo le configurazioni

cp -rp /etc/mysql/my.cnf /etc/mysql-mm-1/my.cnf
cp -rp /etc/mysql/my.cnf /etc/mysql-mm-2/my.cnf

Editiamo ora il file my.cnf fino ad ottenere per il nodo 1 qualche cosa come

[client]
port            = 3307
socket          = /var/run/mysqld/mysqld-mm-1.sock

[mysqld_safe]
socket          = /var/run/mysqld/mysqld-mm-1.sock
nice            = 0

[mysqld]
user            = mysql
pid-file        = /var/run/mysqld/mysqld-mm-1.pid
socket          = /var/run/mysqld/mysqld-mm-1.sock
port            = 3307
basedir         = /usr
datadir         = /var/lib/mysql-mm-1
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
key_buffer              = 16M
max_allowed_packet      = 16M
thread_stack            = 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit       = 1M
query_cache_size        = 16M
log_error = /var/log/mysql/error-mm-1.log
server-id               = 11
log_bin                 = /var/log/mysql/mm-1-bin.log
expire_logs_days        = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet      = 16M

[mysql]

[isamchk]
key_buffer              = 16M

e per il nodo 2 qualche cosa come

[client]
port		= 3308
socket		= /var/run/mysqld/mysqld-mm-2.sock

[mysqld_safe]
socket		= /var/run/mysqld/mysqldi-mm-2.sock
nice		= 0

[mysqld]
user		= mysql
pid-file	= /var/run/mysqld/mysqld-mm-2.pid
socket		= /var/run/mysqld/mysqld-mm-2.sock
port		= 3308
basedir		= /usr
datadir		= /var/lib/mysql-mm-2
tmpdir		= /tmp
lc-messages-dir	= /usr/share/mysql
skip-external-locking
bind-address		= 127.0.0.1
key_buffer		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit	= 1M
query_cache_size        = 16M
log_error = /var/log/mysql/error-mm-2.log
server-id		= 12
log_bin			= /var/log/mysql/mm-2-bin.log
expire_logs_days	= 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet	= 16M

[mysql]
[isamchk]
key_buffer		= 16M

da notare in questi file:

  • port: ho usato porte non standard e diverse per i due nodi
  • server-id: ho dato due valori diversi ai due nodi
  • log-bin: ho abilitato i binary log
  • datadir: specifica per ogni nodo.

Devo ora popolare le directory dei dati:

mysql_install_db --user=mysql  --defaults-file=/etc/mysql-mm-1/my.cnf
mysql_install_db --user=mysql  --defaults-file=/etc/mysql-mm-2/my.cnf

Posso ora avviare i due database

mysqld_safe --defaults-file=/etc/mysql-mm-1/my.cnf &
mysqld_safe --defaults-file=/etc/mysql-mm-2/my.cnf &

Al primo avvio dovranno essere generati tutta una serie di file ma dopo un po’ i due database dovrebbero essere accessibili:

mysql --port 3307 --host 127.0.0.1 --user=root
mysql --port 3308 --host 127.0.0.1 --user=root

NB: su Ubuntu apparmor impedisce di eseguire questa procedura. Per escluderlo

aa-complain /usr/sbin/mysqld

Trattandosi di due istanze appena create non ci dobbiamo preoccupare allineare la situazione iniziale dei due nodi mysql imprtando dei backup.

Procediamo quindi a configurare il nodo mysql due come replica del nodo mysql uno.

Per prima cosa dobbiamo vedere lo stato come master del nodo uno:

mysql --port 3307 --host 127.0.0.1 --user=root
show master status\G

otterremo qualche cosa come

*************************** 1. row ***************************
            File: mm-1-bin.000005
        Position: 107
    Binlog_Do_DB: 
Binlog_Ignore_DB: 
1 row in set (0.00 sec)

Nel caso avessimo ripristinato da un backup avremmo dovuto far riferimento a informazioni analoghe ma prese al momento del backup.
Abilitiamo ora la prima replica

mysql --port 3307 --host 127.0.0.1 --user=root
change master to MASTER_HOST='127.0.0.1', MASTER_PORT=3307 , MASTER_USER='root' , MASTER_LOG_FILE = 'mm-1-bin.000005', MASTER_LOG_POS =107;
show slave status\G

Ho qui utilizzato l’utente root. In condizioni non di test bisognerebbe utilizzare un utente con gli appositi permessi (REPLICATION_CLIENT, REPLICATION_SLAVE).
Lo stesso per l’altro nodo

mysql --port 3308 --host 127.0.0.1 --user=root
change master to MASTER_HOST='127.0.0.1', MASTER_PORT=3308 , MASTER_USER='root' , MASTER_LOG_FILE = 'mm-2-bin.000005', MASTER_LOG_POS =107;
show slave status\G

mysql e binlog

Una delle caratteristiche più interessanti di MySQL è il suo sistema di replica. Questo lo rende possibili molte configurazioni che rendon il prodotto estremamente flessibile.

Si può abilitare MySQL per replicare tutte le query che comportano delle modifiche alla base dati su degli appositi file di log detti binlog. Questo, con le nuove versioni di MySQL può avvenire in due modi: riportando le query o riportando le modifiche ai file. Non approfondiremo oltre, in questo post, le differenze tra queste due modalità e faremo riferimento al caso in cui vengano riportate le query. Gran parte delle considerazioni rimangono valide in entrambi i casi.

I binlog servono almeno in 3 situazioni molto critiche:

  • assieme ai backup per la Point in Time Recovery: se al momento del backup si sono salvati valori riportati da “show master status\G”, è possibile riapplicare tutte le modifiche effettuate al database e riporatte sui binary log fino ad un preciso istante. Idealmente ricosytruendo lo sato del database subito prima del problema.
  • sistemi in replica: i binlog solo lo strumento attraverso il quale i vari nodi in replica si comunicano le rispettive modifiche alla base dati.
  • analisi a posteriori di bug o attacchi

Si noti, avendone la possibilità, è opprtuno valutare se farli scrivere su dischi e attraverso un controller diverso da quelli utilizzati per i file di dati, trattandosi di una scrittura potenzialmente pesante.

La generazione dei bynary log su MySQL non è abilitata di default ma bisogna aggiungere un gruppo di statement di configurazione come il seguente prima delle configurazione dei vari Engine :

server-id = 1
log_bin = /data/log/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 500M
log_slave_updates = 1
#binlog_do_db = include_database_name
#binlog_ignore_db = include_database_name

la lista dei parametri sopra non è esaustiva.

  • server-id: questo valore è fondamentale nei sistemi in replica in quanto permette al server di capire quale nodo ha ricevuto la query per primo. Bisogna quindi assegnare un numero univoco per ogni server coinvolto
  • log_bin: è il template del nome del file che viene generato. Questa linea abilita anche i binlog.
  • expire_log_days: definisce per quanti giorni i binlog vengono mantenuti da MySQL sul disco
  • max_binlog_size: definisce le dimensioni massime del file. Al raggiungimento di questo limite il file viene chiuso e ne viene aperto un’altro. Nello scegliere le dimensioni di questi file bisogna fare un compromesso tra le loro dimensioni e il loro numero per trovare la situazione più facilmente gestibile.
  • log_slave_updates: nei sistemi in replica permette di specificare se il nodo deve salvare sul binlog anche le query che ha ricevuto dal proprio master o solo eventuali query ricevute direttamente
  • binlog_do_db: se presente specifica la lista dei database le cui modifiche devono essere riportate sui binary log
  • binlog_ignore_db: se presente specifica la lista dei database le cui modifiche non devono essere riportate sui binary log

Attenzione a statement come gli ultimi due:

  • hanno comportamenti differenti nel caso si abbiano nei binary log le query o le modifiche
  • il db a cui si riferische è quello della connessione che non è necessariamente quello della query (es. select * from agenda.telefono)

E’ assolutamente opportuno un approfondimento sui manuali ufficiale prima di procedere con sistemi di produzione.

Come si può dedurre dal nome indipendentemente dal formato scelto i binlog sono file binary non facilmente gestibili con gli strumenti standard di UNIX. Viene in aiuto un tool specifico mysqlbinlog. Questo comando applicato ad una serie di file manda in standard output i comandi sql nella giusta sequenza temporale. Ha molte opzioni e, ovviamente, bisogna far riferimento al suo manuale prima di usarlo su sistemi di produzione.

The Capitalist Game

In The Capitalist game si è alla guida di una società che dovrà produrre e vendere beni in un mercato simulato.

Il gioco è basato su turni di un’ora  che corrispondono ad un mese all’interno del gioco. Ogni società ha 40 anni (di gioco) per svilupparsi prima di venir cancellata. Lo stesso giocatore potrà poi dare vita ad altre società.

Si hanno a disposizione tre tipi di infrastrutture: fabbriche, magazzini e negozi per produrre beni, immagazzinarli e venderli. Non tutti i prodotti sono vendibili direttamente nei negozi essendoci numerosi semilavorati che servono come step intermedi per la produzione di altri beni.

Un sistema di licenze permette di sbloccare, con il crescere della società, sempre nuovi prodotti.

Parallelamente è presente una “borsa azioni” in cui il giocatore può comprare e vendere quote delle società in gioco.

E’ presente infine un mercato da cui è sempre possibile acquistare e vendere beni.

Le varie società attive non sono realmente in concorrenza e l’interazione si riduce quasi solo al confronto sulle classifiche.

Il gioco è comunque piacevole ed equilibrato.

perl e memcached

Creaiamo un ambiente di test  per memcached e testiamolo con perl.

Utilizzeremo:

  • memcahced
  • perl
  • il modulo perl Cache::Memcached
  • il modulo perl String::Random
  • il comando di monitoraggio memcache-top

Do per scontato che perl sia installato. Questo è, credo, praticamente sempre vero in ambiente UNIX/LInux.

Potrebbe non essere invece presente il modulo perl per la gestione della cache con memcached. Lo si può istallare o come pacchetto dell distribuzione (es. su Debian/Ubuntu qualche cosa come sudo aptitude install libcache-memcached-perl)  o come modulo cpan:

$ cpan
cpan[1]> install Cache::Memcached

Analogamente istalliamo il modulo String::Random (libstring-random-perl).

Passiamo ora al daemone.memcached. Consideriamo come sempre un sistema Debian/Ubuntu dove basterà:

# aptitude update
# aptitude install memcached
# update-rc.d -f memcached remove
# /etc/init.d/memcached stop

Il primo comando aggiorna le liste dei pacchetti, il secondo istalla il demone. Dato che stiamo lavorando su di un ambiente di test, con il terzo comando si facciamo in modo che il demone non venga avviato all’avvio del pc e con l’ultimo fermiamo il demone lanciato con le configurazioni di default.

Memcached è presente in quasi tutte le distribuzioni e se non si utilizza un sistema Debian o derivato si può far ricorso al sistema di pacchettizzazione della propria distribuzione o anche ai sorgenti del demone recuperabili dal sito ufficiale.

L’ultimo elemento per l’ambiente di test è memcache-top, che può essere scaricato dal sito ufficiale.

wget -O memcache-top  http://memcache-top.googlecode.com/files/memcache-top-v0.6

Ora che abbiamo tutti gli elementi possiamo aprire una shell e avviare 3 demoni per avere un pool non banale. Li metteremo in ascolto su localhost ma, ovviamente, su porte differenti.

/usr/bin/memcached -d -m 64 -p 11211 -u memcache -l 127.0.0.1
/usr/bin/memcached -d -m 64 -p 11212 -u memcache -l 127.0.0.1
/usr/bin/memcached -d -m 64 -p 11213 -u memcache -l 127.0.0.1

possimao poi avviare in un’altra shell memcache-top con cui monitereremo il nostro pool di server

memcache-top --commands --instance=127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213

Vediamo ora un primo semplice script perl che chiameremo memcahced.pl:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

$memd->set("my_key", "Some Value\n");
my $val = $memd->get("my_key");
if ($val) { print $val; }

In dettaglio:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;

Richiama l’eseguibile attivando i warning e i moduli strict e Memcached.

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

definisce l’oggetto di connessione a memcached inserendo una lista di server.

$memd->set("my_key", "Some Value\n");
my $val = $memd->get("my_key");
if ($val) { print $val; }

scrive una chiave su memcached e la rilegge andando poi a scrivere a video il risultato.
Per eseguire questo script non bisogna dimenticare di rendere eseguibile il file o richiamarlo esplicitamente con l’interprete perl.

Con una singola esecuzione memcache-top non dice gran che ma se si esegue ripetutamente lo script ad esempio con

while [ true ]; do ./memcached.pl ; done

si vedranno numerosi accessi ad un singolo server; come ci si sarebbe dovuti aspettare trattandosi di una sola chiave.
Nel mio caso memcahce-top riporta:

memcache-top v0.6	(default port: 11211, color: on, refresh: 3 seconds)

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.0%	0.0%	5	1.0ms	0.0	0	0	2	344	
127.0.0.1:11212		0.0%	0.0%	5	0.8ms	0.0	0	0	2	344	
127.0.0.1:11213		0.0%	0.0%	5	0.6ms	0.0	14	14	647	992	

AVERAGE:		0.0%	0.0%	5	0.8ms	0.0	5	5	217	560	

TOTAL:		83B/	0.2GB		15	2.4ms	0.0	14	14	651	1680	
(ctrl-c to quit.)

ma soprattutto dopo aver ucciso il demone in ascolto sulla porta 11213

memcache-top v0.6	(default port: 11211, color: on, refresh: 3 seconds)

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.0%	0.0%	5	0.6ms	0.0	0	0	2	344	
127.0.0.1:11212		0.0%	0.0%	5	0.4ms	0.0	15	15	662	1007	
127.0.0.1:11213 is DOWN.

AVERAGE:		0.0%	0.0%	3	0.3ms	0.0	5	5	221	450	

TOTAL:		83B/	0.1GB		10	1.0ms	0.0	15	15	664	1351	
(ctrl-c to quit.)

dove so vede che le connessioni si sono spostate su di un’altro demone. Su questo aspetto bisogna fare molta attenzione dato che in un sistema di produzione la gestione dei fault  è cruciale e dato che questo aspetto è demandato alla libreria del linguaggio che si utilizza  bisogna averne molto chiaro il comportamento. Vale comunque in generale che bisognerebbe avere in numero sufficiente di server per cui se se ne dovesse perdere uno i restanti dovrebbero essere sufficienti a svolgere i loro compiti; in altre parole è molto meglio distribuire il servizio su molti serevr che averne uno o due dedicati.

Per vedere in che modo le chiavi si distribuiscono sul cluster ora inseriamo un valore fisso in una chiave di nome casuale (la distribuzione è legata al nome della chiave). Per farlo utilizziamo un altro script perl che chiameremo memcahced_random.pl:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;
use String::Random;

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

my $foo = new String::Random;
my $random_key = $foo->randpattern("...");
my $value = "chiave casuale";

$memd->set("$random_key", "$value\n");
my $val = $memd->get("$random_key");
if ($val) { print $val; }

Non è molto diverso dal precedente tranne nella riga:

my $random_key = $foo->randpattern("...");

in cui viene generata casualmente una stringa di 3 caratteri da utilizzare come nome della chiave.
Anche in questo caso conviene inserire lo script in un loop per visualizzarne meglio gli effetti con memcache-top:

while [ true ]; do ./memcached_random.pl; done

Le connessioni si andranno a distribuire sui vari nodi in modo ragionevolmente equilibrato

memcache-top v0.6	(default port: 11211, color: on, refresh: 3 seconds)

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.1%	0.0%	5	0.9ms	0.0	7	7	317	686	
127.0.0.1:11212		0.1%	0.0%	5	0.6ms	0.0	9	9	375	748	
127.0.0.1:11213		0.1%	0.0%	5	0.6ms	0.0	9	9	403	779	

AVERAGE:		0.1%	0.0%	5	0.7ms	0.0	8	8	365	738	

TOTAL:		132.7KB/	0.2GB		15	2.2ms	0.0	25	25	1095	2213	
(ctrl-c to quit.)

Cluster Mysql

Una delle caratteristiche più interessanti di mysql è la possibilità di replicare le modifiche al database su di un altro nodo che verrà mantenuto quindi aggiornato e al tempo stesso sarà disponibile per effettuare query in lettura,  backup o altro.

Questa caratteristica da luogo alla possibilità di implementare cluster con configurazioni (topologie) più o meno articolate che, avendo caratteristiche differenti, potranno tornare utili in situazioni diverse.

Quello che si cerca di ottenere in genere con queste configurazioni è in genere ridondanza per gestire fault dei server minimizzando il disservizio, scalabilità orizzontale nelle prestazioni o entrambe. Per scalabilità orizzontale si intende la possibilità di aggiungere server al sistema per aumentarne le prestazioni e si contrappone alla scalabilità verticale in cui per far crescere le prestazioni bisogna sostituire l’hardware precedente con altro in genere molto più costoso.

Altre volte quello che si vuole è un nodo disponibile per attività non standard: la possibilità di effettuare dei backup fuori linea, un ambiente su cui generare reportistica senza impattare sulle prestazioni del sistema di produzione, un ambiente su cui fare debugging o altro.

La configurazione più semplice possibile è una configurazione master-slave. In questa configurazione un solo nodo del cluster riceve le chiamate che modificano i dati mentre l’altro è disponibile per altre attività come i backup, attività di reporting o anche l’esecuzione di query di sola lettura di dati.

Sono utili alcune precisazioni:

  • mysql implementa la replica ma nessun meccanismo di distribuzione delle query. E’ demandato all’applicativo l’onere di inviare le query al server giusto. Questo si può fare sia tramite logica interna al client, sia con strumenti che si interpongono tra client e server quali proxy, pooling o loadbalancing.
  • mysql implementa la replica ma non implementa nessun meccanismo di tackeover: se il master dovesse morire il cluster perderebbe il nodo su cui vengono eseguite le query le query in scrittura. Anche in questo caso è demandato a strumenti esterni quali script, load balancing o lo stesso applicativo client l’onere di implementare la ridondanza. C’è da dire poi che questa non è la tipologia di cluster migliore per implementare la ridondanza data la sua forte asimmetria.
  • Se si utilizza il nodo mysql slave per fare dei backup si può avere un dump coerente con strumenti standard anche solo interrompendo i processi di replica; in questo modo infatti si sono sospese tutte le scritture sul nodo in esame.
  • se si utilizza il nodo mysql slave per fare dei backup e si vuole poter sfruttare il point in time recovery, bisogna abilitare i binary log anche sul nodo slave dato che sono questi che dovranno essere associati al backup stesso. O, meglio, bisogna associare al backup l’informazione dello stato della replica rispetto ai binary log del master; informazioni disponibili sullo stato della replica.

Una topologia di replica secondo me molto più interessante e flessibile è quella master-master. In questa configurazione si ha una coppia di server mysql ognuno dei quali replica le modifiche dell’altro.

Questa configurazione è estremamente flessibile perché, data la sua simmetria, è molto facile realizzare un sistema in High Havailability in cui un nodo venga sostituito dall’altro. E’ possibile farlo con diversi struementi, ad esempio heartbeat o un bilanciatore. Inoltre si può sia sfruttare entrambi i nodi per le scritture, sia utilizzare il cluster master-master come se fosse un sistema master-slave sia sfrutatrlo solamente per la ridondanza accedendo in condizioni normali ad un solo nodo. In tutti i casi però la simmetria del sistema fa si che nell’automatizzare lo scambio dei ruoli dei server non sia necessario intervenire sulla configurazione del database.

Ovviamente questa semplicità ha un costo, o se si preferisce i problemi vengono spostati altrove. In questa configurazione ci sono molte sottigliezze legate alla replica di cui l’applicazione deve tenere conto per non dare risultati sbagliati; sottiglizze legate soprattutto agli autoincrement, agli ordinamenti e a considerazioni di ACID compliance.

Se i nodi sono più di due le possibilità sembrano moltiplicarsi ma le vie ragionevolmente percorribili non sono poi così tante.

La prima possibilità è quella di avere un master e molti slave: questa configurazione, molto utile per i siti web in quanto permette di far crescere quasi a piacere la capacità di gestire query in lettura, presenta il non piccolo problema di non essere ridondata sul nodo master. L’implementazione di script che promuovono uno dei nodi slave al ruolo di master nel caso di fault del master precedente presenta moltissimi problemi: si pensi solo alla difficoltà di determinare qual’è lo slave più avanti con la replica e quindi più adatto a prendere il ruolo di master, per non parlare dei problemi nel riattestare tutti gli slave sul nuovo master.

Più praticabile, ma ovviamente molto più costosa, è la possibilità di implementare un cluster classico active-passive con disco condiviso per il solo master. Anche in questo caso comunque alcune attività possono rendersi necessarie sui nodi slave in caso di takeover del master.

Si può poi pensare di estender il caso di configurazione master-master  in una topologia ad anello in cui ogni nodo è replica del precedente e il primo è replica dell’ultimo. Questa configurazione nella pratica non è una grande idea per due ragioni:

  • il sistema nel suo insieme è sempre meno affidabile al crescere del numero dei nodi perché la rottura di un elemento qualsiasi della catena interrompe la propagazione delle modifice e bisogna quindi implementare una logica tutt’altro che banale che automatizzi l’esclusione di nodi rotti. Nodi che soarà poi comunque difficile reinserire senza dare disservizi.
  • ci sono potenziali problemi insidiosi legati al meccanismo di replica di mysql; questo infatti legge tutte le query nel binary log che ha in ingresso e se trova che il primo esecutore di una query è stato un altro nodo la esegue e la scrive nel proprio binary log altrimenti, se cioè riconosce di essere stato il primo esecutore di una query, la scarta. Tutto funziona bene fino a che non muore un nodo. Si pensi a cosa succede se in un sistema di tre server mysql A, B e C configurati ad anello: A master di  B master di C master di A si verifica la seguente situazione. Il nodo A esegue una query di insert e poi muore ad esempio per la rottura di un disco ma solo dopo aver passato l’insert al nodo B. Gli script del sistema riconoscono il problema ed escludono il nodo A rendendo C master di B. B riconosce come non propria la query ricevuta da A, la esegue e la passa a C; C riconosce la query come non propria, la esegue e la passa a B etc in un loop infinito. Per la verità le versioni più recenti di mysql hanno introdotto funzionalità per controllare questo problema ma la situazione rimane delicata.

Un’ultima configurazione che si può pensare di implementare è master-master ( o anche un anello con n master) su cui attestare un gruppo di slave. Gli slave andranno ovviamente divisi tra i master. Il problema di questa configurazione è che se uno dei master viene meno, si perde la possibilità di utilizzare tutti gli slave che a lui fanno capo a meno di non aver implementato una logica che permetta di riattestare gli slave sul master residuo.