Современные жесткие диски - чудо инженерии и техники. Пластины из магниево-алюминиевого сплава несутся со скоростью 120 оборотов в секунду, при этом считывающую голову от поверхности диска отделяет расстояние в 1/10 толщины человеческого волоса. Сама голова перемещается в любую точку всего за 2мс, что вызывает боковую перегрузку в фантастические 550G. Современные диски обладают впечатляющим объемом, большой линейной скоростью (что на чтение, что на запись) и минимальной задержкой поиска. И платят они за это (кроме цены) серьезным падением надежности. Старенькие Seagate Barracude и Quantum Fireball 6-10 гигабайт объемом без каких-либо нареканий жили десятилетиями, еще более старинные IBM на сотни мегабайт можно было безопасно вскрыть и закрыть в домашних условиях без специальной подготовки. Современные модели крайне чувствительны к вибрациям, тряске, углу наклона, удару и даже шуму (sic!). Средний срок жизни современного террабайтника - три года, если он эксплуатируется не слишком активно. Диски для серверов и NAS живут год-два. Если раньше RAID считался дорогой экзотикой серверного мира (по типу SCSI), то сейчас программный soft-raid - абсолютная необходимость, без которого сервер просто нельзя устанавливать.

В данной статье я расскажу, как мигрировать Debian Linux (в статье используется 7 версия, но с 6 тоже проблем не будет) с одного диска на зеркало из двух. В принципе, методика миграции универсальна, и Ubuntu или CentOS можно мигрировать точно так же. В качестве RAID будем использовать его программную реализацию (mdraid), что позволит сэкономить на железе. В общем случае использование mdraid оправдано, если у вас не очень много дисков (очень много - это больше 10), не используется сложный raid-алгоритм (RAID5/RAID6 и их потомки) и нет большой нагрузки на IOT. Если top показывает, что IO wait ушел куда-то к 50% - время подумать о покупке аппаратного решения.

В моем примере используется стандартная разбивка от Неметт:

/dev/sda5        473M  148M  302M  33% /
/dev/sda1        472M   30M  418M   7% /boot
/dev/sda6        949M   33M  917M   4% /tmp
/dev/sda7        5.5G  829M  4.5G  16% /usr
/dev/sda8        448G  674M  425G   1% /var

Внимание! FreeBSD мигрируется по иной схеме, так как во FreeBSD нет mdraid, вместо него используется GEOM. Не пытайтесь адаптировать данную статью для FreeBSD! Если вам нужна статья по миграции FreeBSD - напишите в комментариях, я напишу такую инструкцию отдельно

Подготовка и миграция данных

В данной статье я предполагаю, что у нас два одинаковых диска, /dev/sda - это реальный, а /dev/sdb - свеже установленный. Все действия производятся от root.

Для начала нам потребуется немного ПО:

apt-get install mdmadm rsync screen

screen в данной ситуации ставится, чтобы избежать проблем, если во время копирования данных поломается сеть. Если копируете локально (с консоли) - он вам не нужен

проверим геометрию:

#sfdisk -d /dev/sda
Warning: extended partition does not start at a cylinder boundary.
DOS and Linux will interpret the contents differently.
# partition table of /dev/sda
unit: sectors

/dev/sda1 : start=     2048, size=   997376, Id=83, bootable
/dev/sda2 : start=   999424, size=  1000000, Id=83
/dev/sda3 : start=  2000894, size=974770178, Id= 5
/dev/sda4 : start=        0, size=        0, Id= 0
/dev/sda5 : start=  2000896, size=  7811072, Id=82
/dev/sda6 : start=  9814016, size=  1951744, Id=83
/dev/sda7 : start= 11767808, size= 11716608, Id=83
/dev/sda8 : start= 23486464, size=953284608, Id=83

Warning можно игнорировать. На данном этапе рекомендую проверить, что на диске-получателе геометрии нет. Если там есть данные, то при копировании геометрии они умрут, и шансов восстановить их без скальпеля не будет:

sfdisk -d /dev/sdb
Warning: extended partition does not start at a cylinder boundary.
DOS and Linux will interpret the contents differently.
# partition table of /dev/sda
unit: sectors

Копируем геометрию:

sfdisk -d /dev/sda | sfdisk /dev/sdb

Лирическое отступление. Если у вас большие диски и используется GPT-таблица - sfdisk вам не поможет, потребуется установить программу gdisk и переносить уже ей:

#переносим раздел
sgdisk -R /dev/sdb /dev/sda
#рандомизируем GUID
sgdisk -G /dev/sdb

Теперь у нас есть два идентичных по разбивке диска. Соберем из второго диска RAID. В нашей ситуации это зеркало (raid-1), по этому RAID будет работать, даже если в массиве остался один диск (так как по идеологии RAID-1 диски совершенно равноправны). Мы создадим по одному raid на каждый раздел диска, включая своп (это гарантирует, что система нормально запустится, если любой диск выйдет из строя). Так как в нормальном случае при сборке зеркала надо указывать два диска, вместо отсутствующего мы будем использовать ключевое слово missing

Важное примечание. GRUB первой версии не может загружаться с новой версии mdraid-массива. Специально для boot-раздела нужно указать старую версию дискового массива (ключом –metadata=0.90). В моем примере это md0:

mdadm --create /dev/md0 --level 1 --raid-devices=2 missing /dev/sdb1 --metadata=0.90
mdadm --create /dev/md1 --level 1 --raid-devices=2 missing /dev/sdb2
mdadm --create /dev/md2 --level 1 --raid-devices=2 missing /dev/sdb5
mdadm --create /dev/md3 --level 1 --raid-devices=2 missing /dev/sdb6
mdadm --create /dev/md4 --level 1 --raid-devices=2 missing /dev/sdb7
mdadm --create /dev/md4 --level 1 --raid-devices=2 missing /dev/sdb8

Массив собрался, проверим:

cat /proc/mdstat

Personalities : [raid1]
md5 : active raid1 sdb8[1]
      476511040 blocks super 1.2 [2/1] [_U]

md4 : active raid1 sdb7[1]
      5854144 blocks super 1.2 [2/1] [_U]

md3 : active raid1 sdb6[1]
      975296 blocks super 1.2 [2/1] [_U]

md2 : active raid1 sdb5[1]
      3903424 blocks super 1.2 [2/1] [_U]

md1 : active raid1 sdb2[1]
      499712 blocks super 1.2 [2/1] [_U]

md0 : active raid1 sdb1[1]
      499712 blocks [2/1] [_U]

Выглядит вроде норм (обратите внимание на md0!), можно форматировать наши новые разделы. Я очень рекомендую использовать метки (label) при форматировании - это здорово облегчает жизнь при проблемах. Метка выставляется ключем -L

#так, как у нас GRUB1, который не умеет работать с ext4 - загрузочный раздел поставим на ext3
mkfs.ext3 /dev/md0 -L /boot
[...]
mkfs.ext4 /dev/md1 -L /
[...]
mkswap /dev/md2 -L swap
[...]
mkfs.xfs /dev/md3 -L /tmp
[...]
mkfs.ext4 /dev/md4 -L /usr
[...]
mkfs.ext4 /dev/md5 -L /var

После того, как все отформатируется, можно монтировать:

mount /dev/md1 /mount

#создадим каталоги для виртуальных систем, которые создаются при запуске ОС
mkdir /mount/dev mount/proc /mount/sys
#теперь создадим пути для монтирования наших разделов
mkdir /mount/boot /mount/tmp /mount/usr /mount/var

# и смонтируем необходимые для миграции каталоги
mount /dev/md0 /mount/boot
mount /dev/md4 /mount/usr
mount /dev/md5 /mount/var

