|
Quelli che mi conoscono sanno che il mio ambito lavorativo è nei sistemi embedded e nell'Automazione Industriale ma, alle volte, mi metto la casacca di sistemista e cerco di risolvere alcuni problemi propri di questo settore dell'ICT.
In questi giorni mi è capitato di dover gestire dei server per un mio cliente ed in particolare mi son chiesto come potessi risolvere un problema di alta affidabilità (high reliability) per quanto riguarda un file server, in pratica mi son chiesto come si poteva realizzare un file server NFS che fosse a prova di rotture.
Mi son guardato un po' in giro e poi ho trovato quanto segue...
Leggendo questo articolo e spulciando la documentazione di Heartbeat e DRBD ho trovato una soluzione che, seppur non ancora ottimale, presenta un possibile approccio al problema. Tra l'altro mi ha dato anche enormi spunti di riflessione sulle possibili implementazioni in ambito embedded per realizzare sistemi di controllo/monitoraggio ridondanti...
Ma torniamo al nostro problema del server NFS ridondante. Per fare le mie prove mi son avvalso dell'uso di Virtualbox per simulare le macchine fisiche che implementano il mio mini-cluster; infatti ho installato due macchine Debian Lenny chiamandole nfs00 e nfs01 senza supporto grafico di sorta (tanto non serve).
Le due macchine sono state definite in maniera identica disabilitando il controller floppy, il controller audio ed impostando la scheda di rete in modalità Bridged in modo che si possano vadere le macchine anche dal PC ospite.
Riporto la configurazione:
$ VBoxManage showvminfo nfs00 Sun VirtualBox Command Line Management Interface Version 3.1.8 (C) 2005-2010 Sun Microsystems, Inc. All rights reserved.
Name: nfs00 Guest OS: Debian (64 bit) UUID: b0c67414-4dfd-4e12-9cf2-7fb5d93ae2bb Config file: /home/giometti/.VirtualBox/Machines/nfs00/nfs00.xml Hardware UUID: b0c67414-4dfd-4e12-9cf2-7fb5d93ae2bb Memory size: 256MB VRAM size: 14MB Number of CPUs: 1 Synthetic Cpu: off CPUID overrides: None Boot menu mode: message and menu Boot Device (1): Floppy Boot Device (2): DVD Boot Device (3): HardDisk Boot Device (4): Not Assigned ACPI: on IOAPIC: on PAE: on Time offset: 0 ms Hardw. virt.ext: on Hardw. virt.ext exclusive: on Nested Paging: on VT-x VPID: on State: running (since 2010-05-20T08:02:44.145000000) Monitor count: 1 3D Acceleration: off 2D Video Acceleration: off Teleporter Enabled: off Teleporter Port: 0 Teleporter Address: Teleporter Password: Storage Controller Name (0): IDE Controller Storage Controller Type (0): PIIX4 Storage Controller Instance Number (0): 0 Storage Controller Max Port Count (0): 2 Storage Controller Port Count (0): 2 IDE Controller (0, 0): /home/giometti/.VirtualBox/HardDisks/nfs00.vdi (UUID: b28340f8-2ab8-4f82-82b8-62e2cb57e99e) IDE Controller (1, 0): /dev/sr0 (UUID: 00445644-0000-0000-2f64-65762f737230) NIC 1: MAC: 0800277EA371, Attachment: Bridged Interface 'eth0', Cable connected: on, Trace: off (file: none), Type: 82540EM, Reported speed: 0 Mbps NIC 2: disabled NIC 3: disabled NIC 4: disabled NIC 5: disabled NIC 6: disabled NIC 7: disabled NIC 8: disabled UART 1: disabled UART 2: disabled Audio: disabled Clipboard Mode: Bidirectional Video mode: 640x480x0 VRDP: disabled USB: enabled
USB Device Filters:
Available remote USB devices:
Currently Attached USB Devices:
Shared folders:
VRDP Connection: not active Clients so far: 0
Guest:
Statistics update: disabled
Una volta finita l'installazione delle due macchine inizio le operaizoni di configurazione di base. In particolare, su entrambe le macchine, installo openssh-server in modo da poter entrare con un terminale remoto dentro ad ogni sistema:
# aptitude install openssh-server
Poi modifico l'indirizzo IP delle macchine mettendone uno fisso. Nel file /etc/network/interfaces metto quanto segue (esempio valido solo per il primo nodo, per l'altro basta modificare l'IP nella linea address):
nfs00:~# cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
# allow-hotplug eth0
# iface eth0 inet dhcp
auto eth0
iface eth0 inet static
address 10.0.7.210
netmask 255.0.0.0
broadcast 10.255.255.255
network 10.0.0.0
gateway 10.0.0.1
Una volta ripartito il sistema i due nodi avranno indirizzo 10.0.7.210 per nfs00 e 10.0.7.211 per nfs01.
Ora aggiungo nel file /etc/hosts i nomi dei nodi (poiché non ho un DNS disponibile), quindi il contenuto del file diventa:
nfs00:~# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 nfs00.enneenne.com nfs00
10.0.7.210 nfs00
10.0.7.211 nfs01
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
Ora occorre creare i dischi per il raid via TCP/IP su entrambi i nodi usando DRBD. Questo lo si può fare aggiungendo un nuovo disco ad ogni nodo con due partizioni: la prima per i dati (io ho aggiunto 4 GB) e la seconda per i metadata di DRBD (dovrebbe essere di almeno 128MB ma io l'ho fatta di 500MB). Ho quindi aggiunto un nuovo disco da 4.5GB in modo che:
nfs00:~# fdisk -l /dev/hdb
Disk /dev/hdb: 4831 MB, 4831838208 bytes
16 heads, 63 sectors/track, 9362 cylinders
Units = cylinders of 1008 * 512 = 516096 bytes
Disk identifier: 0x00000000
Disk /dev/hdb doesn't contain a valid partition table
Una volta partizionato il disco con hdb1=8GB e hdb2=500MB si ha:
nfs00:~# fdisk -l /dev/hdb
Disk /dev/hdb: 4831 MB, 4831838208 bytes
16 heads, 63 sectors/track, 9362 cylinders
Units = cylinders of 1008 * 512 = 516096 bytes
Disk identifier: 0xa722022c
Device Boot Start End Blocks Id System
/dev/hdb1 1 7751 3906472+ 83 Linux
/dev/hdb2 7752 9362 811944 83 Linux
Verificate che le configurazioni dei nodi siano le stesse.
A questo punto occorre installare il software per gestire la sincronizzazione del tempo id sistema. Operazione fondamentale in un cluster! Su ogni nodo fare:
nfs00:~# aptitude install ntp ntpdate
Ora passiamo ad installare il software per montare il nostro cluster NFS. Sempre su ogni nodo dare:
nfs00:~# aptitude install drbd8-modules-$(uname -r) drbd8-utils heartbeat nfs-kernel-server
Poi forziamo l'arresto dei demoni di heartbeat e DRBD, nel caso non lo fossero già (ma per default non dovrebbero funzionare):
nfs00:~# /etc/init.d/heartbeat stop
nfs00:~# /etc/init.d/drbd stop
Ora, sempre su ogni nodo, disabilitare l'avvio al boot del demone NFS poiché questo è gestito automaticamente da Heartbeat:
nfs00:~# update-rc.d -f nfs-kernel-server remove
nfs00:~# update-rc.d -f nfs-common remove
Poi abilitiamo le direttive di export per l'NFS server nel file /etc/exports:
nfs00:~# cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
/data/export 10.0.7.0/255.255.255.0(rw)
Ora occorre modificare, su ogni nodo, il file di configurazione di DRBD che si chiama /etc/drbd.conf. Io ho salvato il file di default e l'ho rimpiazzato con questo:
nfs00:~# cat /etc/drbd.conf
global {
usage-count no;
}
common {
syncer { rate 10M; }
}
resource r0 {
protocol C;
handlers {
pri-on-incon-degr "echo o > /proc/sysrq-trigger ; halt -f";
pri-lost-after-sb "echo o > /proc/sysrq-trigger ; halt -f";
local-io-error "echo o > /proc/sysrq-trigger ; halt -f";
# outdate-peer "/usr/lib/drbd/outdate-peer.sh on amd 192.168.22.11 192.168.23.11 on alf 192.168.22.12 192.168.23.12";
outdate-peer "/usr/lib/heartbeat/drbd-peer-outdater -t 5";
#pri-lost "echo pri-lost. Have a look at the log files. | mail -s 'DRBD Alert' root";
#split-brain "echo split-brain. drbdadm -- --discard-my-data connect $DRBD_RESOURCE ? | mail -s 'DRBD Alert' root";
}
startup {
# wfc-timeout 0;
degr-wfc-timeout 120; # 2 minutes.
# wait-after-sb;
# become-primary-on both;
}
disk {
on-io-error detach;
# fencing resource-only;
# size 10G;
# no-disk-flushes;
# no-md-flushes;
}
net {
}
syncer {
rate 10M;
al-extents 257;
}
on nfs00 {
device /dev/drbd1;
disk /dev/hdb1;
address 10.0.7.210:7788;
flexible-meta-disk /dev/hdb2;
}
on nfs01 {
device /dev/drbd1;
disk /dev/hdb1;
address 10.0.7.211:7788;
flexible-meta-disk /dev/hdb2;
}
}
In pratica è facile capire che viene definita una risorsa chiamata r0 e che viene mappata sul disco extra che abbiamo aggiunto ad ogni nodo.
Rimando il lettore curioso sul significato di tutti i parametri di configurazione di DRBD alle sue pagine di documentazione.
Ora passiamo ad heartbeat. Per configurarlo devo creare ex-novo i seguenti file su ogni nodo:
nfs00:~# cat /etc/ha.d/ha.cf
logfacility local0
keepalive 1
deadtime 10
bcast eth0
auto_failback on
node nfs00 nfs01
nfs00:~# cat /etc/ha.d/authkeys
auth 3
3 md5 Hello!
nfs00:~# chmod 600 /etc/ha.d/authkeys
nfs01:~# cat /etc/ha.d/haresources
nfs00 IPaddr::10.0.7.220/24
nfs00 drbddisk::r0 Filesystem::/dev/drbd1::/data::ext3 nfs-kernel-server
Note:
- E' importante che il file /etc/ha.d/authkeys sia impostato in sola lettura per root.
- Il file /etc/ha.d/haresources deve essere uguale per entrambi i nodi poiché in questo modo si dice ad hearbeat che il nodo nfs00 è il primario, il che farà sì che quando nfs00 ritornerà attivo dopo un fail nfs01 gli cederà il titolo di primario ritornando secondario.
- L'indirizzo IP del server NFS ridondante è impostato a 10.0.7.220.
Ci siamo! Ora dobbiamo attivare i vari servizi, prima DRBD (occhio ai nomi dei nodi!):
nfs00:~# modprobe drbd
nfs01:~# modprobe drbd
nfs00:~# chgrp haclient /sbin/drbdsetup
nfs01:~# chgrp haclient /sbin/drbdsetup
nfs00:~# chmod o-x /sbin/drbdsetup
nfs01:~# chmod o-x /sbin/drbdsetup
nfs00:~# chmod u+s /sbin/drbdsetup
nfs01:~# chmod u+s /sbin/drbdsetup
nfs00:~# chgrp haclient /sbin/drbdmeta
nfs01:~# chgrp haclient /sbin/drbdmeta
nfs00:~# chmod o-x /sbin/drbdmeta
nfs01:~# chmod o-x /sbin/drbdmeta
nfs00:~# chmod u+s /sbin/drbdmeta
nfs01:~# chmod u+s /sbin/drbdmeta
node00:~# drbdadm create-md r0
Writing meta data...
initialising activity log
NOT initialized bitmap
New drbd meta data block sucessfully created.
nfs01:~# drbdadm create-md r0
Writing meta data...
initialising activity log
NOT initialized bitmap
New drbd meta data block sucessfully created.
Quindi attiviamo il tutto:
nfs00:~# drbdadm up all
nfs00:~# drbdadm up all
Sui vari nodi ottengo:
nfs00:~# cat /proc/drbd
version: 8.0.14 (api:86/proto:86)
GIT-hash: bb447522fc9a87d0069b7e14f0234911ebdab0f7 build by phil@fat-tyre, 2008-11-12 16:40:33
1: cs:Connected st:Secondary/Secondary ds:Inconsistent/Inconsistent C r---
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0
resync: used:0/61 hits:0 misses:0 starving:0 dirty:0 changed:0
act_log: used:0/257 hits:0 misses:0 starving:0 dirty:0 changed:0
nfs01:~# cat /proc/drbd
version: 8.0.14 (api:86/proto:86)
GIT-hash: bb447522fc9a87d0069b7e14f0234911ebdab0f7 build by phil@fat-tyre, 2008-11-12 16:40:33
1: cs:Connected st:Secondary/Secondary ds:Inconsistent/Inconsistent C r---
ns:0 nr:0 dw:0 dr:0 al:0 bm:0 lo:0 pe:0 ua:0 ap:0
resync: used:0/61 hits:0 misses:0 starving:0 dirty:0 changed:0
act_log: used:0/257 hits:0 misses:0 starving:0 dirty:0 changed:0
Poiché non abbiamo ancora attivato il server NFS e non abbiamo ancora definito il nodo primario i server sono ancora in modalità secondaria e sono inconsistenti. Quindi passo a definire il nodo nfs00 come primario:
nfs00:~# drbdsetup /dev/drbd1 primary -o
nfs00:~# mkfs.ext3 /dev/drbd1
nfs00:~# mkdir /data
nfs00:~# mount -t ext3 /dev/drbd1 /data
Poi metto in quest'area condivisa i file di gestione del server NFS in modo che possano essere utilizzati da entrambi i nodi:
nfs00:~# mv /var/lib/nfs/ /data/
nfs00:~# ln -s /data/nfs/ /var/lib/nfs
nfs00:~# mkdir /data/export
nfs00:~# chmod a+rw /data/export/
nfs00:~# umount /data
Quindi ridiamo un'occhiata allo stato del raid:
nfs00:~# cat /proc/drbd
version: 8.0.14 (api:86/proto:86)
GIT-hash: bb447522fc9a87d0069b7e14f0234911ebdab0f7 build by phil@fat-tyre, 2008-11-12 16:40:33
1: cs:SyncSource st:Primary/Secondary ds:UpToDate/Inconsistent C r---
ns:2502240 nr:0 dw:128256 dr:2374026 al:49 bm:144 lo:0 pe:0 ua:0 ap:0
[===========>........] sync'ed: 60.8% (1532440/3906476)K
finish: 0:02:18 speed: 10,996 (10,188) K/sec
resync: used:0/61 hits:148238 misses:149 starving:0 dirty:0 changed:149
act_log: used:0/257 hits:256120 misses:121 starving:0 dirty:72 changed:49
nfs01:~# cat /proc/drbd
version: 8.0.14 (api:86/proto:86)
GIT-hash: bb447522fc9a87d0069b7e14f0234911ebdab0f7 build by phil@fat-tyre, 2008-11-12 16:40:33
1: cs:SyncTarget st:Secondary/Primary ds:Inconsistent/UpToDate C r---
ns:0 nr:2531936 dw:2531936 dr:0 al:0 bm:146 lo:0 pe:0 ua:0 ap:0
[===========>........] sync'ed: 61.7% (1502744/3906476)K
finish: 0:02:23 speed: 10,456 (10,184) K/sec
resync: used:0/61 hits:150092 misses:151 starving:0 dirty:0 changed:151
act_log: used:0/257 hits:0 misses:0 starving:0 dirty:0 changed:0
Perfetto! Ora nfs00 è primario, nfs01 è secondario e si stanno sincornizzando!
Prepariamo ora nfs01 in modo tale che, una volta attivato, possa prendere il controllo come NFS server:
nfs01:~# mkdir /data
nfs01:~# rm -fr /var/lib/nfs
nfs01:~# ln -s /data/nfs /var/lib/nfs
Da notare che questa volta non ho copiato alcunché in /data poiché i dati si trovano nell'area condivisa e vengono generati inizialmente dal nodo primario.
A questo punto, dopo che il raid si è sincronizzato, con un bel reboot su entrambi i nodi dovremmo vedere le cose funzionare.
Sul primario dovremmo vedere un nuovo indirizzo IP (quello del server in cluster e cioè 10.0.7.220) e che il disco /dev/drbd1 è montato su /data:
nfs00:~# ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:7e:a3:71
inet addr:10.0.7.210 Bcast:10.255.255.255 Mask:255.0.0.0
inet6 addr: fe80::a00:27ff:fe7e:a371/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:45839 errors:0 dropped:0 overruns:0 frame:0
TX packets:112057 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:3622143 (3.4 MiB) TX bytes:166144443 (158.4 MiB)
eth0:0 Link encap:Ethernet HWaddr 08:00:27:7e:a3:71
inet addr:10.0.7.220 Bcast:10.0.7.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:58 errors:0 dropped:0 overruns:0 frame:0
TX packets:58 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4544 (4.4 KiB) TX bytes:4544 (4.4 KiB)
nfs00:~# mount
/dev/hda1 on / type ext3 (rw,errors=remount-ro)
tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
procbususb on /proc/bus/usb type usbfs (rw)
udev on /dev type tmpfs (rw,mode=0755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)
/dev/drbd1 on /data type ext3 (rw)
nfsd on /proc/fs/nfsd type nfsd (rw)
Mentre nulla di tutto ciò si osserva sul nodo nfs01!
A questo punto per fare le prove di test utilizzo una terza macchina come client NFS dove farò mount della directory esportata dal mio nuovo cluster:
$ sudo mount 10.0.7.220:/data/export /mnt/
$ ls /mnt/
$ touch /mnt/PROVA
$ ls /mnt/
PROVA
Poi spengo di brutto nfs00 e dopo qualche secondo su nfs01...:
nfs01:~# ifconfig
eth0 Link encap:Ethernet HWaddr 08:00:27:03:79:5b
inet addr:10.0.7.211 Bcast:10.255.255.255 Mask:255.0.0.0
inet6 addr: fe80::a00:27ff:fe03:795b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:20966 errors:0 dropped:0 overruns:0 frame:0
TX packets:7827 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:27163149 (25.9 MiB) TX bytes:863061 (842.8 KiB)
eth0:0 Link encap:Ethernet HWaddr 08:00:27:03:79:5b
inet addr:10.0.7.220 Bcast:10.0.7.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
node01:~# mount
/dev/hda1 on / type ext3 (rw,errors=remount-ro)
tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
procbususb on /proc/bus/usb type usbfs (rw)
udev on /dev type tmpfs (rw,mode=0755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev) devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620) /dev/drbd1 on /data type ext3 (rw) nfsd on /proc/fs/nfsd type nfsd (rw)
nfs01:~# ls /data/ export lost+found nfs nfs01:~# ls /data/export/ PROVA
Sììì, nfs01 ha preso il posto di nfs00 e il client non si è accorto di nulla (tranne il periodo di aggiornamento della tabella ARP)! Infatti sul client ottengo ancora:
$ ls /mnt/
PROVA
Ora provo a scrivere di nuovo in /data e poi riavvio il primario per vedere se i dati sono consistenti:
$ touch /mnt/RIPROVA
$ ls /mnt/
PROVA RIPROVA
Riattivo il primario ed attendo che riprenda il controllo (basta vedere che ora ha lui l'IP del server NFS) e sul client vedo che il file RIPROVA c'è:
$ ls /mnt/
PROVA RIPROVA
Se ora faccio ripartire nfs00 questo riprenderà il controllo dopo che nfs01 è riuscito a smontare la directory /data o a fare reboot. Tutto funziona!
Da notare che il passaggio dal secondario al primario è un pochettino più lento... e devo indagare il perché! Ma io sono solo un povero programmatore, quindi... Suggerimenti? ;)
|