Software-RAID mit USB-Sticks (Robert Schulze <rob at rob-schulze.de> 2007-02-06)

Beim Philosophieren bei den Abenden der BraLUG-Treffen ist ab und zu mal die Idee gekommen, daß man doch mal unter Linux ein Software-RAID auf USB-Sticks bauen könnte.

Als feststand, daß wir am 4.3.2007 zu den Chemnitzer Linuxtagen eingeladen sind, wurde an einigen überraschungen gearbeitet. Da dachte ich mir: was solls, probier ich es mal aus.

Meine persönliche Herausforderung dabei war, daß das RAID-System ohne jegliche Kommandoakrobatik, also automagisch, funktionieren sollte.

Dieses Dokument beschreibt die Architektur und alle nötigen Schritte.
Warscheinlich kann man es auch viel einfacher machen, dies alles hat den Status "it works for me" ;-)

Das Skript funktioniert nur für RAID-1, da hier schon 1 Stick für einen funktionsfähigen Verbund ausreicht, und nicht besonders unterschieden werden muß, ob und welche/wieviele Geräte schon im Verbund vorhanden sind.

Was braucht man?

Hardware

Ich habe 4 Kingston Datatraveller mit jeweils 512 MB Größe eingekauft. Dazu gesellt sich ein 4-Port-USB-Hub.

Software

Um das ganze so zu machen, wie ich, braucht man:

Vorbereitungen

USB-Sticks eindeutig identifizieren

Man sollte erstmal auf jeden USB-Stick eine Nummer raufschreiben - das erhöht den Demonstrationsfaktor ;-)
Dann sollte man die ganzen USB-Sticks anschließen - jeden!
Bei einer neueren Distibution poppen dann lauter Fenster auf (da die Datenträger auch automatisch eingebunden werden). OK - es sollte meist so sein, daß die Sticks als Geräte /dev/sd[abcd]1 im System eingebunden sind. Wer schon sonstwelche anderen USB-Speichergeräte im System hat, sollte diese am besten vorher entfernen, um diese Anleitung leicht nachzuvollziehen!

Nun geht es ans identifizieren. Mittels udevinfo kann man sich tolle Informationen von allen Devices holen, wir nutzen dies nun, um alle eindeutigen Seriennummern der USB-Sticks zu bestimmen.

Folgend der Aufruf für die Seriennummer vom 1. USB-Stick (/dev/sda):

$ udevinfo -a -p `udevinfo -q path -n /dev/sda1` | grep serial
spuckt bei mir folgendes aus:

SYSFS{serial}=="5B6B1D8E5ECD" SYSFS{serial}=="0001:10:1b.2"
Uns intereßiert der Wert mit dem höchsten Informationsgehalt, also der erste!
Folglich wird eine kleine Tabelle gemalt:

DeviceSerial
sda15B6B1D8E5ECD
sdb15B6B1B916C31
sdc15B6B1B916C27
sdd15B6B1D9063E8

Super, jetzt ist schonmal das Schwerste geschafft! *hüstel*

Ändern der Partitionstypen

Es ist durchaus möglich, wie oben beschrieben, daß die Geräte sofort beim Reinschieben erkannt und gemountet werden.
Daher erstmal alle Geräte sd* unmounten:

$ sudo umount /dev/sd*1
Nun kann man - damit dieses automatische Mounten nicht mehr vorkommt, den Partitionstyp der Geräte ändern.
Bei RAID-Verbunden setzt man ihn auf 0xFD, sodaß der Kernel den Verbund automatisch beim Booten aufbaut.

In diesem Fall ist der einzige Grund, den Partitionstyp zu ändern, daß die Geräte eben nicht automatisch gemountet werden! Der Kernel wird dieses USB-Raid niemals selber aktivieren, da die Treiber in der falschen Reihenfolge geladen werden und außerdem wollen wir die Kontrolle über das RAID!

