Software RAID 1 w Linuksie

Poniższy artykuł ma na celu przedstawienie - krok po kroku - w jaki sposób skonfigurować i uruchomić prostą macierz dyskową w trybie RAID 1 na działającym już systemie operacyjnym Linux.

Czym jest RAID 1?

Jednym słowem: mirroring. W praktyce oznacza to równoległy zapis tych samych danych na dwóch oddzielnych dyskach. W przypadku awarii jednego z dysków system operacyjny może działać dalej korzystając z drugiego, sprawnego dysku.

Dlaczego akurat Software RAID?

O wyborze programowego rozwiązania decyduje przede wszystkim cena. Nie musimy inwestować pieniędzy w drogi, sprzętowy kontroler macierzy dyskowych. Ponadto większość "tanich i dobrych" kontrolerów często wymaga dodatkowych sterowników, które nie zawsze lubią współpracować z Linuksem. No i też nie jest to rozwiązanie w pełni sprzętowe.

Platforma testowa:

Na potrzeby tego artykułu uruchomiona została macierz składająca się z dwóch dysków 120 GB (Maxtor 6Y120P0), działająca pod kontrolą dystrybucji Slackware Linux (gałąź -current, jądro w wersji 2.6.26.3-grsec). Każdy z dysków podłączony jest do innego kanału ATA, co wpływa na większą wydajność macierzy.

Co do wyboru dysków zazwyczaj zaleca się tworzenie macierzy na dwóch identycznych dyskach, jednak nie ma większych przeszkód przy stworzeniu takiej macierzy na dyskach różnej pojemności. Wpływa to nieco na wydajność z racji różniących się parametrów. W przypadku różnych dysków musimy zadbać o dwie rzeczy:

  1. Dysk, na którym aktualnie działa system operacyjny musi być mniejszy od dysku, na którym będziemy konfigurować macierz.
  2. Na obydwu dyskach musimy zadbać o taką samą adresację (dotyczy także identycznych dysków).
    Metodę adresacji wybiera się w BIOSie. W przypadku jakichkolwiek problemów można też użyć fdiska.
# fdisk -l /dev/hda | grep heads                    
255 heads, 63 sectors/track, 14946 cylinders
# fdisk -l /dev/hdc | grep heads
255 heads, 63 sectors/track, 14946 cylinders

Jest to przykład dla dwóch identycznych dysków. W tym przypadku wszystkie wartości są te same.

# fdisk -l /dev/hda | grep heads
255 heads, 63 sectors/track, 9729 cylinders
# fdisk -l /dev/hdc | grep heads
255 heads, 63 sectors/track, 14946 cylinders

A to przykład użycia dwóch różnych dysków.
Wartości, które muszą być identyczne to liczba głowic (heads) oraz liczba sektorów na ścieżkę (sectors/track). Liczba cylindrów jest różna z powodu różnych pojemności (hda to 80 GB, hdc 120 GB).
Adresacja jest o tyle ważna, gdyż przy tworzeniu partycji programy takie jak fdisk czy cfdisk zaokrąglają rozmiary partycji. W przypadku tej samej adresacji nie ma ryzyka, że np. partycja 20 GB na jednym dysku będzie o 500 kB mniejsza od tej na drugim.

Przejdźmy więc do właściwej konfiguracji. Podstawowa kwestia to odpowiednie opcje wkompilowane w jądro, a więc wybieramy:

Device Drivers  --->
[*] Multiple devices driver support (RAID and LVM)  --->
<*>   RAID support
<*>     RAID-1 (mirroring) mode

Nie będę opisywał samej rekompilacji jądra. Pomijam także zabawę z initrd, dlatego sterownik wkompilujemy w jądro.

Dysk, na którym zainstalowany jest mój system ma taką strukturę:

# fdisk -l /dev/hda

Disk /dev/hda: 122.9 GB, 122942324736 bytes
255 heads, 63 sectors/track, 14946 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1               1         524     4208998+  83  Linux      # /
/dev/hda2             525       14946   115844715    5  Extended
/dev/hda5             525         907     3076416   83  Linux      # /tmp
/dev/hda6             908        1290     3076416   83  Linux      # /var
/dev/hda7            1291        2183     7172991   83  Linux      # /usr
/dev/hda8            2184       14831   101595028+  83  Linux      # /home
/dev/hda9           14832       14946      923706   82  Linux swap

