Friday, April 22, 2011

How to convert a Linux (Ubuntu, Debian, etc.) system to a two drive RAID mirror (RAID1 or RAID10) using Linux MD

Drives are cheap nowadays, and your data is valuable. Buy a second disk, and convert your system to a RAID1 or RAID10 mirrored drive setup using Linux software RAID (MD). I've done this procedure on Debian Squeeze 6.0 and on Ubuntu Lucid Lynx 10.04, but it should work with slight modifications on any modern Linux distribution like Fedora, Gentoo, Slackware, etc.

If you are in a position to make a fresh installation instead of converting, perhaps you are looking for my guide on making a fresh installation of Linux/GNU on a mirror.

Alignment issues are outside the scope of this document, so if you are planning on mixing disks with 512 and 4096 blocks, my guide won't be of help resolving any performance issues.

Also, please note that recently, I discovered that if you have a GPT disk label (rather than the "usual" msdos disk label), this procedure is somewhat more complicated. I'd have to write a separate guide on how to convert such a system. To find out what your disk label is, run parted on your primary drive, and use the "print" command, e.g. if your primary drive is /dev/sda:

sudo parted /dev/sda

Look for the line that reads "Partition Table:", and if it says gpt rather than msdos, do not proceed. Generally speaking GPT is used on drives of size 2TB and larger.

If your system is using GPT indeed, consider making a fresh installation with my guide on making a fresh installation of Linux/GNU on a mirror.

0) Prerequisites:
- A working Linux installation that you want to convert to use a mirrored system drive.
- Mdadm and Grub v2 installed on your hard drive installation (mdadm and grub2 packages in Debian/Ubuntu) .
- A second drive of the same capacity as your system drive. If you are in a tight spot and have to make do with drives of slightly different capacity, instead of picking the new drive to make the mirror on in step (3), pick the old drive, and skip to step (6). In step (7), make enough space at the end of the drive so that the data would fit on both drives and still have enough left over for the md superblock. Finally, in step (9), make sure you add the "--size" parameter to mdadm to tell it not to use the entire device. Make careful calculations so that your choice of "--size" would work for both drives. Also, keep in mind that in this case, you are working on your main drive and not on a backup.
- If you have two drives of different capacity, see my example at the end of this guide for a rough idea on how to proceed.

1) Boot into a LiveCD that preferably matches your hard drive installation, like Ubuntu 10.04.

2) Turn off swap in case the LiveCD discovered and started using a swap partition on your system drive.

sudo swapoff -a

3) Let's say your system drive is /dev/sda and your new drive is /dev/sdb, and the drive capacities are the same (see step (0)).

4) Make a "backup" copy of your system drive:

sudo dd if=/dev/sda of=/dev/sdb bs=1024k

5) If you are bored, you can monitor the progress of dd by running this command, which will make dd issue progress information:

sudo killall -SIGUSR1 dd

6) When done copying, you need to make a bit of space at the end of the drive for the md superblock, so run gparted on the drive what you wish to start the mirror with. For instance, let's say you've decided to start the mirror with the new drive first, and then add your original drive to the mirror:

sudo gparted /dev/sdb

7) Shrink the last partition on your drive (the right-most one) by anything more than 128 kilobytes. Let's say 8MB to be absolutely safe. If you are using an msdos label, don't forget that if the last partition is a logical partition (i.e. lives within an extended partition), you need to shrink the extended partition that houses it as well. First shrink the logical partition, THEN, the extended partition. To shrink partitions, right click on them and select Resize. To right-click on an extended partition is tricky, as you have to click on a very narrow strip of pixels at the edge of the box that represents your drive. Click apply, and wait for all operations to complete.

8) Make sure md is installed in your LiveCD, and load the md module:

sudo apt-get update
sudo apt-get install mdadm
sudo modprobe md_mod
sudo modprobe raid10