Das ändern der Partitionstypen geht wunderbar mit cfdisk, wie das exemplarisch am Gerät /dev/sda (dem ersten USB-Stick) geht, beschreibe ich hier. Dieser Vorgang muß für jeden USB-Stick wiederholt werden, dazu einfach /dev/sda beim Aufruf von cfdisk durch /dev/sdb, /dev/sdc, usw. ersetzen.

Anlegen des RAID-Verbunds


$ sudo mdadm --create /dev/md0 --raid-devices=4 --level=raid1 --auto=yes /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1
legt einen RAID-1 Verbund unter /dev/md0 an, der aus den Partitionen /dev/s[abcd]1 besteht.
Den Status (und das generelle Befinden des RAIDS) kann man mittels

$ cat /proc/mdstat
ansehen.
Wenn man das tut, wird man sehen, daß es eine Weile braucht, bis das RAID fertig eingerichtet ist:

Personalities : [raid1] md0 : active raid1 sdd1[3] sdc1[2] sdb1[1] sda1[0] 503680 blocks [4/4] [UUUU] [>....................] resync = 4.2% (21632/503680) finish=1.8min speed=4326K/sec unused devices:
Wenn dort sowas steht:

Personalities : [raid1] md0 : active raid1 sdd1[1] sdc1[3] sdb1[0] sda1[2] 503680 blocks [4/4] [UUUU] unused devices:
... dann ist das RAID bereit!
Um später keine Verwirrung zu stiften, sollte das RAID nun erstmal wieder deaktiviert werden:

$ sudo mdadm -S /dev/md0

Eine Prise Automagisches

udev ist dafür da, um bei allen möglichen Hardwareänderungen alles mögliche zu machen (das kann man wörtlich nehmen!). Um die Regeln zu verstehen, empfehle ich http://www.reactivated.net/writing_udev_rules.html!

Ein wichtiger Hinweis: man muß sich genau ansehen, welche Ereigniße wann auftreten, welche Variablen dabei verfügbar sind und wann es sinnvoll ist, in die Ereigniskette einzugreifen.

Ergo ist es sinnlos, eine RAID-Komponente hinzufügen zu wollen, bevor noch nicht mal beim Kernel angekommen ist, daß ein neues disk-device da ist, insbesondere sollte der Kernel über eine Partition bescheid wißen (letztes udev-Ereignis beim Einführen des Geräts). Im umgekehrten Fall, beim Entfernen des USB-Sticks, muß das Entfernen der Partition aus dem Verbund so schnell wie möglich paßieren, noch bevor das Entfernen des disk-device vom Kernel bemerkt wurde, da sonst das Device unter /dev nicht mehr verfügbar ist und somit nicht entfernt werden kann!

Hier nun die udev-Regeln, die man unter der Datei /etc/udev/rules.d/10-local.rules ablegen sollte.
Es gilt zu beachten, daß die Einträge hinter SYSFS{serial} durch die oben bestimmten Seriennummern zu ersetzen sind!

ACTION=="add", BUS=="usb", SUBSYSTEM=="block", DEVPATH=="*[0-9]" SYSFS{serial}=="5B6B1D8E5ECD", RUN+="/lib/udev/usb_raid add" ACTION=="add", BUS=="usb", SUBSYSTEM=="block", DEVPATH=="*[0-9]" SYSFS{serial}=="5B6B1B916C31", RUN+="/lib/udev/usb_raid add" ACTION=="add", BUS=="usb", SUBSYSTEM=="block", DEVPATH=="*[0-9]" SYSFS{serial}=="5B6B1B916C27", RUN+="/lib/udev/usb_raid add" ACTION=="add", BUS=="usb", SUBSYSTEM=="block", DEVPATH=="*[0-9]" SYSFS{serial}=="5B6B1D9063E8", RUN+="/lib/udev/usb_raid add" ACTION=="remove", SUBSYSTEM=="scsi_generic", RUN+="/lib/udev/usb_raid remove"
Diese SYSFS{serial}-Einträge überprüfen, ob die Seriennummer des Geräts zu den gewünschten RAID-Komponenten paßt. Somit kann man also konkret eingrenzen, ob der jeweilige Datenträger in das RAID eingebunden werden soll oder nicht.

