Привет, Хабр! Представляю вашему вниманию русскоязычную версию статьи "Migrating CentOS system from HDD to smaller SSD on XFS filesystem" автора Denis Savenko.


Cover Image


Данная статья является русскоязычной версией ранее мной же опубликованной статьи на английском языке с небольшими корректировками на аудиторию. Сразу оговорюсь — я не являюсь маньяком линукса или даже профессиональным системным администратором, поэтому вполне вероятно, что порой я мог использовать необычные, или даже глупые решения. Я буду очень благодарен вам, если вы укажете мне на них в комментариях, чтобы я мог улучшить данное руководство вместо того, чтобы просто заминусовать статью. Заранее вас за это благодарю.


Я думаю я не один, кто в какой-то момент решил преобрести себе SSD-диск для работающей системы. В моём случае это была работающая система на CentOS 7 на моём крошечном домашнем сервере. Далее я хотел перенести её "как есть" на новый диск. Но, как оказалось, это не так то просто сделать, учитывая следующее:


  • Новый SSD диск был гораздо меньшего объёма, чем уже установленный HDD (серьёзно, SSD всё ещё весьма дорог по сравнению с дисковыми накопителями).
  • Разделы на прежнем диске были отформатированы в файловой системе xfs. Это и не удивительно, учитывая тот факт, что CentOS, начиная с 7-ой версии предлагает эту файловую систему "по умолчанию" (наряду и с другими системами на основе RHEL, такими как, собственно, Red Hat Enterprise Linux 7, Oracle Linux 7 или Scientific Linux 7).
  • Работающая система должна остаться без изменений, включая конфигурацию, установленное ПО, права доступа и прочие атрибуты файловой системы.

Позвольте мне пояснить, почему описанные мной выше вещи являются серьёзными усложнениями поставленной перед нами задачи.


Во-первых, если бы размер нового диска был таким же или больше, чем у прежнего, можно было бы применить полное клонирование разделов с данными. Для этого существует масса утилит, таких как dd, ddrescue, partclone или даже clonezilla. Единственное, что нужно было бы сделать после, это подправить пару конфигурационных файлов в загрузочном разделе. Если же вы используете LVM для ваших разделов (что также является опцией по умолчанию в CentOS 7), задача могла бы быть ещё проще — в логический том добавляется новый диск, после чего с использованием команды pvmove данные переносятся на другой физический носитель внутри логического тома, и старый диск изымается из раздела, однако это невозможно при меньшем размере нового физического носителя.


Затем, если бы использовалась файловая система, отличная от xfs, например, достаточно популярная ext4, можно было бы "сжать" старый раздел диска до размеров нового диска. Затем выполняются какие либо действия из описанных выше и вы в дамках. Однако, изменение размера разделов в сторону уменьшения на xfs невозможно в силу её реализации — вы можете только увеличить разделы при её использовании.


И последнее, но ничуть не менее важное, если бы перед нами не стояло задачи перенести работающую сконфигурированную систему с сохранением всех метаданных, было бы проще просто установить систему с нуля на новый диск, заранее сохранив пару важных файлов. Это не подходило мне по двум причинам — во-первых волосы становились дыбом при мысли, что мне придётся повторить все те же действия, которые я выполнял на протяжении нескольких дней кряду, а во-вторых — это просто не спортивно. Я был уверен, что при должном понимании технических особенностей, перенос работающей системы на другой диск не должен был быть невыполнимой задачей.


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


Этап 1. Подготовка


Вот список того, что нам потребуется для переноса:


  1. Собственно, подлежащая миграции работающая система. В моём случае это была CentOS 7.4, но я уверен, что ситуация не будет отличаться от любой другой линукс-системы, работающей на xfs.
  2. Live CD или флешка с каким-либо дистрибутивом линукс. Для простоты, я буду отталкиваться от того же CentOS 7.4 Live CD. Я предпочитаю версию с Gnome, но это уже дело вкуса. Выбор этого дистрибутива обусловлен тем, что во-первых, он у меня уже был, а во-вторых с ним сразу поставляется нужный нам для переноса набор утилит, а в частности xfsdump. Я не буду останавливаться здесь на том, как создать загрузочный диск или флешку с Live CD дистрибутивом, так как предполагаю, что хабрачитатель должен справиться с этим и без меня.
  3. Объём данных на прежнем накопителе не должен превышать размер нового диска (будь то SSD или любой другой диск меньшего размера). В моём случае занято было всего порядка 10 ГБ, поэтому я даже не задумывался от чего нужно избавляться.
  4. Чашка горячего кофе.

