Laboratorio

Modulo 7 - Sistemi operativi di rete

Esperienza su High Availability (HA) con heartbeat, DRBD e mon

Per la realizzazione di questo modulo useremo Netkit4TIC con la connettività con la rete reale (leggere il file README).

In questo modulo sperimenteremo un High availability cluster (HA Cluster) costituito da un two-node cluster. Un cluster HA ha il compito di erogare i servizi limitando al minimo i danni provocati dal blocco di un nodo. Lo scopo infatti è quello di eliminare i punti deboli del sistema chiamati "Single Points of Failures" (SPoF).
Per questa esercitazione ci siamo ispirati alla tesi di laurea "Un Cluster in Alta Disponibilità in Ambiente Linux" di Sabino Calò.

Tra le due tipologie Active-Passive e Active-Active abbiamo scelto la seconda in modo da ottenere maggiori prestazioni in mancanza di guasti a scapito di peggiori prestazioni nel caso opposto. I due nodi li abbiamo chiamati left e right con attivi due servizi indipendenti: su left è attivato il servizio http (apache) e su right è attivato il servizio NFS. In caso di guasto il nodo superstite offrirà entrambe i servizi.

Il motivo guida è quello di evitare la presenza di SPoF e quindi sono stati considerati i seguenti casi:

Gli strumenti per realizzare HA sono implementati da:

Schema

La rete descritta nella mappa (pdf, xml) è costituita principalmente da due nodi collegati in modalità cluster High Availability (HA). Ogni nodo ha due schede di rete configurate in modalità bonding e connesse all'altro nodo. Una terza scheda di rete rende disponibile la connettività dalla quale vengono erogati i servizi. Un terzo e ultimo nodo rappresenta il nodo client generico da dove possono essere provati i servizi o dal quale si amministra il cluster.

Il bonding delle interfacce eth0 ed eth1 permette la tollerabilità ai guasti della comunicazione "interna" tra i due nodi. In particolare scegliendo l'algoritmo di distribuzione di tipo round-robin il carico, in assenza di guasti, viene equamente suddiviso tra le interfacce slave.

La configurazione di DRBD è stata pensata per offrire nei nodi due servizi totalmente indipendenti. Quindi ogni nodo ha la copia dei dati dell'altro nodo.
Il nodo left con apache ha un primo blocco drbd in modalità primary e un secondo blocco drbd in modalità slave. La radice del server apache utilizzerà il primo blocco.
Viceversa il nodo right con NFS ha un primo blocco drbd in modalità slave e un secondo blocco drbd in modalità primary. La radice del server NFS utilizzerà il secondo blocco.
Il primo blocco del nodo left è in corrispondenza con il primo blocco del nodo right e il secondo blocco del nodo left è in corrispondenza con il secondo blocco del nodo right.

Per come viene configurato DRBD ne segue che ogni nodo del cluster avrà delle copie esatte del contenuto degli altri nodi e quindi in presenza di un guasto può dare, con prestazioni degradate, l'intero insieme di servizi erogati in origine dal cluster.

In seguito gli argomenti vengono esposti suddivisi nelle seguenti sezioni:

Prerequisiti

Per lo svolgimento di questa esercitazione serve una macchina con almeno 1 GB di RAM o in alternativa una macchina anche con 256 MB di RAM ma con un partizione temporanea di disco con almeno 581 MB liberi e ovviamente con permessi di scrittura.

Nel secondo caso occorre impostare la variabile d'ambiente:

export BACK_ST=/mnt/hdaX

Preparazione dell'ambiente bonding

Per la configurazione della modalità "channel-bonding" occorre caricare il modulo bonding, specificando al driver di utilizzare tutte le schede slave in modalità round-robin (mode=0) e specificando di attivare la modalità di monitoraggio sulle schede (miimon=100) espressa in millisecondi.
In seguito si configura l'interfaccia bond0 appena definita e poi si associano tutte le schede (slave) che si vogliono far partecipare al bonding (eth0 e eth1):

left# modprobe bonding mode=0 miimon=100 && \
      ifconfig bond0 192.168.0.1 up && \
      ifenslave bond0 eth0 eth1