Mittels "RUN" wird ein Skript ausgeführt, das die eigentliche Arbeit macht.
Dieses sollte unter /lib/udev/usb_raid abgelegt werden und für root:root ausführbar gemacht werden.

Nun wird sich jeder fragen, warum dafür so ein aufgeblasenes Skript braucht.
Das Problem: das erste Ereignis beim Entfernen bringt folgende Variablen mit sich:

SUBSYSTEM=scsi_generic UDEV_LOG=3 DEVNAME=/dev/sg0 MAJOR=21 UDEVD_EVENT=1 DEVPATH=/claß/scsi_generic/sg0 SEQNUM=2335 PHYSDEVDRIVER=sd ACTION=remove MINOR=0 PHYSDEVPATH=/devices/pci0001:10/0001:10:1b.2/usb1/1-1/1-1.4/1-1.4:1.0/host2/target2:0:0/2:0:0:0 PHYSDEVBUS=scsi
Wo steht da ein Gerätename, wie /dev/sda? Nirgends.
Der Gerätename (Schlüßel DEVNAME) steht aber auf jeden Fall beim Einführen des Geräts zur Verfügung, hier alle Environmentvariablen nach dem Einführen:

ID_FS_LABEL_SAFE= ID_BUS=usb ID_SERIAL=Kingston_DataTraveler_2.0_5B6B1D8E5ECD SUBSYSTEM=block ID_FS_VERSION=16777216.0 ID_FS_USAGE=filesystem DEVLINKS=/dev/disk/by-id/usb-Kingston_DataTraveler_2.0_5B6B1D8E5ECD-part1 [...] ID_VENDOR=Kingston DEVPATH=/block/sda/sda1 PHYSDEVDRIVER=sd ID_FS_TYPE=ext2 UDEV_LOG=3 DEVNAME=/dev/sda1 MAJOR=8 ID_FS_LABEL= UDEVD_EVENT=1 SEQNUM=2332 ID_REVISION=PMAP ID_PATH=pci-0001:10:1b.2-usb-0:1.4:1.0-scsi-0:0:0:0 ID_MODEL=DataTraveler_2.0 ACTION=add ID_FS_UUID=41917f84-0b62-4c17-93fd-dd3f57071820 MINOR=1 ID_TYPE=disk PHYSDEVPATH=/devices/pci0001:10/0001:10:1b.2/usb1/1-1/1-1.4/1-1.4:1.0/host2/target2:0:0/2:0:0:0 PHYSDEVBUS=scsi
Somit braucht man nur noch einen Schlüßel, der beim Einführen und Entfernen des Geräts zur Verfügung steht, und dieser ist hier: PHYSDEVPATH.

*klingeling*

Na da steht dann nichts näher, als eine Tabelle über PHYSDEVPATH und den zugehörigen /dev-Einträgen (DEVPATH) zu pflegen. Beim Einführen wird ein Eintrag "PHYSDEVPATH DEVPATH" in die Datei /tmp/usb_raid_tab eingefügt - beim Auswerfen wird DEVPATH mittels PHYSDEVPATH aufgelöst und mdadm mitgeteilt, welche RAID-Komponente aus dem Verbund zu entfernen ist.

Ausprobieren

Um die ganzen Meldungen mitzubekommen, öffnet man /var/log/meßages:

$ tail -f /var/log/meßages
Dann einfach USB-Stick reinstecken und zukieken.
Sobald man den zweiten Stick einführt, werden die Sticks wie wild anfangen zu blinken, das RAID wird nun synchronisiert.