Перевести уже работающий Linux-сервер на RAID1 значительно сложнее, чем создать RAID во время установки системы. Особенно если сервер находится удалённо, работает в режиме UEFI, содержит виртуальные машины и его нельзя надолго останавливать.

В этой статье разберём практический случай миграции CentOS Stream 8 на программный RAID1 средствами mdadm без переустановки операционной системы.

Основной принцип миграции следующий: сначала создаём деградированный RAID1 на свободном диске, полностью переносим туда систему, делаем новый диск загрузочным и проверяем реальный запуск с него. Только после успешной загрузки старый системный диск можно переразметить и добавить вторым зеркалом.

Исходная конфигурация сервера

На сервере изначально использовалась следующая схема:

/dev/sda    240 GB SSD — системный диск
├─sda1     600 MB      /boot/efi
├─sda2       1 GB      /boot
├─sda3      50 GB      /
├─sda4     7.8 GB      swap
└─sda5     164 GB      /Data

/dev/sdd    240 GB SSD — пустой диск

/dev/sdb      4 TB HDD — старый /Backup
/dev/sdc      4 TB HDD — новый пустой диск

/dev/nvme0n1 256 GB NVMe
└─nvme0n1p1             /SSD

Каталоги /Data, /SSD и /Backup использовались в том числе виртуальными машинами KVM/libvirt.

Необходимо было получить следующую конфигурацию:

sda + sdd
    ↓
RAID1

md11 → /boot
md12 → / и /Data

sdb + sdc
    ↓
md10 → /Backup

nvme0n1p1 → /SSD

При этом отдельный раздел /Data было решено ликвидировать. После миграции /Data стал обычным каталогом внутри большой корневой файловой системы /.

Важное предупреждение

Команды wipefs, parted, mkfs и mdadm могут уничтожить данные.

Перед выполнением любых операций необходимо несколько раз проверить имена дисков:

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
findmnt
blkid
df -hT
cat /etc/fstab

Если сервер находится удалённо, особенно важно не уничтожать старый системный диск до тех пор, пока новый диск не пройдёт настоящую контрольную загрузку.

RAID1 не является резервной копией. RAID защищает от отказа физического диска, но не защищает от удаления файлов, повреждения файловой системы, шифровальщика, ошибки администратора или повреждения данных приложением.


Создание RAID1 для /Backup

Начнём с хранилища резервных данных.

Исходная схема:

/dev/sdb → старый /Backup
/dev/sdc → новый пустой HDD 4 TB

Заполненный sdb нельзя было сразу использовать для создания нового RAID, поэтому сначала массив был создан в деградированном состоянии только на пустом sdc.

Разметка нового диска

parted -s /dev/sdc mklabel gpt
parted -s -a optimal /dev/sdc mkpart primary 1MiB 100%
parted -s /dev/sdc set 1 raid on

partprobe /dev/sdc
udevadm settle

Создание деградированного RAID1

mdadm --create /dev/md10 \
    --metadata=1.2 \
    --level=1 \
    --raid-devices=2 \
    --bitmap=internal \
    missing /dev/sdc1

После этого:

cat /proc/mdstat

покажет примерно:

md10 : active raid1 sdc1[1]
      [2/1] [_U]

Это нормальное состояние: массив рассчитан на два устройства, но пока работает только на одном.

Создание файловой системы

mkfs.ext4 -m 0 -L BACKUP_RAID /dev/md10

Монтируем:

mkdir -p /mnt/backup-raid
mount /dev/md10 /mnt/backup-raid

Перенос данных

Перед копированием необходимо остановить виртуальные машины, которые используют файлы на старом /Backup.

Для обычных данных можно использовать:

rsync -aH --numeric-ids --info=progress2 \
    /Backup/ \
    /mnt/backup-raid/

Для активно работающих виртуальных дисков простого копирования .img или .qcow2 недостаточно. Виртуальную машину лучше корректно остановить либо использовать штатный механизм snapshot/backup гипервизора.

