controllo dei processi – supervisord

Parliamo oggi di supervisord, un sistema di controllo dei processi. Questi sistemi hanno la funzione di garantire che altri processi siano costantemente attivi. Possono essere quindi elementi importanti per garantire l’affidabilità di un servizio soprattutto nei casi in cui il daemon da controllare è sviluppato in casa e da, quindi, molta meno affidabilità di un daemon con migliaia di istallazioni.

Il primo passo è, come sempre, istallare supervisord. Lo possiamo fare dal sito del progetto o utlizzando il sistema di pacchetti della propria distribuzione. Su Debian o Ubuntu:

aptitude install supervisor

Ad istallazione effettuata si avrà il demone supervisord e il tool di controllo supervisorctl. Inoltre il demone può essere configurato per rispondere a chiamate html (di default sulla porta 9001) sia per monitoraggio via browser sia per gestione tramite API xml-rpc.

Per prima cosa esaminiamo la configurazione di default che arriva con il pacchetto Ubuntu:

; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                       ; sockef file mode (default 0700)

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.conf

Come avviene in moltissimi casi i parametri di configurazione sono raggruppati in paragrafi.

  • [unix_http_server]: contiene i parametri necessari a definire il socket unix per le connessioni a supervisord.
  • [supervisord]: configurazione vera e propria del daemon; file di log, file di lock.
  • [rpcinterface:supervisor]: serve ad abilitare l’interfaccia rpc
  • [supervisorctl]: parametri passati usati al client; ad esempio come connettersi al server se non specificato altrimenti
  • [include]: un elenco di file da includere. Tipicamente uno per ogni tipo di daemon da controllare con le specifiche direttive.

Come prima cosa attiviamo l’interfaccia web abilitando un’utenza ed una password. Aggiungiamo o modifichiamo quindi il paragrafo inet_http_server con i seguenti valori:

[inet_http_server]
port = 127.0.0.1:9001
username = user
password = 123

e riavviamo il daemon

/etc/init.d/supervisord restart

Possiamo ora accedere all’interfaccia web connettendoci in localhost sulla porta 9001 ed inserendo i dati dell’account quando richiesti.

Creiamo  ora uno script che utilizzeremo come daemon di test e configuriamo supervisord per gestirlo.

Editiamo quindi un file denominato test_supervisord.sh  con il nostro editor di riferimento ed inseriamoci il seguente codice:

#!/bin/sh
while [ true ]; do 
	ls -lh /tmp/
	rm /tmp/pippo
	touch /tmp/pippo
	sleep 5
done

cambiamo poi i permessi per renderlo eseguibile

chmod 700 test_supervisord.sh

Creiamo ora la configurazione su supervisord per dirgli di gestire questo daemon. Andremo ad inserire nel file /etc/supervisor/conf.d/test.conf il seguente contenuto

[program:test_supervisord]
command=/tmp/test_supervisord.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/test.err
stdout_logfile=/var/log/supervisor/test.log

dove, in un apposito paragrafo specifichiamo

  • [program:test_supervisord]: il nome del daemon da riportare sui vari report
  • command=/tmp/test_supervisord.sh: il comando da eseguire
  • autostart=true: se deve partire automaticamente all’avvio
  • autorestart=true: se deve essere riavviato in caso di interruzione
  • stderr_logfile=/var/log/supervisor/test.err: file di log in cui viene registrato l’output del daemon
  • stdout_logfile=/var/log/supervisor/test.log file di log in cui vengono riportato lo standard error del daemon

I parametri specificabili sono molti altri. Ad esempio

  • user: permette di specificare l’utente con cui il daemon viene eseguito
  • environment: accetta coppie chiave-valore che vengono trasformate in variabili di ambiente per l’esecuzione del daemon.

Per rendere attiva la nuova configurazione si può procedere con uno tra

kill -HUP "PID"
supervisorctl reread && supervisorctl update

o anche attraverso l’interfaccia web visto che l’abbiamo attivata.

supervisorctl rimane comunque l’interfaccia di eccellenza di questo sistema.

Al suo avvio presenta l’elenco dei demoni configurati ed il loro stato. Ed il comando help mostra cosa può essere fatto all’interno di questa command line.

root@nb:/etc/supervisor/conf.d# supervisorctl 
test_supervisord                 RUNNING    pid 8443, uptime 0:03:40
supervisor> help

default commands (type help ):
=====================================
add    clear  fg        open  quit    remove  restart   start   stop  update 
avail  exit   maintail  pid   reload  reread  shutdown  status  tail  version

supervisor>

stop, start, restart e status serviranno ad esempio a gestire i vari daemon. reread e update li abbiamo già visti in azione. Quit permette di lasciare questa shell.

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.

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.

MySql High Availability

Voglio qui parlare di “Mysql High Availability”, un libro scritto da Charles Bell, Mats Kindahl e Lars Thalmann ed edito da o’reilly, che ho trovato veramente utile ed illuminante.

Premesso che presuppone una buona conoscenza di MySql, affronta con notevole precisione e ricchezza tutta una serie di argomenti avanzati ma fondamentali se si ha la necessità di lavorare con architetture non banali.

In modo particolare viene affrontata nel dettaglio tutto quello che riguarda la replica e vengono esaminate tutta una serie di soluzioni per implementare architetture che scalano in lettura ma anche in scrittura.

Per meglio comprendere queste architetture vengono esaminati nel dettaglio il processo di replica ed i binary log ed il loro uso per nel Disaster Recovery.

La seconda parte del libro è dedicata al monitoraggio, al troubleshooting legato alla replica ed al disaster recovery.

Un ultima parte è dedicata all’uso di mysql in ambienti cloud e all’introduzione di MySQL Cluster.