Il pacchetto ifenslave è un "empty package" che si traduce in ifenslave-2.4 o in ifenslave-2.6 in dipendenza del kernel utilizzato.

Nel caso della configurazione di un nodo reale per il caricamento di un modulo al boot consigliamo l'utilizzo di modconf (eventualmente apt-get install modconf) che permette tramite un comodo menu di selezionare il modulo desiderato e di specificare gli eventuali parametri.

Nota bene che i comandi lsmod|grep bonding visualizzano un "Used by" pari a 0 ma se noi rimoviamo il modulo con modprobe -r bonding viene rilasciato bond0 e con esso tutti i suoi slaves.

Per controllare il corretto funzionamento:

left# cat /proc/net/bonding/bond0 
Ethernet Channel Bonding Driver: v2.6.1 (October 29, 2004)

Bonding Mode: load balancing (round-robin)
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

Slave Interface: eth0
MII Status: up
Link Failure Count: 0

Slave Interface: eth1
MII Status: up
Link Failure Count: 0

Preparazione dell'ambiente DRBD

Per la configurazione di DRBD occorre per prima cosa generare il modulo drbd.o fondamentale per la gestione dal lato kernel. Per il lato user space occorre aver installato drbd0.7-utils.
In seguito passiamo a configurare il file /etc/drbd.conf, uguale per entrambe i nodi, secondo le nostre necessità che sono:

# /etc/drbd.conf
# primo device
resource drbd0 {
  protocol C;
  startup {
    # Wait for connection timeout.
    # Default is 0, which means unlimited. Unit is seconds.
    #
    wfc-timeout  1;
  }
  net {
  }
  syncer {
    rate 800K;
    group 1;
  }
  on left {
    device     /dev/drbd/0;
    disk       /dev/ubd/1;
    address    192.168.0.1:7788;
    meta-disk  internal;
  }
  on right {
    device     /dev/drbd/0;
    disk       /dev/ubd/1;
    address    192.168.0.2:7788;
    meta-disk  internal;
  }
}
######################################################
# secondo device
resource drbd1 {
  protocol C;
  startup {
    # Wait for connection timeout.
    # Default is 0, which means unlimited. Unit is seconds.
    #
    wfc-timeout  1;
  }
  net {
  }
  syncer {
    rate 800K;
    group 2;
  }
  on left {
    device     /dev/drbd/1;
    disk       /dev/ubd/2;
    address    192.168.0.1:7789;
    meta-disk  internal;
  }
  on right {
    device     /dev/drbd/1;
    disk       /dev/ubd/2;
    address    192.168.0.2:7789;
    meta-disk  internal;
  }
}

Nella precedente configurazione abbiamo specificato che i metadata vengono memorizzati all'interno dello stesso blocco. Lo spazio occupato dai metadata è 128MB indipendentemente dalla dimensione del device che può essere al più 4TB.
Questo limite ci costringe quindi a fare delle prove su device di dimensione > 128MB. Moltiplicando 128MB+nfsMB per due (blocchi) e il risultato per due (nodi) deriva l'alto consumo di spazio disco di questa esperienza (circa 700MB).
La definizione del gruppo serve nella fase di risincronizzazione a definire una sequenza di priorità. Nel caso definissimo un unico gruppo allora tutte le risorse associate verrebbero sincronizzare simultaneamente e questo è utile quando le risorse sono su dischi separati.

Per default il numero di risorse definibili sono due e se dovessimo averne in numero superiore occorre passarne il numero al modulo al momento del caricamento. Il nome del parametro è minor_count=#.

Nel nodo left inizializziamo, una tantum, drbd e costruiamo il filesystem XFS nel primo blocco:

left# modprobe drbd && \
      drbdadm up all && \
      drbdadm -- --do-what-I-say primary drbd0 && \
      mkfs.xfs -q /dev/drbd/0

Operiamo le stesse operazioni nel nodo right questa volta sul secondo blocco:

right# modprobe drbd && \
       drbdadm up all && \
       drbdadm -- --do-what-I-say primary drbd1 && \
       mkfs.xfs -q /dev/drbd/1