После переноса меняем /etc/fstab, чтобы /Backup монтировался с md10.

UUID определяем командой:

blkid /dev/md10

Пример строки:

UUID=<UUID_MD10> /Backup ext4 defaults 1 2

Проверяем:

findmnt /Backup

Должно быть:

/Backup /dev/md10 ext4

Перевод системного диска на RAID1

Теперь переходим к системным SSD.

/dev/sda — работающая система
/dev/sdd — пустой SSD такого же размера

Главный принцип безопасности: старый sda пока вообще не трогаем.

На пустом sdd создаём будущую систему, загружаемся с неё и только потом переразмечаем sda.

Разметка нового системного SSD

Для конкретного диска объёмом 240057409536 байт была использована следующая схема:

wipefs -a /dev/sdd

parted -s /dev/sdd mklabel gpt

parted -s /dev/sdd unit s mkpart ESP fat32 2048 1230847
parted -s /dev/sdd set 1 esp on

parted -s /dev/sdd unit s mkpart primary 1230848 3327999
parted -s /dev/sdd set 2 raid on

parted -s /dev/sdd unit s mkpart primary 3328000 468850687
parted -s /dev/sdd set 3 raid on

partprobe /dev/sdd
udevadm settle

Получаем:

sdd
├─sdd1   600M   EFI
├─sdd2     1G   Linux RAID
└─sdd3   222G   Linux RAID

Важно: номера секторов выше подходят только для дисков с такой же геометрией. На другом сервере границы необходимо рассчитывать отдельно.

Создание RAID1 для /boot

mdadm --create /dev/md11 \
    --metadata=1.0 \
    --level=1 \
    --raid-devices=2 \
    --bitmap=none \
    missing /dev/sdd2

Для /boot используется metadata 1.0.

Создание RAID1 для корневой файловой системы

mdadm --create /dev/md12 \
    --metadata=1.2 \
    --level=1 \
    --raid-devices=2 \
    --bitmap=none \
    missing /dev/sdd3

Проверяем:

cat /proc/mdstat

Нормальное состояние на данном этапе:

md11 [2/1] [_U]
md12 [2/1] [_U]

Создание файловых систем

EFI

mkfs.vfat -F32 -n EFI_RAID2 /dev/sdd1

/boot

mkfs.ext4 -L boot_raid /dev/md11

Корень /

mkfs.ext4 -L root_raid /dev/md12

Монтирование будущей системы

mkdir -p /mnt/newroot

mount /dev/md12 /mnt/newroot

mkdir -p /mnt/newroot/boot
mount /dev/md11 /mnt/newroot/boot

mkdir -p /mnt/newroot/boot/efi
mount /dev/sdd1 /mnt/newroot/boot/efi

Проверяем:

findmnt /mnt/newroot
findmnt /mnt/newroot/boot
findmnt /mnt/newroot/boot/efi

Перенос работающей CentOS на md12

Для переноса корневой системы используем rsync.

Очень важен параметр -x. Он запрещает переходить на другие файловые системы, поэтому команда не начинает копировать внутрь root содержимое отдельно смонтированных /Data, /Backup, /SSD, /boot, /proc, /sys и других точек монтирования.

rsync -aHAx --numeric-ids --info=progress2 \
    / \
    /mnt/newroot/

Первоначально использовался вариант с -X, но при переносе SELinux extended attributes появились ошибки:

rsync_xal_set:
lremovexattr(...,"security.selinux") failed:
Permission denied

Поэтому перенос повторили без -X, а SELinux-контексты решили восстановить отдельно после успешной миграции.

Перенос /boot

rsync -aHAx --numeric-ids --info=progress2 \
    /boot/ \
    /mnt/newroot/boot/

Перенос EFI

rsync -rt --info=progress2 \
    /boot/efi/ \
    /mnt/newroot/boot/efi/

Объединение /Data с корневой файловой системой

До миграции /Data находился на отдельном разделе:

/dev/sda5 → /Data

После миграции отдельный раздел решили убрать.

Перед переносом необходимо остановить виртуальные машины, которые используют файлы на /Data.

Проверить открытые файлы:

fuser -vm /Data

После остановки VM:

mkdir -p /mnt/newroot/Data

rsync -aHAx --numeric-ids --info=progress2 \
    /Data/ \
    /mnt/newroot/Data/

Проверяем размеры:

du -sh /Data
du -sh /mnt/newroot/Data

После перехода на новую систему:

/          → md12
/Data      → обычный каталог внутри md12

Замена swap-раздела на swapfile

Отдельный swap-раздел после объединения пространства больше не нужен.

fallocate -l 8G /mnt/newroot/swapfile
chmod 600 /mnt/newroot/swapfile
mkswap /mnt/newroot/swapfile

Создание нового /etc/fstab

Получаем UUID:

ROOTUUID=&#36;(blkid -s UUID -o value /dev/md12)
BOOTUUID=&#36;(blkid -s UUID -o value /dev/md11)
EFIUUID=&#36;(blkid -s UUID -o value /dev/sdd1)
BACKUPUUID=&#36;(blkid -s UUID -o value /dev/md10)
SSDUUID=&#36;(blkid -s UUID -o value /dev/nvme0n1p1)

Новый /etc/fstab:

UUID=<ROOTUUID>     /          ext4    defaults        1 1
UUID=<BOOTUUID>     /boot      ext4    defaults        1 2
UUID=<EFIUUID>      /boot/efi  vfat    umask=0077,shortname=winnt 0 2

/swapfile            none       swap    defaults        0 0

UUID=<BACKUPUUID>   /Backup    ext4    defaults        1 2
UUID=<SSDUUID>      /SSD       ext4    defaults        1 1

Отдельной записи для /Data больше нет.


Настройка mdadm.conf

Проверяем найденные массивы:

mdadm --detail --scan

Пример:

ARRAY /dev/md10 ...
ARRAY /dev/md11 ...
ARRAY /dev/md12 ...

Новая система должна содержать эти массивы в:

/etc/mdadm.conf

Подготовка chroot

Подключаем системные файловые системы:

mount --bind /dev /mnt/newroot/dev
mount --bind /dev/pts /mnt/newroot/dev/pts
mount -t proc /proc /mnt/newroot/proc
mount --bind /sys /mnt/newroot/sys
mount --bind /run /mnt/newroot/run

Заходим:

chroot /mnt/newroot /bin/bash

Пересборка initramfs с поддержкой RAID

Для текущего ядра:

dracut -N -f \
    --add mdraid \
    --mdadmconf \
    /boot/initramfs-4.18.0-408.el8.x86_64.img \
    4.18.0-408.el8.x86_64

Проверяем наличие mdraid и mdadm:

lsinitrd /boot/initramfs-4.18.0-408.el8.x86_64.img | \
grep -Ei 'mdadm|mdraid|mdadm.conf'

Также можно проверить встроенный конфигурационный файл:

lsinitrd \
    /boot/initramfs-4.18.0-408.el8.x86_64.img \
    /etc/mdadm.conf

Настройка kernelopts

Проверяем текущие параметры:

grub2-editenv /boot/grub2/grubenv list

Корневой раздел должен указываться через UUID новой файловой системы md12:

root=UUID=<UUID_MD12>

Также указываем mdraid-массив:

rd.md.uuid=<UUID_ARRAY_MD12>

Для первой удалённой тестовой загрузки можно временно использовать:

enforcing=0

Например:

grubby --update-kernel=ALL \
    --args="root=UUID=<ROOT_UUID> rd.md.uuid=<MD12_UUID> enforcing=0"

Старые параметры root=, resume= и rd.md=0 необходимо удалить.

После изменения:

grub2-editenv /boot/grub2/grubenv list

Настройка GRUB для UEFI

Проверяем наличие загрузочных файлов:

find /boot/efi/EFI -maxdepth 2 -type f

В CentOS обычно присутствуют:

/boot/efi/EFI/centos/shimx64.efi
/boot/efi/EFI/centos/grubx64.efi
/boot/efi/EFI/centos/grub.cfg

Создаём конфигурацию GRUB:

grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

Если os-prober начинает сканировать старые диски и создаёт ошибки device-mapper, его можно временно отключить:

chmod -x /etc/grub.d/30_os-prober

grub2-mkconfig \
    -o /boot/efi/EFI/centos/grub.cfg

chmod +x /etc/grub.d/30_os-prober

Проверяем синтаксис:

grub2-script-check /boot/efi/EFI/centos/grub.cfg
echo &#36;?

Результат должен быть:

0

Также полезно проверить, что GRUB подключает поддержку mdraid:

grep -nE 'mdraid|blscfg' \
    /boot/efi/EFI/centos/grub.cfg

В рабочей конфигурации должна присутствовать строка:

insmod mdraid1x

Безопасная тестовая загрузка через UEFI BootNext

На удалённом сервере не следует сразу менять постоянную загрузочную запись.

Создаём новую UEFI-запись для sdd:

efibootmgr -c \
    -d /dev/sdd \
    -p 1 \
    -L "CentOS RAID TEST" \
    -l '\EFI\centos\shimx64.efi'

Проверяем:

efibootmgr -v

Например:

Boot0000* CentOS RAID TEST

Назначаем эту запись только для следующего запуска:

efibootmgr -n 0000

Проверяем:

efibootmgr

Должно появиться:

BootNext: 0000

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

sync
reboot

Проверка после первой загрузки с RAID

После восстановления SSH выполняем:

efibootmgr

findmnt /
findmnt /boot
findmnt /boot/efi

findmnt /Data

cat /proc/mdstat
swapon --show
getenforce

Правильный результат:

/          → /dev/md12
/boot      → /dev/md11
/boot/efi  → /dev/sdd1

/Data      → не является отдельной файловой системой

swap       → /swapfile

SELinux    → Permissive

При этом массивы пока деградированы:

md11 [_U]
md12 [_U]

Это нормально, потому что старый sda ещё не был добавлен.


Обязательная контрольная загрузка перед уничтожением старого sda

После первого успешного запуска необходимо отдельно проверить постоянную UEFI-запись нового SSD.

После reboot проверяем:

efibootmgr
findmnt /
findmnt /boot
findmnt /boot/efi

Необходимо убедиться, что:

BootCurrent → запись нового sdd

/          → /dev/md12
/boot      → /dev/md11
/boot/efi  → /dev/sdd1

Только после этого можно уничтожать старую систему на sda.


Добавление старого системного SSD в RAID1

После проверки загрузки старый sda переразмечивается аналогично sdd:

sda1   600M   EFI
sda2     1G   Linux RAID
sda3   222G   Linux RAID

Перед добавлением обязательно сравниваем размеры:

blockdev --getsz /dev/sda2
blockdev --getsz /dev/sdd2

blockdev --getsz /dev/sda3
blockdev --getsz /dev/sdd3

Добавление второго диска в /boot RAID

mdadm --manage /dev/md11 --add /dev/sda2

Следим:

watch -n 2 cat /proc/mdstat

После завершения:

md11 [UU]

Добавление второго диска в root RAID

mdadm --manage /dev/md12 --add /dev/sda3

Следим:

watch -n 5 cat /proc/mdstat

В нашем случае итоговый результат:

md11 : active raid1 sda2[2] sdd2[1]
       [UU]

md12 : active raid1 sda3[2] sdd3[1]
       [UU]

Второй EFI-раздел

EFI System Partition в RAID1 не включается.

На втором SSD создаём отдельный FAT32 ESP:

mkfs.vfat -F32 -n EFI_RAID1 /dev/sda1

Монтируем:

mkdir -p /mnt/efi-sda
mount /dev/sda1 /mnt/efi-sda

Копируем текущий рабочий EFI:

rsync -rt /boot/efi/ /mnt/efi-sda/
sync

После копирования:

umount /mnt/efi-sda

Создаём UEFI-запись:

efibootmgr -c \
    -d /dev/sda \
    -p 1 \
    -L "CentOS RAID SDA" \
    -l '\EFI\centos\shimx64.efi'

Таким образом каждый системный SSD содержит собственный EFI System Partition, а /boot и / защищены RAID1.


Диагностика проблемного HDD при создании RAID1 для /Backup

Во время создания RAID1 для /Backup возник отдельный интересный случай.

Старый диск /dev/sdb имел историю ошибок чтения UNC.

SMART показывал:

SMART overall-health: PASSED

Reallocated_Sector_Ct    0
Current_Pending_Sector   0
Offline_Uncorrectable    0

Reported Uncorrectable Errors: 6

Несмотря на SMART PASSED, ранее в журнале диска присутствовали реальные UNC.

Первая попытка добавить диск

После добавления в RAID:

md10 : active raid1 sdb1[2](W)(F) sdc1[1]

Обозначения:

W = write-mostly
F = faulty

Диск был исключён из массива.

Проверка прямой записи

dd if=/dev/zero of=/dev/sdb1 \
    bs=4M count=256 \
    oflag=direct conv=fsync \
    status=progress

Запись 1 GiB прошла успешно со скоростью около 190 MB/s.

Проверка известного проблемного участка

Дополнительно был перезаписан участок, содержащий ранее проблемный LBA:

dd if=/dev/zero of=/dev/sdb1 \
    bs=4096 \
    seek=14454479 \
    count=4096 \
    oflag=direct \
    conv=fsync \
    status=progress

Затем этот же участок был прочитан:

dd if=/dev/sdb1 of=/dev/null \
    bs=4096 \
    skip=14454479 \
    count=4096 \
    iflag=direct \
    status=progress

Обе операции завершились с:

RC=0

Полный SMART Extended Test

smartctl -t long /dev/sdb

Для данного Toshiba 4 TB тест занял несколько часов.

Финальный результат:

Extended offline
Completed without error

После полного теста:

Reallocated_Sector_Ct      0
Current_Pending_Sector     0
Offline_Uncorrectable      0
Device Error Count         6

То есть старые шесть ошибок остались в истории SMART, но новых ошибок за полный проход поверхности не появилось.


Повторное добавление проблемного HDD в RAID

После полного тестирования диск был добавлен в массив ещё раз.

mdadm /dev/md10 --add /dev/sdb1

После успешного старта recovery для диска был включён режим write-mostly:

echo writemostly > \
/sys/block/md10/md/dev-sdb1/state

Проверяем:

cat /sys/block/md10/md/dev-sdb1/state

Во время восстановления:

write_mostly,spare

Массив:

md10 : active raid1 sdb1[2](W) sdc1[1]
      [>....................] recovery = ...

После успешной синхронизации ожидаем:

md10 [UU]

и:

in_sync,write_mostly

Режим write-mostly означает, что Linux MD по возможности будет читать данные с другого зеркала и использовать данный диск преимущественно для записи копии.

Для диска с историей UNC это разумная дополнительная мера, хотя она не отменяет необходимости дальнейшего SMART-мониторинга.


Что делать с отдельным NVMe

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

/dev/nvme0n1p1 → /SSD

На нём находятся наиболее производительно-зависимые диски виртуальных машин.

Добавлять NVMe в RAID1 с обычными SATA SSD нецелесообразно: производительность записи зеркала будет ограничена более медленным устройством.

Поэтому NVMe оставлен отдельным:

/SSD

Но одиночный NVMe становится отдельной точкой отказа.

Следовательно, данные с /SSD необходимо регулярно резервировать на:

/Backup

Для образов работающих виртуальных машин следует использовать консистентный backup: snapshot, guest-agent, штатные средства libvirt либо временную остановку VM.

