Amazon EC2 è una piattaforma di cloud computing che consente di utilizzare server virtuali senza necessità di hardware dedicato. Le caratteristiche del server virtuale sono registrate in una  AMI (Amazon Machine Image). Si può creare una propria AMI partendo da zero, ma è molto più facile e sicuro usare  una delle tante AMI certificate da Amazon e riconfigurarla per le nostre esigenze (installando software, patch  file etc.).

Il problema è che queste AMI evolvono nel tempo. Amazon, o le terze parti, correggono errori e implementano ottimizzazioni che, a volte, sono importanti. Se abbiamo modificato AMI di partenza per adattarla alle nostre esigenze, gestire l'upgrade della AMI è un po' come dover reinstallare il nostro software su nuovo server.

Questo articolo propone un processo di change management per gestire l'aggiornamento delle AMI alla base dei i nostri server virtuali. Nel cloud computing, questa attività avviene molto più spesso che in architetture tradizionali. Dobbiamo infatti essere pronti a frequenti upgrade della infrastruttura, in media anche più di due all'anno.

In estrema sintesi occorre:

  1. fare un backup "ragionato" dei soli dati e programmi specifici presenti sul nostro server,
  2. lanciare una nuova istanza di un server virtuale  partendo da una AMI aggiornata,
  3. applicare sul nuovo server gli aggiornamenti al sistema operativo e installare i pacchetti software  standard eventualmente necessari.
  4. ripristinare dati e programmi dal backup ragionato sul nuovo server
  5. assegnare al nuovo server l'indirizzo del server vecchio

Facile a dirsi ma difficilissimo da farsi. Sopratutto la parte relativa al backup "ragionato"

II processo di upgrade di una AMI deve avvenire sempre partendo da una istanza del nostro server perfettamente funzionante e on-line.

Il primo consiglio: evitate di approfittare di uno spegnimento pianificato o peggio, di un crash del sisstema per effettuare un upgrade della AMI. Infatti non si può essere davvero mai certi di aver backuppato tutte le informazioni necessarie alla esecuzione dei servizi..

Ecco quindi la nostra ricetta in tre atti:

Atto primo: memorizzate tutte le configurazioni che effettuate sulla macchina man mano che le implementate

In accordo con le best practice promosse da ITIL, conviene partire da un elenco dei servizi che "girano" sul nostro server, avendo cura di suddividerli in "Business Services" ovvero i servizi che si interfacciano direttamente con l'utente (es. un sito web) e "Infrastructure Services" ovvero servizi necessari all'erogazione di un servizio di Business (es. mysql, apache etc).

I "Business Services" devono, nel limite del possibile, associati a specifici utenti nella macchina. Un utente specifico (possibilmente non root) si prende la responsabilità degli "Infrastructure Services".

Memorizzate da qualche parte il Kernel ID e il RAM disk ID utilizzati dalla vostra istanza (l'informazione si vede tra i metadati dell'istanza anche nella management console di EC2) ovviamente non dimenticate di appuntarvi l'ID della AMI che avete usato per lanciare l'istanza del vostro server. Queste informazioni potrebbero rivelarsi preziose in caso di crash.

Se avete un CMDB ricordate di catalogare e gestire con cura questi metadati.

Per ogni utente:

  1. preparare uno script in cui registrare ogni operazione di setup che impatta sulla configurazione dei servizi, tipicamente le modifiche a file, installazioni di pacchetti software, setup dei database etc.. In tale script sono da evitare il più possibile chiamate a comandi che richiedono inserimento manuale di informazioni. Se indispensabili, i dati da inserire manualmente devono essere ben documentati nello script stesso. Nel secondo atto, vedrete che tale script andrà eseguito spesso, e nel terzo atto capirete che è bene eseguirlo velocemente. Tutte ragioni per limitare le azioni manuali allo stretto necessario: in unix comandi come ed, awk, patch, diff fanno miracoli.
  2. Insieme a questo script raccogliamo in una directory tutti i file di sistema modificati in sede di configurazione avendo cura di mantenere una copia di tali file nella loro forma orginale. Questo ovviamente significa fare una copia dei file PRIMA di modificarli :-).
  3. Preparate un file che identifica tutti i nuovi file che sono necessari all'esecuzione dei servizi . Ricordatevi, ad esempio, di includere i file configurazione di eventuali vhost su apache, la document root, eventuali log (se vi servono), etc. Nell'elenco dei file da va inserito ovviamente anche lo script di inizialzzazione e eventuali dati a supporto.
  4. Preparate infine anche un file, che contenga l'indicazione dei database e delle utenze su db eventualmente necessarie ai servizi.