In ogni nodo occorre dare disposizioni sul montaggio di questi blocchi:

# /etc/fstab
[...]
/dev/drbd/0      /var/www     xfs      noatime,noauto  0 0
/dev/drbd/1      /nfs         xfs      noatime,noauto  0 0

La sequenza di startup dell'esperimento virtuale costruisce prima il nodo left, con l'inizializzazione del bonding e del drbd che non riuscendo a contattare il nodo right forza Primary il blocco 0 e lo formatta xfs in modalità standalone (Wait For Connection [WFConnection]) e marca Secondary il secondo blocco.

left# cat /proc/drbd
version: 0.7.10 (api:77/proto:74)
SVN Revision: 1743 build by doros@picinin.dorogroup.com, 2005-08-01 06:57:56
 0: cs:WFConnection st:Primary/Unknown ld:Consistent
    ns:0 nr:0 dw:5250 dr:151 al:4 bm:2 lo:0 pe:0 ua:0 ap:0
 1: cs:WFConnection st:Secondary/Unknown ld:Inconsistent
    ns:0 nr:0 dw:0 dr:0 al:0 bm:2 lo:0 pe:0 ua:0 ap:0

Alla partenza il nodo right forza Primary il blocco 1 e lo formatta xfs in questo caso però riesce a contattare il nodo left e quindi comanda il mirroring che viene fatto sia dal blocco 1 (right) verso il blocco 1 (left) che dal blocco 0 (left) verso il blocco 0 (right) rimasto pendente da prima.