Простое копирование активно работающего образа виртуального диска через cp или rsync не является полноценным резервным копированием.


Итоговая конфигурация сервера

                  SYSTEM RAID1
           ┌─────────────────────┐
           │                     │
      /dev/sda              /dev/sdd
           │                     │
       sda2 ├────── md11 ────────┤ sdd2
           │        /boot        │
           │                     │
       sda3 ├────── md12 ────────┤ sdd3
           │          /          │
           │        /Data        │
           └─────────────────────┘

EFI:
sda1 ─ отдельная ESP
sdd1 ─ отдельная ESP

                 BACKUP RAID1
           ┌─────────────────────┐
           │                     │
       sdb1 ├────── md10 ────────┤ sdc1
           │       /Backup       │
           └─────────────────────┘

NVMe:

nvme0n1p1
    │
   /SSD
    │
быстрые диски VM
    │
backup → /Backup

Полезные команды мониторинга RAID

Общее состояние

cat /proc/mdstat

Подробная информация

mdadm --detail /dev/md10
mdadm --detail /dev/md11
mdadm --detail /dev/md12

Нагрузка на диски

iostat -xm 5

Сообщения ядра в реальном времени

journalctl -k -f

Поиск дисковых ошибок

journalctl -k --no-pager | \
egrep -i 'I/O error|UNC|medium error|failed|ata|sd[a-z]'

Полный SMART

smartctl -x /dev/sdb

Extended SMART Test

smartctl -t long /dev/sdb

Результаты self-test

smartctl -l selftest /dev/sdb

Возвращение SELinux в Enforcing

На время первой удалённой загрузки мы использовали:

enforcing=0

После полной проверки RAID, загрузчика, сетевых интерфейсов и сервисов необходимо вернуть штатный режим SELinux.

Для полной перемаркировки файловой системы:

touch /.autorelabel

Убираем временный параметр ядра:

grubby --update-kernel=ALL \
    --remove-args="enforcing=0"

После этого выполняем перезагрузку в запланированное окно обслуживания:

reboot

Первая загрузка после полной SELinux-перемаркировки может занять значительно больше времени, поэтому на удалённом сервере желательно иметь доступ к IPMI, iDRAC, iLO, KVM или другой out-of-band консоли.

После загрузки:

getenforce

Ожидаемый результат:

Enforcing

Итог

Перевести уже установленный CentOS Stream 8 с обычного системного диска на программный RAID1 можно без переустановки операционной системы.

Самая безопасная схема миграции для удалённого сервера заключается не в попытке переделать работающий root «на месте», а в последовательном переносе:

  1. Создать деградированный RAID1 на свободном диске.
  2. Создать новые файловые системы.
  3. Перенести работающую систему через rsync.
  4. Перенести /boot и EFI.
  5. Объединить /Data с новой корневой файловой системой.
  6. Создать новый fstab и mdadm.conf.
  7. Пересобрать initramfs с поддержкой mdraid.
  8. Настроить GRUB и UEFI.
  9. Выполнить тестовую загрузку через BootNext.
  10. Проверить реальную загрузку с нового SSD.
  11. Только после этого переразметить старый системный диск.
  12. Добавить второй диск в RAID1 и дождаться состояния [UU].

В результате в нашем случае удалось:

  • перевести /boot на RAID1;
  • перевести корневую файловую систему / на RAID1;
  • объединить старые / и /Data;
  • заменить отдельный swap-раздел на swapfile;
  • создать отдельный RAID1 для /Backup;
  • сохранить NVMe как быстрое хранилище виртуальных машин;
  • создать независимые EFI-разделы на обоих системных SSD;
  • сохранить возможность загрузки сервера при отказе одного системного диска.

И главное правило, которое стоит помнить после завершения всех работ:

состояние RAID1 [UU] обеспечивает отказоустойчивость дисков, но RAID не заменяет резервное копирование.