Этап 2. Миграция


Глотнём кофе и приступим к процессу переноса с запуска системы с подготовленного Live CD дистрибутива. Затем откройте терминал и перейдите в режим суперпользователя при помощи команды su -


Далее в данном руководстве предполагается, что все команды выполняются от имени суперпользователя (root)

Шаг 1. Разрешение удалённого доступа (опционально)


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


Для того, чтобы разрешить удалённый доступ, необходимо задать пароль для пользователя root и запустить SSH демон:


su
passwd
systemctl start sshd

Теперь можно подключиться к целевой машине вашим SSH клиентом (например, PuTTY).


Шаг 2. Разбивка диска на разделы


Вы можете использовать вашу любимую утилиту для этого. Например, в Gnome есть предустановленная утилита для управления дисками. Также в интернетах хвалят gparted, однако я намеренно использовал fdisk, т.к, например, gparted просто не распознавал мой NVMe SSD-диск.


Диск следует разбить по образу и подобию прежнего диска. Я не маньяк партиционирования, поэтому моя схема партиционирования была стандартной:


  • /boot — стандартный раздел размером 1GB;
  • swap раздел размером 4G;
  • / — корневой раздел, LVM volume group с наименованием "main", занимающий оставшееся пространство на диске.

Итак, приступим к разбивке нового диска:


lsblk # проверим какое наименование в системе получил новый диск, в моём случае это nvme0n1
fdisk /dev/nvme0n1
n # добавить новый раздел (для /boot)
p # primary partition
# оставляем по умолчанию (1-й раздел)
# оставляем по умолчанию (от начала диска)
+1G # размер для раздела /boot
# готово
n # добавить новый раздел (для новой LVM volume group)
p # primary partition
# оставляем по умолчанию (2-й раздел)
# оставляем по умолчанию (от начала предыдущего раздела)
# оставляем по умолчанию (100% оставшегося места)
# готово
a # установка флага "bootable"
1 # для 10го раздела
p # проверим, всё ли в порядке
w # записываем таблицу партиционирования на диск

Раздел /boot должен быть стандартным линукс-разделом, поэтому сразу создаём файловую систему на нём:


mkfs.xfs /dev/nvme0n1p1 -f

И теперь нам необходимо создать LVM-структуру для второго раздела на диске. Назовём новую LVM-группу newmain (впоследствии переименуем):


pvcreate /dev/nvme0n1p2 # создаем новый физический LVM-диск для раздела
vgcreate newmain /dev/nvme0n1p2 # создаем новую LVM-группу и добавляем в неё только что добавленный физический LVM-диск
lvcreate -L 4G -n swap newmain # создаем логический раздел размером 4 ГБ под swap
lvcreate -l 100%FREE -n root newmain # создаем логический раздел на всё оставшееся место под корень

Теперь мы готовы к созданию файловой системы на новых логических разделах:


mkfs.xfs /dev/newmain/root # создание файловой системы для корневого раздела
mkswap -L swap /dev/newmain/swap # создание swap на новом разделе
swapon /dev/newmain/swap

Шаг 3. Активная фаза


Прежде чем мы начнём, сделаем обе LVM-группы активными (т.к. мы работаем из под Live CD):


vgchange -a y main
vgchange -a y newmain

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


mkdir -p /mnt/old/boot
mkdir -p /mnt/old/root
mkdir -p /mnt/new/boot
mkdir -p /mnt/new/root
mount /dev/sda1 /mnt/old/boot
mount /dev/nvme0n1p1 /mnt/new/boot
mount /dev/main/root /mnt/old/root
mount /dev/newmain/root /mnt/new/root

Убедимся, что всё в порядке при помощи команды lsblk:


lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
+-sda1             8:1    0     1G  0 part /mnt/old/boot
L-sda2             8:2    0 930.5G  0 part
  +-main-swap    253:0    0   3.6G  0 lvm  [SWAP]
  L-main-root    253:1    0 926.9G  0 lvm  /mnt/old/root
nvme0n1          259:0    0 119.2G  0 disk
+-nvme0n1p1      259:3    0     1G  0 part /mnt/new/boot
L-nvme0n1p2      259:4    0 118.2G  0 part
  +-newmain-swap 253:5    0     4G  0 lvm  [SWAP]
  L-newmain-root 253:6    0 114.2G  0 lvm  /mnt/new/root