left# cat /proc/drbd
 version: 0.7.10 (api:77/proto:74)
 SVN Revision: 1743 build by doros@picinin.dorogroup.com, 2005-08-01 06:57:56
  0: cs:SyncSource st:Primary/Secondary ld:Consistent
     ns:1700 nr:0 dw:5250 dr:1851 al:4 bm:2 lo:0 pe:4 ua:0 ap:0
         [=======> sync'ed: 40.0% (15724/17408)K
         finish: 0:00:47 speed: 280 (280) K/sec
  1: cs:SyncTarget st:Secondary/Primary ld:Inconsistent
     ns:0 nr:1200 dw:1200 dr:0 al:0 bm:2 lo:9 pe:20 ua:9 ap:0
         [=======> sync'ed: 40.0% (16336/17408)K
         finish: 0:00:54 speed: 268 (268) K/sec

In seguito terminate le operazioni di allineamento (sync) la situazione su entrambe i nodi sarà stabilizzata:

left# cat /proc/drbd
version: 0.7.10 (api:77/proto:74)
SVN Revision: 1743 build by doros@picinin.dorogroup.com, 2005-08-01 06:57:56
 0: cs:Connected st:Primary/Secondary ld:Consistent
    ns:17424 nr:0 dw:5250 dr:17575 al:4 bm:4 lo:0 pe:0 ua:0 ap:0
 1: cs:Connected st:Secondary/Primary ld:Consistent
    ns:0 nr:19440 dw:19440 dr:0 al:0 bm:4 lo:0 pe:0 ua:0 ap:0


right# cat /proc/drbd
version: 0.7.10 (api:77/proto:74)
SVN Revision: 1743 build by doros@picinin.dorogroup.com, 2005-08-01 06:57:56
 0: cs:Connected st:Secondary/Primary ld:Consistent
    ns:0 nr:17412 dw:17412 dr:0 al:0 bm:4 lo:0 pe:0 ua:0 ap:0
 1: cs:Connected st:Primary/Secondary ld:Consistent
    ns:19440 nr:0 dw:4964 dr:17600 al:4 bm:4 lo:0 pe:0 ua:0 ap:0

Preparazione ambiente pconsole

Pconsole stà per parallel console. Questo pacchetto permette di metterci in contatto simultaneamente con ogni nodo di un cluster utilizzando SSH. Attraverso una shell specializzata si possono impartire comandi che vengono spediti a tutte le connessioni aperte. Può essere usata anche in modalità non grafica. La sua installazione deve essere fatta solo sul nodo da cui si intende operare (nel nostro caso il nodo client). Nei nodi del cluster occorre invece attivare il servizio SSH e per una migliore usabilità abilitiamo l'autorizzazione attraverso chiavi RSA.

Per prima cosa costruiamo una chiave RSA attraverso la quale impostare una connessione ssh basata su "RSA based authentication".

client# ssh-keygen -t dsa -b 1024 -N ''

e aggiungiamo la chiave publica appena generata nei rispettivi file /root/.ssh/authorized_keys2 di ogni nodo del cluster.

Prima di lanciare il comando impostiamo due variabili d'ambiente che servono per impostare il nome del comando per la connessione (ssh), le dimensioni delle windows (geometry):

client# export P_CONNECT_CMD=ssh && \
        export P_TERM_OPTIONS="-geometry 140x10 +sb"

Preparazione ambiente heartbeat

Per configurare Heartbeat occorrono tre file tutti residenti sulla directory /etc/ha.d. Essi sono:

# /etc/ha.d/ha.cf
[...]
# What UDP port to use for communication?
udpport        694

# What interfaces to heartbeat over?
bcast          bond0

#
auto_failback  on

# Tell what machines are in the cluster
node           left right

Il file haresources contiene la lista di risorse che vengono spostate da un nodo all'altro in dipendenza dello stato dei nodi stessi. La prima risorsa consiste dell'indirizzo IP 193.206.185.10, del blocco DRBD numero 0 montato in /var/www con filesystem xfs. Su tale risorsa viene attivato il servizio apache e abilitato il watch cluster-http.
La seconda risorsa consiste dell'indirizzo IP 193.206.185.11, del blocco DRBD numero 1 montato in /mnt/nfs con filesystem xfs. Su tale risorsa vengono attivati i servizi NFS (nfs-common nfs-user-server) e abilitato il watch cluster-nfs:

# /etc/ha.d/haresources
left 193.206.185.10 \
  drbddisk::drbd0 \
  Filesystem::/dev/drbd/0::/var/www::xfs \
  drbdlinks-http apache mon-cluster-http

right 193.206.185.11 \
  drbddisk::drbd1 \
  Filesystem::/dev/drbd/1::/nfs::xfs \
  drbdlinks-nfs nfs-common nfs-user-server mon-cluster-nfs

Gli script mon-cluster-*, che verranno descritti in seguito assieme all'ambiente di mon, sono delle attivazioni di attività di monitoraggio dei relativi servizi http e nfs.

Nel file authkeys viene specificato il metodo di autenticazione tra i nodi del cluster. Nel nostro caso avendo dei canali dedicati (crossover cable) abbiamo scelto di non cifrare la comunicazione:

# /etc/ha.d/authkeys
auth 1
1 crc

Preparazione ambiente mon

La strategia che vogliamo adottare per il monitoraggio di questa configurazione di cluster active-active deve funzionare ovviamente anche nel caso di failover cioè quando un unico nodo eroga tutti i servizi.

Per questo motivo abbiamo pensato di attivare il servizio mon al boot di ogni nodo con tutti gli swatch disattivati attraverso l'uso dello switch -l e utilizzando il file di stato /var/state/mon/disabled inizialmente riempito con l'elenco di tutti gli swatch:

disable watch cluster-nfs
disable watch cluster-http

L'idea è quindi quella di utilizzare heartbeat in congiunzione con moncmd in modo che quando heartbeat attiva un servizio contemporaneamente abilita il corrisponden watch. Quando parte un servizio viene abilitato il watch relativo attraverso il comando moncmd contenuto negli script mon-cluster-*. Ad esempio il file mon-cluster-http ha questo contenuto:

#! /bin/sh
# /etc/ha.d/resource.d/mon-cluster-http

case "$1" in
  start)
    echo -n "Enabling cluster-http"

cat <<EOF | moncmd -l mon -a
PASS=mon

enable watch cluster-http
EOF

    echo "."
    ;;
  stop)
    echo -n "Disabling cluster-http"

cat <<EOF | moncmd -l mon -a
PASS=mon

disable watch cluster-http
EOF

    echo "."
    ;;
  *)
    echo "Usage: $0 {start|stop}"
    exit 1
    ;;