Pierwsze co musimy zrobić to stworzyć na drugim dysku takie same partycje, zmieniając ich typ na Linux raid autodetect. Efekt końcowy wygląda tak:

# fdisk -l /dev/hdc

Disk /dev/hdc: 122.9 GB, 122942324736 bytes
255 heads, 63 sectors/track, 14946 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hdc1               1         524     4208998+  fd  Linux raid autodetect
/dev/hdc2             525       14946   115844715    5  Extended
/dev/hdc5             525         907     3076416   fd  Linux raid autodetect
/dev/hdc6             908        1290     3076416   fd  Linux raid autodetect
/dev/hdc7            1291        2183     7172991   fd  Linux raid autodetect
/dev/hdc8            2184       14831   101595028+  fd  Linux raid autodetect
/dev/hdc9           14832       14946      923706   fd  Linux raid autodetect

Oznaczenia partycji Extended nie zmieniamy. Jest ona informacją o istnieniu dysków logicznych.

Kolejny krok to stworzenie macierzy:

# mdadm -C /dev/md0 -l 1 -n 2 missing /dev/hdc1

Tworzymy w ten sposób macierz /dev/md0 pierwszego poziomu (RAID 1), składającą się z dwóch dysków. Na początku konfigurujemy w niej tylko partycję nowego dysku (hdc1), ponieważ hda1 jest używana przez nas system (oznaczamy ją jako missing).
Czynność powtarzamy dla pozostałych partycji:

# mdadm -C /dev/md1 -l 1 -n 2 missing /dev/hdc5
# mdadm -C /dev/md2 -l 1 -n 2 missing /dev/hdc6
# mdadm -C /dev/md3 -l 1 -n 2 missing /dev/hdc7
# mdadm -C /dev/md4 -l 1 -n 2 missing /dev/hdc8
# mdadm -C /dev/md5 -l 1 -n 2 missing /dev/hdc9

Nowe partycje musimy sformatować (oczywiście wybór systemu plików wedle upodobań, ja używam ext3):

# mkfs.ext3 /dev/md0
# mkfs.ext3 /dev/md1
# mkfs.ext3 /dev/md2
# mkfs.ext3 /dev/md3
# mkfs.ext3 /dev/md4
# mkswap /dev/md5

Tworzymy katalogi, pod które będziemy montować partycje oraz /dev, /proc, /sys:

# mkdir -p /raid/{tmp,var,usr,home,dev,proc,sys}
# chmod 555 /raid/proc

Montujemy je:

# mount /dev/md0 /raid
# mount /dev/md1 /raid/tmp
# mount /dev/md2 /raid/var
# mount /dev/md3 /raid/usr
# mount /dev/md4 /raid/home

Czas na przekopiowanie naszego systemu na nowy dysk. Możemy użyć do tego celu rsync:

# rsync -auH --progress --stats --exclude=/raid --exclude=/dev --exclude=/proc --exclude=/sys / /raid

Na nowym dysku trzeba jeszcze przeprowadzić kilka modyfikacji. Pierwsza to dopisanie macierzy do pliku konfiguracyjnego mdadm. Edytujemy więc /raid/etc/mdadm.conf i dopisujemy do niego linię:

DEVICE /dev/hda[156789] /dev/hdc[156789]

Tajemnicze cyfry w nawiasach to oznaczenia kolejnych dysków (hda1, hda5, itd.). Musimy także umieścić informacje o poszczególnych partycjach macierzy. Dane te wygeneruje nam mdadm:

# mdadm --detail --scan >> /raid/etc/mdadm.conf

Kolejną modyfikacją jest zmiana wpisów w fstab. Edytujemy więc /raid/etc/fstab i zamieniamy oznaczenia starych partycji na nowe (hda1 na md0, hda5 na md1, itd.):