Если вы видите что-то наподобие этого, вы всё сделали верно. И здесь начинается настоящая магия — мы собираемся использовать xfsdump для миграции наших данных. Эта утилита достаточно умная и знает о внутреннем устройстве файловой системы xfs, что позволяет ей копировать только занятые данными блоки, что, в свою очередь, во-первых позволяет нам копировать данные на диск меньшего размера, а во-вторых сильно ускоряет процесс переноса. Итак, давайте сделаем дамп данных при помощи этой утилиты, и на лету развернём эти данные на новом месте:


xfsdump -l0 -J - /mnt/old/boot | xfsrestore -J - /mnt/new/boot
xfsdump -l0 -J - /mnt/old/root | xfsrestore -J - /mnt/new/root

Пару слов об использованных флагах:


  • -J уменьшает размер фидбека;
  • - сообщает xfsdump и xfsrestore использовать стандартные потоки stdout и stdin соответственно вместо файлов.

Эта процедура может занять некоторое время (зависит от объёма ваших данных). Поэтому здесь вам пригодится приготовленная заранее чашка кофе, которую самое время выпить, а то остынет.


Если всё прошло хорошо, ваши данные были полностью скопированы с сохранением всех метаданных. Теперь необходимо подправить пару конфигурационных файлов и переустановить Grub2 на новый диск, чтобы сделать его загрузочным.


Шаг 4. Делаем новый диск загрузочным


Первое, что нужно сделать, это узнать UUID для старого и нового загрузочных разделов (/boot) при помощи команды blkid:


blkid
...
/dev/nvme0n1p1: UUID="3055d690-7b2d-4380-a3ed-4c78cd0456ba" TYPE="xfs"
...
/dev/sda1: UUID="809fd5ba-3754-4c2f-941a-ca0b6fb5c86e" TYPE="xfs"
...

Предполагая, что sda1 — прежний раздел \boot, и nvme0n1p1 — новый, производим замену UUID в конфигурационных файлах на новой точке монтирования:


sed -i "s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g" /mnt/new/root/etc/fstab
sed -i "s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g" /mnt/new/boot/grub2/grub.cfg

Эти две команды подготовят ваши системные файлы конфигурации к новому диску.


Теперь самое время переименовать LVM-группы и отмонтировать диски:


umount /mnt/{old,new}/{boot,root}
vgrename -v {,old}main
vgrename -v {new,}main

Единственное, что осталось сделать, это переустановить загрузчик. Это необходимо делать с использованием chroot:


mount /dev/main/root /mnt
mkdir -p /mnt/boot
mount /dev/nvme0n1p1 /mnt/boot
mount -t devtmpfs /dev /mnt/dev
mount -t proc /proc /mnt/proc
mount -t sysfs /sys /mnt/sys
chroot /mnt/ grub2-install /dev/nvme0n1

Шаг 5. Последний штрих


На данном этапе все данные уже должны быть перенесены и новый диск должен стать загрузочным. Нужно только перезапуститься, вынуть Live CD из привода, и выбрать новый диск в BIOS для загрузки:


systemctl reboot -f

Если что-то пошло не так и система не загружается, вы всегда можете "откатиться" на старый диск, просто запустившись с Live CD повторно и переименовав LVM-группы обратно, выполнив vgrename -v {,new}main и vgrename -v {old,}main, и затем снова перезапуститься.

На этом обязательная часть программы окончена и мы успешно перенесли работающую систему на новый диск. Старый диск можно извлекать из компьютера.




Использование старого HDD в качестве медиа-хранилища


Если вы, как и я, после переноса системы, хотите использовать ваш прежний диск как медиа-хранилище, это делается также легко.


Во-первых, переразбейте прежний диск


fdisk /dev/sda
d # удалить раздел 2
d # удалить раздел 1
n # новый раздел
p # primary partition
# раздел 1
# от начала
# до конца диска
# готово
p # проверим, что всё хорошо
w # перезапишем таблицу партиционирования

Мы не станем создавать файловую систему на диске напрямую. Вместо этого мы создадим новую LVM-группу, в которую добавим этот диск. Это позволит нам в будущем легко добавлять новые диски в эту группу без лишней мороки (логический диск при этом останется прежний):


pvcreate /dev/sda1 # новый физический LVM-диск
vgcreate media /dev/sda1 # новая LVM-группа
lvcreate -l 100%FREE -n media1 media # новый логический топ размером во весь добавленный диск
vgchange -a y media # делаем новую LVM-группу активной

mkfs.xfs /dev/media/media1 # создаем файловую систему на логическом диске

mkdir -p /var/media # создаем директорию для точки монтирования
mount /dev/media/media1 /var/media # монтируем новый диск к системе

Для того, чтобы сохранить изменения после перезагрузки системы, следует также добавить запись о точке монтирования в /etc/fstab:


/dev/mapper/media-media1 /var/media                       xfs     defaults        0 0

Готово!


Теперь можно утверждать, что мы успешно перенесли работающую на xfs систему на новый диск меньшего размера. Как бонус, мы начали использовать прежний диск в качестве медиа-хранилища.




UPDATE 02.04.2018


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


systemctl enable fstrim.timer

Это включит на новой системе исполнение trim раз в неделю на любой systemd системе. В случае, если вы или ваша система не используете systemd, есть исчерпывающее руководство от DigitalOcean на почти любой случай.




Пожалуйста, оставляйте ваши комментарии, если нашли ошибку или знаете способ лучше как выполнить те или иные действия. Надеюсь, данная инструкция окажется вам полезной!

Комментарии (25)


  1. Softer
    30.03.2018 13:32

    Столько телодвижений… В ведь хватило бы простого rsync (+ grub-install), не?
    Причем делать rsync можно даже на живой системе, главное в exclude добавить /dev и прочие pseudo- и tmp- fs.


    1. ZZa Автор
      30.03.2018 14:07

      Я не адепт `rsync`, но возможно вы правы. Однако, телодвижений, возможно, пришлось сделать бы даже больше (учитывая то, насколько аккуратно нужно было бы указывать какие директории переносить, а какие нет). А ещё `rsync` ничего не знает о расширенных атрибутах файловой системы `xfs`, которые будут безвозвратно утеряны при переносе.


      1. Softer
        30.03.2018 14:15

        Атрибуты — возможно, я в свою очередь не адепт «XFS» :)
        Пока есть успешный опыт многочисленных переносов EXT4 -> EXT4 и ReiserFS -> EXT4 разных дистров и разных объемов данных.
        Аккуратность же сводится к просмотру /etc/mtab и обязательному указанию в exclude директории куда примонтирован новый накопитель (а то получится рекурсивное копирование — занятно потом по директориям пройтись :) ).


      1. bfuvx
        01.04.2018 19:47

        rsync знает о расширенных атрибутах и имеет соответтсвующую опцию -X.


    1. bfuvx
      01.04.2018 19:43

      Причем делать rsync можно даже на живой системе

      Сделать-то можно, но точной консистентности данных не получится. Можно, конечно, остановить все сервисы и сделать remount root'а в read only, но легче ребутнуться и сделать финальный rsync из live окружения и гарантировать консистентность. Ведь все равно сервисы будут недоступны и в первом варианте.


      1. Softer
        01.04.2018 20:45

        Да, именно. Для десктопов, ИМХО, хватает просто все закрыть. Для серверов — остановить сервисы. Простой будет, но гораздо меньше чем полная остановка. Из практики: 2 Тб писем в Exim4+Cyrus при переносе HW>VIRT (в довесок — ReiserFS > EXT4) — даунтайм 15-25 минут; 300 ГБ MySQL-баз — минут 10 даунтайм.


  1. cab404
    30.03.2018 13:35

    Если уж это LVM, не легче ли было сделать зеркало всех разделов на новом диске, отключить старый, и вуаля — переключение дисков на лету? Не знаю, правда, про xfs, но ext4 умеет менять размеры смонтированных разделов, так что и с SSD меньшего размера не должно было быть проблем — недавно провел подобное на ноутбуке.


    1. mikkisse
      30.03.2018 13:49

      Вот, что сообщает документация Red Hat, например

      After an XFS file system is created, its size cannot be reduced. However, it can still be enlarged using the xfs_growfs command


      1. cab404
        30.03.2018 13:59

        Ок, тогда метод действительно не подойдет для автора. Только заметил, что автор выключал сервер при переносе.


    1. ZZa Автор
      30.03.2018 14:03

      То, что вы предлагаете сделать, невозможно с xfs (и да, я об этом написал в статье). А на ext4 вы правы, можно было бы shrink'ануть раздел и выполнить pvmove, но это совсем другая история.


      1. cab404
        30.03.2018 14:15

        Да даже не с pvmove, с lvconvert -m 1, а потом lvconvert -m 0


        1. bfuvx
          01.04.2018 19:24

          А что, по вашему, делает pvmove? Создается raid через тот же dm-raid и дальше только мониторится процесс синка с заданным интервалом. Мало того, процесс pvmove можно спокойно «прибивать» сразу после запуска, на синк это никак не повлияет (сам процесс синка можно будет мониторить, например, так: dmsetup status|grep mirror). Pvmove только меняет lvm метаданные, создает raid и по окончанию меняет метаданные снова и разваливает raid.


    1. mtex
      30.03.2018 14:05

      Ext4 не умеет в уменьшение раздела на лету.


      1. cab404
        30.03.2018 14:11

        Хм, попробовал, действительно так. Видимо переносил таки меньшие разделы.


    1. shikhov
      01.04.2018 13:20

      Кстати, Windows 2008 тоже можно перенести на лету через создание зеркала.


  1. BubaVV
    30.03.2018 14:39

    Помню, в давние времена XFS любил при сбое затирать нулями все открытые файлы…


    1. ZZa Автор
      30.03.2018 18:53

      Думаю тот факт, что Red Hat сделали xfs файловой системой по умолчанию в 7-й версии своего основного коммерческого продукта говорит о том, что она (xfs) шагнула далеко вперёд в плане надёжности


      1. maxzhurkin
        31.03.2018 19:51

        Или добавили рекомендацию не допускать сбоев в документацию


  1. dra90n7
    30.03.2018 18:50

    Есть статья на схожую тематику.
    vladimir-stupin.blogspot.ru/2013/10/debian-raid-1.html


  1. iZENfire
    31.03.2018 12:10

    На FreeBSD в файловой системе UFS2 давно пользуются dump/restore для точного переноса данных из одной ФС в новую безотносительно размеров — главное чтобы объём данных помещался в новую ФС. Снапшотинг ZFS — то же самое.


  1. bfuvx
    01.04.2018 18:50

    Для того, чтобы разрешить удалённый доступ, необходимо задать пароль для пользователя root и запустить SSH демон

    Если говорить о redhat-производных дистрибитивах, то можно на этапе загрузки добавить опции в commandline ядра: inst.sshd inst.vnc inst.vncpassword=pass. Ssh, правда, будет беспарольный, и если сеть недоверенная, то после первого захода лучше установить пароль. Этот способ удобней, если используется не livecd, а какой-нибудь minimal/netinstall и не будет подготовленных конфигов для запуска ssh с помощью systemd/init сервиса + есть vnc, если хочется графики.
    задача могла бы быть ещё проще — в логический том добавляется новый диск, после чего с использованием команды pvmove данные переносятся на другой физический носитель внутри логического тома

    Тут небольшая путаница. Новый диск (его раздел) добавляется в физичесий том, физический том в логическую группу томов (расширяется существующая), и затем логический том переносится внутри логической группы на другий физический носитель (физичесий том).
    Единственное, что осталось сделать, это переустановить загрузчик. Это необходимо делать с использованием chroot

    Можно и без chroot, у grub2-install есть опция --boot-directory. В том числе актуально, если под рукой есть только x86 live, а препарируемая система — x86_64 и chrot'нуться не получится.

    Ну и как говорили выше, намного универсальней будет использовать rsync, например с опциями --delete --numeric-ids -aHAXS (сохраняем все возможные атрибуты в неизменном виде), тогда мы не зависим от фс между которыми перенос. Если сервер выключен, то ничего exclud'ить не надо. А если нужно минимизировать время простоя, то обычно делают 3ой rsync (в live режиме с exclud'ами, еще раз, чтобы синхронизировать разницу, которая накопилась во время первого прогона и 3ий раз после выключения).


    1. ZZa Автор
      01.04.2018 18:55

      Спасибо, действительно дельные дополнения. Особенно про опцию --boot-directory для grub2-install. Странно, что в интернете нигде таких рекомендаций не дают, а напротив, зачем-то предлагают использовать grub-install, потому что, якобы, с Grub2 у них возникала масса проблем.


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


      1. bfuvx
        01.04.2018 19:07

        Еще забыл упомянуть: в статье не помечено, но т.к. переезд осуществляется на ssd, то уже на перенесенной системе нужно не забыть включить функционал trim'а (например так: systemctl enable fstrim.timer, для trim'а раз в неделю в рассматриваемом дистрибутиве).


        1. ZZa Автор
          02.04.2018 14:07

          Я не стал об этом писать, т.к. это мой первый опыт использования SSD и как я понял, trim поддерживается совсем не всеми SSD-дисками. Те же способы проверки поддерживается ли эта команда диском или нет, на моём диске не отрабатывали (ADATA SX8000). А рекомендовать то, в чём я не уверен, не в моих правилах.


          1. ZZa Автор
            02.04.2018 14:29

            Руководствуясь вашим советом и статьёй с DigitalOcean, я так понял, что скрипт fstrim сам разберётся поддерживается ли trim для дисков. Поэтому я дополнил статью этой рекомендацией. Ещё раз спасибо за дельное замечание.