Теперь у нас есть две файловых системы: реальная, с которой сервер сейчас загружен и “пустая”, структурно повторяющая реальную. И во вторую можно копировать данные из первой. Процедура длительная, крайне рекомендую проводить ее через screen. Перед началом рекомендуется остановить все сервисы, которые активно пишут в жесткий диск (например, mysql) - иначе есть риск получить неконсистентность данных:

#переключаемся в screen
screen -dR

#начинаем копировать. Нужно указать в качестве исключений при копирвании служебные
#файловые системы (proc, sys, dev, selinux, если он есть)
#и служебные каталоги самих файлсистем. в ext3/ext4 таким является lost+found,
#это учет inode-сирот и копировать его бессмысленно
#и, разумеется, надо исключить каталог, в который мы копируем - иначе будет петля

rsync -rahzP --exclude=/dev --exclude=/proc --exclude=/sys \
--exclude=/lost+found --exclude=/boot/lost+found --exclude=/var/lost+found --exclude=/usr/lost+found \
--exclude=/mount / /mount/

Переключение на новый диск

Теперь нам нужно установить загрузчик на второй диск. Прелесть загрузчика в том, что он живет в загрузочной области, которая не отображается на файловую систему, а значит, копировать его “в лоб” - бессмысленно. Загрузчик ставится на диск, а не на раздел. Если у вас 10 дисков - ставить надо на все, которые в теории должны быть загрузочными

grub-install /dev/sdb

После переноса данных начинается самое сложное - правка путей. В старых версиях linux адресация дисков была относительной, master в канале ide0 назывался hda, slave ide 1 - hde (hdd был зарезервирован). Это вызывало интересные коллизии в SCSI и SATA, где адресация внутри шины не абсолютна, и после перезагрузки диски могли легко поменяется местами - просто потому, что кто-то очнулся раньше. Поэтому в новых версиях введен механизм block uuid, у каждого блочного устройства есть абсолютно уникальный идентификатор, и обращения к дискам идут строго по нему.

Нам нужно получить список всех блочных идентификаторов, их выдает команда blkid:

blkid

/dev/sda5: UUID="b7b1edbd-308d-489f-8289-43126f1a9b70" TYPE="swap"
/dev/sda1: UUID="1b2e76b3-ffc9-4846-a210-02ed19373976" TYPE="ext3" SEC_TYPE="ext2" LABEL="/boot"
/dev/sda2: UUID="07ca4a9e-4262-44d4-a8e4-31ae37306003" TYPE="ext4"
/dev/sda6: UUID="b7ab9817-7e56-455c-99f6-9c0d9a8e23d6" TYPE="xfs"
/dev/sda7: UUID="8e6777bf-a565-4983-9851-ff358a1d0da3" TYPE="ext4"
/dev/sda8: UUID="591e3e16-db6d-4caa-87d3-f0d3b8848b98" TYPE="ext4"
/dev/sdb1: UUID="a96db57d-5724-63e6-769a-12bac1c5caa9" TYPE="linux_raid_member"
/dev/sdb2: UUID="0b338f2d-c26b-d8c0-f8c4-5384672d5ebb" UUID_SUB="f459ad18-3892-50ff-b2f0-d9cec2490d65" LABEL="ffs:1" TYPE="linux_raid_member"
/dev/sdb5: UUID="5438f240-b120-69f1-0eb9-fd89f7deb762" UUID_SUB="ff19fb3c-c5ce-ad49-2825-28cb490c98d9" LABEL="ffs:2" TYPE="linux_raid_member"
/dev/sdb6: UUID="a769e010-9f0e-0861-c1fc-d120e436004a" UUID_SUB="27db9864-f01a-5dac-9e26-ff75a8e5ccb9" LABEL="ffs:3" TYPE="linux_raid_member"
/dev/sdb7: UUID="40aef537-e1b0-de63-8412-2c0d49e8a73c" UUID_SUB="3bb89ea7-6b8b-7952-98d8-1fb080c548f4" LABEL="ffs:4" TYPE="linux_raid_member"
/dev/sdb8: UUID="d1e75e0e-5434-b700-dd83-a571d7b5ae3b" UUID_SUB="f7602eb7-2169-8fed-e8fb-bc8feb04b431" LABEL="ffs:5" TYPE="linux_raid_member"
/dev/md0: LABEL="/boot" UUID="9f2ec8da-9f6c-417c-81c2-39fd2e1b8dd9" SEC_TYPE="ext2" TYPE="ext3"
/dev/md1: LABEL="/" UUID="9441c57c-c88f-4476-8e8d-b9a281292170" TYPE="ext4"
/dev/md2: LABEL="swap" UUID="bb4a83e7-1e21-434c-9d27-2b53a9431bdb" TYPE="swap"
/dev/md3: LABEL="/tmp" UUID="5f5b2216-3766-4b00-a8a7-0bd1330c8393" TYPE="xfs"
/dev/md4: LABEL="/usr" UUID="f9d85aee-3ef2-409d-8406-351e0dfd81ab" TYPE="ext4"
/dev/md5: LABEL="/var" UUID="7a36dd15-2a40-475e-b3e1-257569ca2958" TYPE="ext4"