9) Create the array. I decided to make a RAID10 array instead of a RAID1 array, which gives me faster sequential reads, but amounts to the same thing in terms of data replication. RAID10 with two drives and "n2"
layout provides you with two identical drives with normal layout. See "man mdadm" for information on what each option below means.

sudo mdadm --create
--metadata=1.0 /dev/md0 -l10 -pn2 -c512 -n2 /dev/sdb missing
You should see output that reads something like "mdadm: array /dev/md0 started."

If you want a RAID1 mirror, then the line above is simpler:

sudo mdadm --create --metadata=1.0 /dev/md0 -l1 -n2 /dev/sdb missing

10) Depending on what LiveCD you are using, you may or may not already have the partitions on /dev/md0 detected. If you do, you should see devices like /dev/md0p1, etc. If you don't, you can try some of these
ways to make them appear:

sudo mdadm --assemble --scan
sudo udevadm trigger
sudo mdadm-startall

11) Once you see the partitions, mount your root partition (mine was /dev/md0p5):

sudo mount /dev/md0p5 /mnt

Also, if you have a separate /boot, /usr, and /var, mount those too:

sudo mount /dev/md0p1 /mnt/boot
sudo mount /dev/md0p7 /mnt/usr/
sudo mount /dev/md0p8 /mnt/var/

12) Edit your fstab, and change the entries that read either UUID=XXXXXXXXX or /dev/sdaN to /dev/md0pN. In other words, you need to make sure your system will not decide to use the raw devices underlying /dev/md0, but the mirror itself. If your device is indicated by a UUID, then hopefully there is some indication what the device name used to be at installation time in a comment. For example, where the device used to be /dev/sda1, it should now read /dev/md0p1

13) Save your array into /etc/mdadm/mdadm.conf, and rebuild your initrd in order to make sure the array gets assembled at boot time, and under the correct name (e.g. /dev/md0 instead of /dev/md_d0).

sudo chroot /mnt

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

apt-get update
apt-get install mdadm
mkdir -p /etc/mdadm/
mdadm --examine --scan > /etc/mdadm/mdadm.conf
update-initramfs -c -k all

If you get complaints that initrd is custom, you can force update-initramfs totake over the custom initrd with these options instead:

update-initramfs -ut -k all


14) Configure and install grub v2 into the MBR of the mirror. For Ubuntu 10.04, it was trivially easy to do. First we make sure that Grub v2 is installed.

sudo apt-get install grub2 # Just in case the prerequisite was not satisfied.

Next, edit /etc/default/grub, and *uncomment* the line that reads "GRUB_DISABLE_LINUX_UUID=true".

grub-mkconfig > /boot/grub/grub.cfg
grub-install --recheck --modules="raid" /dev/md0

15) Reboot into your disk installation, but make sure you are booting off /dev/sdb, on which the mirror lives now. To do so, you can usually call up a one-time Boot menu with one of the F-keys (like F8) on many
systems. Otherwise, modify the necessary BIOS settings to do so.

Once you're in, you can add /dev/sda to the array that was created in steps 1-16 above.

sudo mdadm --add /dev/md0 /dev/sda

17) You can watch the process of resyncing the drives with:

watch cat /proc/mdstat

Example of two 80GB drives of slightly different capacity, and how I dealt with them:
[ 2.272122] sd 0:0:0:0: [sda] 156301488 512-byte logical blocks: (80.0 GB/74.5 GiB)
[ 2.441572] sd 2:0:0:0: [sdb] 156250000 512-byte logical blocks: (80.0 GB/74.5 GiB)

Using gparted, I shrunk the last partition by 32MB, and using fdisk, determined it ends at block 156232124. Then I used --size=78116352 with mdadm to make the mirror fit on either drive.

The kibibyte value I gave to --size was arrived at thus: 78116352=((156232124+580)*512/1024)

The value 156232124+580=156232704 was arrived as the smallest amount of 512byte blocks that gave me a round value in kibibytes (bytes/1024).