/dev/md5         swap             swap          defaults           0   0
/dev/md0         /                ext3          defaults           1   1
/dev/md1         /tmp             ext3          defaults           1   2
/dev/md2         /var             ext3          defaults           1   2
/dev/md3         /usr             ext3          defaults           1   2
/dev/md4         /home            ext3          defaults,usrquota  1   2

Ostatnia zmiana to LILO. Nowy wpis w /raid/etc/lilo.conf powinien wyglądać następująco:

image = /boot/bzImage-2.6.26.3
  root = /dev/md0
  label = Linux-RAID
  read-only

Można oczywiście zostawić także stary wpis na wypadek jakiegoś niepowodzenia:

image = /boot/bzImage-2.6.26.3
  root = /dev/hda1
  label = Linux-Normal
  read-only

Oczywiście w przypadku GRUB-a zmiany są analogiczne. Instalujemy nowe LILO:

# lilo -C /raid/etc/lilo.conf

Taka konfiguracja pozwoli nam na uruchomienie systemu z obsługą macierzy bez potrzeby zmiany kolejności bootowania dysków w BIOS-ie (LILO zainstaluje się w MBR dysku /dev/hda), a w razie problemów uruchomienie systemu bez RAIDa.

Nadszedł więc czas zrestartowania systemu. Wybieramy oczywiście jądro Linux-RAID. Po uruchomieniu systemu sprawdźmy czy RAID rzeczywiście działa:

# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/root             4.0G  701M  3.1G  19% /
/dev/md/1             2.9G  173M  2.6G   7% /tmp
/dev/md/2             2.9G  297M  2.5G  11% /var
/dev/md/3             6.8G  2.0G  4.5G  32% /usr
/dev/md/4              96G   46G   45G  51% /home
Wygląda w porządku. Jeszcze szybki rzut oka na status naszego RAID-a:
# cat /proc/mdstat 
Personalities : [raid1] 
md0 : active raid1 hdc1[1]
      4208896 blocks [2/1] [_U]

md1 : active raid1 hdc5[1]
      3076352 blocks [2/1] [_U]
      
md2 : active raid1 hdc6[1]
      3076352 blocks [2/1] [_U]
      
md3 : active raid1 hdc7[1]
      7172864 blocks [2/1] [_U]
      
md4 : active raid1 hdc8[1]
      101594944 blocks [2/1] [_U]
      
md5 : active raid1 hdc9[1]
      923584 blocks [2/1] [_U]

Jak widać aktualnie w naszej macierzy działa jeden dysk. U (up) oznacza dysk działający. _ to dysk niepodłączony.
Dodajmy więc pierwszy dysk do macierzy. Tu także musimy zmienić Id partycji na Linux raid autodetect:

# fdisk -l /dev/hda

Disk /dev/hda: 122.9 GB, 122942324736 bytes
255 heads, 63 sectors/track, 14946 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1               1         524     4208998+  fd  Linux raid autodetect
/dev/hda2             525       14946   115844715    5  Extended
/dev/hda5             525         907     3076416   fd  Linux raid autodetect
/dev/hda6             908        1290     3076416   fd  Linux raid autodetect
/dev/hda7            1291        2183     7172991   fd  Linux raid autodetect
/dev/hda8            2184       14831   101595028+  fd  Linux raid autodetect
/dev/hda9           14832       14946      923706   fd  Linux raid autodetect

Ostatni krok to dodanie nowych partycji do naszej macierzy. Wpisujemy więc kolejno:

# mdadm /dev/md0 -a /dev/hda1
# mdadm /dev/md1 -a /dev/hda5
# mdadm /dev/md2 -a /dev/hda6
# mdadm /dev/md3 -a /dev/hda7
# mdadm /dev/md4 -a /dev/hda8
# mdadm /dev/md5 -a /dev/hda9

Po czym możemy obserwować jak RAID automatycznie kopiuje dane na nowy dysk:

# watch -n 0.1 cat /proc/mdstat
Every 0.1s: cat /proc/mdstat                                                Wed Sep  3 18:35:46 2008