Много-много страшного текста (а я говорил, что label - очень помогает!). Теперь нам нужно поменять одни UID на другие. Менять можно в любом текстовом редакторе. Главное их не перепутать. Я рекомендую скопировать UID-ы куда-нибудь примерно в таком виде:

1b2e76b3-ffc9-4846-a210-02ed19373976 -> 9f2ec8da-9f6c-417c-81c2-39fd2e1b8dd9
07ca4a9e-4262-44d4-a8e4-31ae37306003 -> 9441c57c-c88f-4476-8e8d-b9a281292170

Менять UUID надо в двух местах:

  • /mount/etc/fstab - список файловых систем. Там нужно поменять все UID
  • /mount/boot/grub/grub.cfg - конфигурация загрузчика. Тут будут только UUID разделов /boot и /, но будет их, в отличие от fstab, несколько

Если у вас нет доступа к BIOS и вы не можете поменять приоритет загрузки дисков, новый конфиг grub из /mount/boot/grub/grub.cfg нужно скопировать на место старого (/boot/grub/grub.cfg).

Данные скопированы, и теперь можно спокойно перезагружать сервер:

sync
reboot

После того, как сервер вновь станет отвечать на внешние раздражители, проверим, что он загрузился с новых дисков:

#mount
/dev/md0 on /boot type ext3 (rw,relatime,errors=continue,user_xattr,acl,barrier=1,data=ordered)
/dev/md3 on /tmp type xfs (rw,relatime,attr2,delaylog,noquota)
/dev/md4 on /usr type ext4 (rw,relatime,user_xattr,barrier=1,data=ordered)
/dev/md5 on /var type ext4 (rw,relatime,user_xattr,barrier=1,data=ordered)

Теперь осталось только проверить, что данные не потерялись по дороге и можно добавлять старый диск в новый RAID (напомню, сейчас наши массивы состоят из одного диска каждый, и RAID, строго говоря, не являются). До добавления старого диска мы еще можем обращаться к данным на нем, так убедитесь, что все данные переехали на новый диск и ничего не забыто. Шансов восстановить данные с диска, добавленного в RAID - нулевые:

mdadm /dev/md0 --add /dev/sda1
mdadm /dev/md1 --add /dev/sda2
mdadm /dev/md2 --add /dev/sda5
mdadm /dev/md3 --add /dev/sda6
mdadm /dev/md4 --add /dev/sda7
mdadm /dev/md5 --add /dev/sda8

Теперь остается только дождаться окончания синхронизации массива, а посмотреть состояние можно так:

cat /proc/mdstat