Memorizzarte tutte queste informazioni in una directory nella home dell'utente.

Ad esempio, facciamo tre ipotesi:

  1. supponiamo che l'utente u01 sia responsabile del Business Service "shop.company.com",
  2. il servizio sia implementato con un sito web ospitato in un server,
  3. infine ipotizziamo che il server sia realizzato partendo da una una AMI basata su una distribuzione standard di ubuntu per EC2.

La struttura della home directory di u01 contiene:

  • /home/u01/vhosts/shop.company.com : document root del servizio web
  • /home/u01/backup.d/files.archive: un file con l'elenco delle directory e dei file coinvolti nell'erogazione del servizio. In questo esempio è sufficente indicare /home/u01 e /etc/apache/available-sites/shop.cfg
  • /home/u01/backup.d/db.archive : contiene i nomi dei database usati dai servizi
  • /home/u01/rebuild.d/prepareami.sh : è lo script dove memorizziamo eventuali configurazioni al sistema necessari al setup del servizio, nel nostro caso lo script dovrà contenete il comando "a2ensite shop.cfg; /etc/init.d/apache2 reload". Sempre in questo esempio il file dovrà contenere anche i comandi per creare un utente sul database, assegnarli una password e assicurare i grant all'utente e caricare da un backup il database.
  • Ad esempio:
    mysql  << EOFMYSQL
    CREATE USER 'shopdb'@'localhost' IDENTIFIED BY  'secret';
    GRANT ALL PRIVILEGES ON  \`shopdb\` . * TO  'shopdb'@'localhost';
    EOFMYSQL
    if [ -e db.dump.sql ] ; then
        mysql << db.dupmp.sql
    fi
    a2ensite shop.cfg
    /etc/init.d/apache2 reload
    

L'utente ubuntu (ovvero l'utente amministrativo di default per la AMI ubuntu), utilizzando una struttura delle directory del tutto analoga, si farà invece carico delle azioni necessarie ad installare sulla AMI vergine tutti i pacchetti necessari a trasformarla in un server LAMP. Ovviamente tale script dovrà farsi carico anche della creazione dell'utente u01 stesso. Un esempio dello script prepareami.sh dell'utente ubuntu è riportato in fondo a questo articolo.

Gli script ed i file, se ben fatti e ben commentati, assolvono anche alla funzione di documentazione della configurazione macchina. 

Idea: perchè non descrivete i servizi IT ospitati nella vostra AMI in un file RDF utilizzando l'ontolgia ismo (IT service Management Ontology) come vocabolario per i metadati? In fututro potrete usarlo per alimentare automaticamente il vostro CMDB semantico.

 

Atto secondo: replicate, possibilmente automaticamente la vostra macchina partendo dall'AMI vergine

Se avete se seguito il primo atto, ora vi troverete con una macchina in cui sono tracciate tutte le modifiche sistemistiche fatte all'AMI originale. E' tempo di prepararsi ad evolvere.

Dobbiamo ora effettuare il famoso backup "ragionato" e, sopratutto, assicurarci di aver effettivamente ragionato bene durante la sua costruzione. In concreto dobbiamo ora costruire un backup dei soli dati e dei soli programmi aggiunti alla AMI originaria e verificare che questo backup sia ripristinabile.

Cominciamo con il creare un nuovo scritp che produce un file di backup (es. un file tar compresso) di tutti i file necessari all'esecuzione di tutti i servizi allocati all'utente. Nel backup inseriamo anche un dump dei database necessari.

Memorizziamo questo script nella cartella backup.d dell'utente amministrativo. Rendiamo lo script parametrico rispetto al nome dell'utente.

Riprendendo l'esempio precedente, ecco un esempio di script userbackup.sh:

#!/bin/bash
#Utente amministrativo
ADMINUSER="ubuntu"
ADMINGROUP="ubuntu"
#Path per il tar
TARDIR=/mnt/backups


##############################################
# Funzioni di supporto
#############################################
function Usage {
        echo "USAGE: $0 user| user list | ALL"
        echo "ALL means all user except root and $ADMINUSER"
}

############################################
# Questa funzione  crea in /tmp/filelist l'elenco delle directory da 
# backuppare per l'utente passato come primo parametro
function makeFileList {
        USER="$1"
        if [ ! -d "/home/$USER" ] ; then
           echo "Warning:  $USER does not exist - skipped"
           return
        fi

        # Creo un dump delle tabelle dei database
        if [ -e /home/$USER/backup.d/db.archive ] ; then
           mysqldump --databases $(cat /home/$USER/backup.d/db.archive) >\\
                /home/$USER/backup.d/db.dump.sql
           chmod 400 /home/$USER/backup.d/db.dump
        fi

        # Aggiungo alla lista dei file da copiare la home directory e i file specifici
        echo "/home/$USER/" >> /tmp/filelist
        if [ -e /home/$USER/backup.d/files.archive ] ; then
                cat /home/$USER/backup.d/files.archive >> /tmp/filelist
        fi
}

params=$@
if [ $# -eq 0  ] ; then
        Usage; exit
fi

# Se il primpo parametro = "ALL" considera tutti gli utenti eccetto quello amministativo
if [ "$1" = "ALL" ] ; then
        params=$(ls /home | grep -v \ADMINUSER)
fi

#Compone la lista dei file da backuppare
cat /dev/null > /tmp/filelist
for i in $params ; do
        makeFileList $i
done

if [ ! -s /tmp/filelist ] ; then
        echo "Warning: nothing to backup"
        Usage; exit
fi

# Definisce un nome leggibile per il file di backup
if [ $# -gt 1 ] ; then
        TARTAG="someUsers"
else
        TARTAG="$1"
fi

#Esegue il tar
TARPATH="$TARDIR/$(date +%Y%m%d%H%M)-$TARTAG.tar.gz"
mkdir -p "$TARDIR"
tar -czf "$TARPATH" \$(cat /tmp/filelist | sort | uniq)

#Rende il backup leggibile solo all'utente ammnistrativo
chmod 400 $TARPATH
chown $ADMINUSER.$ADMINGROUP $TARPATH

Ora, per semplicità, indichiamo con A l'istanza che vogliamo duplicare e con B il duplicato che vogliamo ottenere:

  1. lanciare una nuova istanza della AMI vergine identica a quella su cui è basata l'istanza A.
  2. su A, eseguire lo script userbackup.sh sull'utente amministrativo e trasferire il file creato su B.
  3. su B ripristinate il backup ed eseguire lo script prepareami.sh dell'utente amministrativo.
  4. Se, quando eseguite prepareami.sh, trovate degli errori vuole dire che il backup fatto su A non era sufficientemente "ragionato". Tornate su A e correggete lo script userbackup.sh ripetendo il processo di copia da A a B.

Continuate così fino a che non sarete certi di aver replicato bene su B tutti i servizi di base.

A questo punto, per ogni altro utente di A, rieseguite gli stessi passi che avete già sperimentato con utente amministrativo: create il backup, trasferitelo, lanciate lo script prepareami.sh di ogni utente e fate le vostre verifiche. Se trovate errori non scoraggiatevi e riprendete sempre da capo. E' un po' noioso ma alla fine la certezza di aver fatto le cose per bene ripagherà della fatica. Il processo tende comunque a convergere in fretta.

Ora siete pronti per il terzo atto.

Atto terzo: replicate il server su una nuova AMI

Lanciate ora una nuova istanza di della AMI aggiornata verso cui volete migrare il vostro server. Chiamiamola C.

Eseguite su A il solito backup dell'utente amministrativo e trasferitelo su C.

Altolà! non pensate lo script possa essere eseguito sulla nuova AMI: la nuova macchina potrebbe avere infatti file di configurazioni diverse che ne rendono impossibile l'esecuzione. Lo script andrà eseguito manualmente passo passo, apportando eventuali modifiche necessarie allo script stesso e salvandone una nuova versione C.

 

Lo stesso processo si applica a tutti gli utenti di A. Questa volta però se qualcosa gira storto ripartite dal terzo atto e non dall'inizio (piccola consolazione). Quando siete certi che tutto funziona, e quindi dopo molti test, siete pronti per l'ultimo passo.

A questo punto a furia di ripetere il processo dovremmo aver maturato una discreta manualità. Bene! Perche nell'ultimo passo la velocità serve.

Per evitare che i dati ci cambino durante il processo di migrazione, dobbiamo isolare la macchina A da Internet prima del trasferimento sino a quando la macchina C non sarà pronta. Se gli script sono pronti, automatici e funzionanti e voi sapete esattamente cosa fare, potrebbe essere questione di pochi minuti. Ovviamente meno i nostri servizi rimangono "spenti" meglio è.

Ecco dunque l'ultimo sforzo:

  1. da management console do da API scollegate l'ip pubblico dalla macchina A (elastic IP).
  2. sospendete eventuali processi batch presento su A
  3. eseguite da zero tutto il processo di migrazione da A su C sin qui descritto.
  4. associate l'ip pubblico a C.
  5. fate gli ultimi test di accettazione

Se qualcosa ancora gira storto basta riconnettere l'ip internet alla vecchia macchina e ricominciare con pazienza il terzo atto. Usando l'elastic IP di Amazon, il rollback è questione di secondi. Se tutto "gira" è arrivato il momento di brindare.

Seguire questo processo in un ambiente tradizionale è molto oneroso e lungo. Usando una infrastruttura di cloud computing, dove eseguire snapshot, installare server, trasferire GB di dati , è questione di secondi, tutto va visto in prospettiva diversa.

Come risultato finale abbiamo ottenuto dei server sempre allineati con la tecnologia più efficace, e un senso di tranquillità che vi farà dormire sereni.

Molti di voi si saranno accorti delle tante leggerezze presenti in questo articolo. Quella più evidente si nasconde dietro frasi del tipo "ora fate le vostre verifiche per vedere se funziona tutto". Il problema del testing è tutt'altro che banale, in un prossimo articolo analizzeremo come e se il cloud computing ci può aiutare anche in questa fase. Per ora vi invito a scaricare e leggere il glossario e il Syllabus di ISTQB: contiene tutto quanto un tester deve sapere, e anche molto di più.
Un'altra leggerezza è legata al tema dell'installazione unattended dei pacchetti software. Esistono intere architetture, molto complesse, che si fanno carico di questo problema, in attesa di approfondire l'argomento vi segnalo alcune risorse utili ad inquadrare il problema: il meccanismo di preseeding di Debian/ubuntu e la piattaforma di Opscode

 

Come promesso, ecco infine il contenuto di uno script che trasforma l'AMI standard di ubuntu 10.4 in un server lamp. Nell'esempio non sono riportati i file di sistema modificati, ma nello script, prima di ogni comando che esegue una patch, trovate l'indicazione di articoli che spiegano come e quali modifiche fare. Attenzione non è codice per cut&paste, usatelo solo come riferimento.

Notate l'utilizzo di debconf-set-selections o della funzione "patch" nei punti in cui è richiesto un input manuale

#!/bin/bash
##################################################################
#AMI ID: ami-16794c62
#RAM Disk ID: -
#Kernel ID: aki-6a794c1e
#Root Device: /dev/sda1
#Block Devices: /dev/sda1=vol-d8c923b1:attached:2010-09-29T12:43:17.000Z:false
#Type: m1.small
##################################################################
# MODDIR contiene una copia dei file di sistema modificati.
##################################################################
MODDIR=/home/ubuntu/rebuild.d/modfiles
##################################################################
# PASSWORD E VARIABILI USATE NELLO SCRIPT:
HOSTMASTER="Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo."
MYSQLROOTPASSWORD="secret1"
PHPMYADMINPASSWORD="secret2"

##################################################################
#Questa funzione sovrascrive un file di sistema dopo aver verificato che tale modifica 
#sia valida rispetto alle condizioni in cui la modifica era originariamente pensata.
#Il primo parametro contiene il pathaname assoluto del file da modificare, senza / iniziale.
#come prerequisito nella directory $MODDIR deve esistere un file con lo stesso path. Deve inoltre esistere 
#un file con estensione .diff che contiene le differenze attese tra file originale e file modificato.
#In caso di errore lo script si ferma.
##################################################################
function patch {
        # Verifica prerequisiti
        if [ !  -e $MODDIR/$1  ] ;then
                echo "Errore interno: $MODDIR/$1 inesistente" ; exit 1
        fi

        if [ -e /$1 -a ! -e $MODDIR/$1.orig ] ; then
                echo "Errore : /$1 esiste ma manca $MODDIR/$1.orig" ; exit 1
        fi

        diff /$1 $MODDIR/$1.orig > /tmp/file.diff
        # il file file.diff deve essere vuoto i.e. nessuna differenza
        if [ -S /tmp/file.diff ] ; then
                echo "Errore: patch non valida (diff inconsistente):"
                cat /tmp/file.diff
                rm -f /tmp/file.diff
                exit 1
        fi
        rm -f /tmp/file.diff

        if [ -e /$1 ] ; then
                mv /$1 /$1.orig
        fi
        cp -a $MODDIR/$1 /$1
}

# update AMI Packages
apt-get -y update
# DO NOT UPGRAGE GRUB. THIS WILL PREVENT AMI BOOT FAILURE AFTER UPGRADE!
echo "grub-pc hold"|dpkg --set-selections
echo "grub-common hold"|dpkg --set-selections
apt-get -y upgrade

#Installa supporto per debconf-set-selections
apt-get install -y debconf-utils

#Installazione apache
apt-get install -y apache2 apache2-mpm-prefork

#Installazione mysql
debconf-set-selections << EOT 
#New password for the MySQL "root" user:
mysql-server-5.1 mysql-server/root_password password $MYSQLROOTPASSWORD
#Repeat password for the MySQL "root" user:
mysql-server-5.1 mysql-server/root_password_again password $MYSQLROOTPASSWORD
EOT
apt-get install -y mysql-server
# Save mysql root password for admin purpose
MYSQLCONFIGFILE="/home/ubuntu/.my.cnf"
cat < $MYSQLCONFIGFILE
[client]
user=root
password=$MYSQLROOTPASSWORD
EOT
chown ubuntu.ubuntu $MYSQLCONFIGFILE
chmod 400 $MYSQLCONFIGFILE

# Installazione php
apt-get install -y libapache2-mod-php5 php5-mysql

#Installazione phpmyadmin (pannello di controllo mysql)
debconf-set-selections  /etc/aliases
update-exim4.conf
/etc/init.d/exim4 restart

#Installazione chkrookkit per la ricerca di rootkit
# vedi http://samiux.wordpress.com/2009/07/02/howto-logwatch-on-ubuntu-9-04-server/
apt-get install -y chkrootkit

#Installazione logwatch per l'analisi giornaliera dei file di log
# PATCH INFO: http://openskill.info/infobox.php?ID=656
apt-get install -y logwatch
patch usr/share/logwatch/default.conf/logwatch.conf

a2enmod  rewrite
/etc/init.d/apache2 restart


##################################################################
# CREAZIONE UTENTI
##################################################################
for i in 1 2 3 4 5 6 7 8 9
do
        adduser  --quiet --disabled-login --disabled-password --gecos u0$i,,, u0$i
        adduser  www-data u0$i
done