Personalities : [raid1]
md0 : active raid1 hda1[0] hdc1[1]
      4208896 blocks [2/2] [UU]

md1 : active raid1 hda5[0] hdc5[1]
      3076352 blocks [2/2] [UU]

md2 : active raid1 hda6[0] hdc6[1]
      3076352 blocks [2/2] [UU]

md3 : active raid1 hda7[0] hdc7[1]
      7172864 blocks [2/1] [_U]
        resync=DELAYED

md4 : active raid1 hda8[0] hdc8[1]
      101594944 blocks [2/1] [_U]
      [====>................]  recovery = 20.2% (20581248/101594944) finish=69.4min speed=19430K/sec

md5 : active raid1 hda9[0] hdc9[0]
      923584 blocks [2/2] [UU]

unused devices: <none>

Proces kopiowania danych może oczywiście zająć kilka godzin, w zależności od rozmiaru dysków.
Po zakończeniu kopiowania warto zainstalować LILO na obydwu dyskach (możemy już skasować stary wpis dotyczący /dev/hda1) oraz ustawić ich bootowanie kolejno po sobie w BIOSie. W razie awarii któregoś z dysków, gdy będziemy restartować system, drugi dysk automatycznie podejmie pracę.

Pozwoliłem sobie także napisać niewielki skrypt CGI, który w przejrzysty sposób wyświetla status naszej macierzy:

--- cut here ---
#!/bin/sh

function print() {
	i=1
	while [ $i -le `grep -o 'md[0-9]*' /proc/mdstat | wc -l` ]; do
		if [ `grep -o '\[..\]' /proc/mdstat | head -$i | tail -1 | head -c$1 | tail -c1` == "U" ]; then
			echo "[  <span style=\"color:#006400\">up</span>  ]"
		else
			echo "[ <span style=\"color:#8b0000\">down</span> ]"
		fi
		((i++))
	done
}

echo -e "Content-type: text/html\n"
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
echo "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">"
echo -e "<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
echo "<title>mdstat - RAID 1 - $HOSTNAME</title>"
echo -e "<style type=\"text/css\">\n<!--"
echo -e "\ttd { text-align:center;padding:0 20px 0 20px }"
echo -e "-->\n</style>\n</head>\n<body>"
if [ ! -e /proc/mdstat ]; then
	echo "<pre>/proc/mdstat doesn't exist...</pre>"
else
	echo "<table><tr>"
	echo -e "<th>device</th>\n<th>#0</th>\n<th>#1</th>\n<th>status #0</th>\n<th>status #1</th></tr><tr>"
	echo -e "<td><pre>\n`grep -o 'md[0-9]*' /proc/mdstat`\n</pre></td>"
	echo -e "<td><pre>\n`grep -o '[a-z0-9]*\[0\]' /proc/mdstat | sed 's/\[0\]//'`\n</pre></td>"
	echo -e "<td><pre>\n`grep -o '[a-z0-9]*\[1\]' /proc/mdstat | sed 's/\[1\]//'`\n</pre></td>"
	echo "<td><pre>"
	if [ `grep -o '\[[0-9]\]' /proc/mdstat | head -c3` == "[0]" ]; then
		print 2
	else
		print 3
	fi
	echo -e "</pre></td>\n<td><pre>"
	if [ `grep -o '\[[0-9]\]' /proc/mdstat | head -c3` == "[1]" ]; then
		print 2
	else
		print 3
	fi
	echo -e "</pre>\n</td>\n</tr></table>"
fi
echo -e "</body>\n</html>"
--- cut here ---

Tym oto akcentem temat Software RAID 1 można uznać za wyczerpany. Wszelkie sugestie i uwagi odnośnie tego artykułu będą mile widziane. W razie pytań proszę o kontakt e-mailowy. Adres e-mail podany jest na mojej stronie.


Copyright 2008 Damian Pasternok.
Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.
Zezwala się na wykonywanie i dystrybucję wiernych kopii tego tekstu, niezależnie od nośnika, pod warunkiem zachowania niniejszego zezwolenia.

Aktualizowane: $Date: 2008/09/03 19:47:21 $ $Author: crh $