esac

exit 0

Fortunatamente tutto si basa su un solo file di configurazione:

# /etc/mon/mon.cf, configuration file for mon
#
# global options
#
maxprocs    = 20
histlength = 100
randstart = 60s


#
# authentication types:
#   getpwnam      standard Unix passwd, NOT for shadow passwords
#   shadow        Unix shadow passwords (not implemented)
#   userfile      "mon" user file
#
authtype = userfile
userfile = /etc/mon/uf


#
# NB:  hostgroup and watch entries are terminated with a blank line (or
# end of file).  Don't forget the blank lines between them or you lose.
#

#
# group definitions (hostnames or IP addresses)
#
hostgroup cluster-http 193.206.185.10

hostgroup cluster-nfs 193.206.185.11

watch cluster-http
  service http
    description "check http service availability"
    interval 1m
    monitor http.monitor
    period month {Jan-Dec}
      alertafter 2
      alert file.alert /var/www/alert.txt
      alert apacheRestart.alert
  service http-takeover
    description "check http service availability for takeover"
    interval 1m
    monitor http.monitor
    period month {Jan-Dec}
      alertafter 3 10m
      numalerts 1
      alert file.alert /var/www/alert.txt
      alert haStop.alert

watch cluster-nfs
  service nfs
    description "check NFS service availability"
    interval 1m
    monitor rpc.monitor -r mountd -r nfs
    period month {Jan-Dec}
      alertafter 2
      alert file.alert /nfs/alert.txt
      alert nfsRestart.alert
  service nfs-takeover
    description "check NFS service availability for takeover"
    interval 1m
    monitor rpc.monitor -r mountd -r nfs
    period month {Jan-Dec}
      alertafter 3 10m
      numalerts 1
      alert file.alert /nfs/alert.txt
      alert haStop.alert

Diamo una spiegazione dettagliata solo per watch cluster-http. È composto da due service: il service http ha il compito di verificare il funzionamento del server apache e nel caso di fallimento cerca di farlo ripartire. Il service http-takeover ha il compito di verificare il funzionamento del server apache e nel caso di fallimento ripetuto ferma il servizio heartbeat facendo migrare il servizio http nel nodo superstite.

Il terzo e ultimo file di configurazione /etc/mon/auth.cf permette di controllare le autorizzazioni per l'esecuzione di moncmd. Nel nostro caso abbiamo utilizzato il file /etc/mon/uf per memorizzare l'elenco di coppie (user, password) che possono essere autorizzate all'uso di moncmd. Per la costruzione abbiamo inserito il solo utente mon con password mon. L'utility per impostare questi valori è htpasswd.

Il piccolo pacchetto drbdlinks non è ancora entrato in Debian Sarge ma lo abbiamo utilizzato lo stesso. È una piccola serie di utility che serve per costruire allo startup un link simbolico al blocco configurato e per distruggere alla chiusura il medesimo link.

L'aspetto del file di configurazione per nfs:

# /etc/drbdlinks-nfs.conf
mountpoint('/nfs')
link('/var/lib/nfs/')

Preparazione ambiente apache

Per la configurazione del servizio utilizziamo un file /etc/apache/httpd.conf standard. Per il test utilizziamo la pagina speciale index.php:

<php
phpinfo()
?>

Il servizio utilizza il primo blocco drbd, è radicato sul /var/www e assegnato allo statup al nodo left. Il servizio non è gestito dallo startup del nodo ma da heartbeat:

left# update-rc.d -f apache remove
right# update-rc.d -f apache remove

inoltre poichè un successivo eventuale fix di sicurezza rigenera la struttura /etc/rc?.d occorre mettere il pacchetto nello stato "hold":

left# echo apache hold | dpkg --set-selections
right# echo apache hold | dpkg --set-selections

Preparazione ambiente NFS

Per la configurazione del servizio basta impostare il file /etc/exports:

/nfs  193.206.185.0/255.255.255.0(rw,no_root_squash)

Il servizio utilizza il secondo blocco drbd, è radicato in /nfs e assegnato allo statup al nodo right. Il servizio non è gestito dallo startup del nodo ma da heartbeat:

left# update-rc.d -f nfs-common remove && \
      update-rc.d -f nfs-user-server remove
right# update-rc.d -f nfs-common remove && \
       update-rc.d -f nfs-user-server remove

inoltre poichè un successivo eventuale fix di sicurezza rigenera la struttura /etc/rc?.d occorre mettere il pacchetto nello stato "hold":

left# echo nfs-common hold | dpkg --set-selections; \
      echo nfs-user-server hold | dpkg --set-selections
right# echo nfs-common hold | dpkg --set-selections; \
       echo nfs-user-server hold | dpkg --set-selections

Preparazione ambiente NFS lato client

Dal momento che vogliamo testare il funzionamento del servizio NFS occorre configurare opportunamente il nodo client per poter montare il filesystem NFS. Per prima cosa occorre aggiungere un mount point, ad esempio /mnt/nfs e aggiungere una riga nel file /etc/fstab che indichi che l'IP 193.206.185.11 (quello fluttuante) esporta /nfs:

# /etc/fstab
193.206.185.11:/nfs      /mnt/nfs    nfs     noauto          0 0

Quindi per montare la partizione NFS (attualmente i moduli NFS del kernel non sono "a bordo" della vm e quindi li forniamo separatamente nel file modules.tgz e li precarichiamo) diamo il comando:

client# modprobe nfs lockd sunrpc

Sperimentazione interattiva

Per prima vogliamo mostrare le potenzialità di pconsole. Quindi abilitiamo il nodo client alla visualizzazione di applicazioni X sul desktop reale:

realHost$ xhost +192.168.77.2

inoltre occorre istruire i client grafici all'interno del nodo client di dirigere l'output verso il desktop reale:

client# export DISPLAY=192.168.77.1:0.0

e ora proviamo pconsole in connessione SSH simultanea con i due nodi del cluster:

client# /usr/share/doc/pconsole/examples/pconsole.sh left right

Nello (screenshot) possiamo vedere pconsole in azione utilizzata per visualizzare lo stato di drbd e lo stato dei watch. In seguito possiamo completare la sequenza di boot impartendo in pconsole il comando di start per heartbeat e controllando in seguito il file di log:

pconsole > /etc/init.d/heartbeat start
pconsole > tail -f /var/log/ha-log

(screenshot). Ora possiamo sperimentare la migrazione dei servizi. Andiamo ad esempio sul nodo right e impartiamo il comando takeover che implica la migrazione del servizio http dal nodo left al nodo right:

right# /usr/lib/heartbeat/hb_takeover

(screenshot) e la visualizzazione di drbd e dei watch disabilitati (screenshot).

Per testare il servizio NFS diamo i comandi:

client# mount /mnt/nfs && \
        df /mnt/nfs
Filesystem           1K-blocks      Used Available Use% Mounted on
193.206.185.11:/nfs      11584        32     11552   1% /mnt/nfs
client# arp -n 193.206.185.11
Address                  HWtype  HWaddress           Flags Mask            Iface
193.206.185.11           ether   FE:FD:C1:CE:B9:02   C                     eth1

e dopo aver azionato il takeover sul nodo left rileviamo il cambio di MAC address:

client# df /mnt/nfs
Filesystem           1K-blocks      Used Available Use% Mounted on
193.206.185.11:/nfs      11584        32     11552   1% /mnt/nfs
client# arp -n 193.206.185.11
Address                  HWtype  HWaddress           Flags Mask            Iface
193.206.185.11           ether   FE:FD:C1:CE:B9:01   C                     eth1

Per testare il servizio apache diamo il comando:

realHost$ mozilla-firefox http://193.206.185.10/index.php

riassunto nello (screenshot). In seguito al takeover sul nodo right il refresh sul browser riassunto nello (screenshot) differisce dal precedente per il nome dell'host.

Riferimenti ad altri sistemi di virtualizzazione

Creative Commons License FREE THE MOUSE Valid HTML! Sandro Doro (email me)
Ultima modifica: $Date: 2008-01-04 16:43:31 $