Как перенести UEFI системный диск Enterprise Linux на другое устройство?
Часто бывает, что при автоматизации процессов инженеры чувствуют себя весьма расслабленно — мол, система сделает все сама и как надо. Но, увы, иногда автоматика выбирает немного не то, что выбрал бы сам инженер при ручных действиях. Приходится это исправлять.
В статье рассмотрим способ переноса системного раздела ОС Linux на другое блочное устройство и необходимые изменения в UEFI загрузчике.
Предыстория
Ситуация казалась типичной. Пришел новый сервер, внутри два диска. Я сделал из них аппаратный RAID1 и отправил в Kickstart инсталляцию, чтобы накатить заказанный Enterprise Linux 8.
Коллеги из DBA Bercut через Ansible установили СУБД и смигрировали туда сервис, запустили в прод. А через несколько дней я случайно заметил, что на файловых системах сервера свободно 1,5ТБ пространства, хотя я помнил, что там было два SAS диска по 600ГБ. Нестыковочка.
Оказывается, в купленном заказчиком сервере была установлена еще пара контроллеров NVMe, и Anaconda во время Kickstart инсталляции решила занять именно один из них.
На эти NVMe контроллеры были другие планы — их собирались использовать более оптимальным образом.
Сервер уже в проде — ну что же, используем эту ошибку, как возможность поделиться опытом и написать интересную статью для Хабра.
Погнали чинить
Конечно, придется делать работы ночью: т.к. будет пара перезагрузок, мы совместим их с другими плановыми работами.
Требуется небольшая подготовка, нужен свежий SystemResqueCD. Возьмем его отсюда.
Необходимо предусмотреть, где мы временно разместим дамп существующих файловых систем. Это может быть сетевой носитель, либо еще один свободный диск внутри этого же сервера, например, второй NVMe контроллер (он уже есть в нашем сервере) отлично подойдет.
Осмотримся на сервере
lsblk покажет нам все доступные блочные устройства.
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 558.4G 0 disk
nvme1n1 259:0 0 1.8T 0 disk
nvme0n1 259:1 0 1.8T 0 disk
├─nvme0n1p1 259:2 0 256M 0 part /boot/efi
├─nvme0n1p2 259:3 0 1G 0 part /boot
└─nvme0n1p3 259:4 0 1.8T 0 part
├─bercutvg-root 252:0 0 24.4G 0 lvm /
├─bercutvg-swap 252:1 0 16.6G 0 lvm [SWAP]
├─bercutvg-u01 252:2 0 1.7T 0 lvm /u01
├─bercutvg-home 252:3 0 19.5G 0 lvm /home
├─bercutvg-opt 252:4 0 9.8G 0 lvm /opt
└─bercutvg-var 252:5 0 9.8G 0 lvm /var
...
Под устройством /dev/sda
скрывается аппаратное зеркало из двух SAS дисков по 600ГБ.
# smartctl -i /dev/sda -d megaraid,0
=== START OF INFORMATION SECTION ===
Vendor: SEAGATE
Product: BL600MM0069
User Capacity: 600,127,266,816 bytes [600 GB]
Rotation Rate: 10000 rpm
Transport protocol: SAS (SPL-3)
...
# smartctl -i /dev/sda -d megaraid,1
=== START OF INFORMATION SECTION ===
Vendor: SEAGATE
Product: BL600MM0069
User Capacity: 600,127,266,816 bytes [600 GB]
Rotation Rate: 10000 rpm
Transport protocol: SAS (SPL-3)
...
nvme1n1
— незадействованное NVMe устройство, мы будем использовать его как временное хранилище дампа файловых систем.nvme0n1
— текущий системный диск.
Мы хотим перенести системный диск с nvme0n1
на sda
.
Команда vgs
показывает нам, что у нас есть LVM группа bercutvg
, старую группу мы переименуем, а новую создадим.
Загрузка с SystemResqueCD
Планируем ночные работы. Понадобится доступ к системному контроллеру сервера, чтобы попасть в его консоль и запустить с ISO-образа SystemResqueCD
. Во время старта сервера входим в меню загрузки и выбираем CD-ROM.
Загружаем сервер с SystemResqueCD
с опциями по умолчанию.
Создаем временное рабочее окружение
Если вы любите работать с GUI-окружением, после загрузки можно запустить графическую оболочку командой startx
, либо остаться работать в консольном интерфейсе.
Если вы планируете сложить дамп файловых систем в какую-то сетевую папку, запустите nmtui
и настройте сетевые интерфейсы: IP, MASK, GW. В нашем случае это не обязательно, т.к. временный дамп будет располагаться на внутреннем устройстве nvme1n1.
По умолчанию на системе SystemResqueCD
уже есть работающий sshd
. Если вам удобней работать через ssh-клиент, надо прорезать порт 22/tcp в firewall (либо отключить сервис iptables
), а также задать временный пароль для root.
Временный пароль root задаем в
passwd root
Чтобы настроить ssh в iptables, нужно добавить в середину файла/etc/iptables/iptables.rules правило:
-A INPUT -p tcp -s 192.168.1.100 --dport 22 -j ACCEPT
А затем перезапустить сервис:
systemctl restart iptables
В конфигурационной строке выше 192.168.1.100
— это адрес, с которого мы подключаемся во время работ.
Я использую ssh соединение. Это удобно — можно копировать команды в блокнот и обратно.
Создание дампа файловых систем
Дамп файловых систем мы будем делать с помощью утилиты fsarchiver
.
Запустим эту утилиту в режиме осмотра:
fsarchiver probe detailed
В ответ мы увидим подробную карту блочных устройств, названия LVM томов и UUID файловых систем.
Нам необходимо перенести файловые системы:
/dev/nvme0n1p1 | /boot |
/dev/nvme0n1p2 | /boot/efi |
/dev/mapper/bercutvg-root | / |
/dev/mapper/bercutvg-var | /var |
/dev/mapper/bercutvg-home | /home |
/dev/mapper/bercutvg-opt | /opt |
/dev/mapper/bercutvg-u01 | /u01 |
А также нужно не забыть пересоздать swap.
/dev/mapper/bercutvg-swap | swap |
Через команду pvs
или lsblk
определяем, что текущая VG bercutvg
размещена на /dev/nvme0n1p3
, а устройство nvme1n1
свободно.
Создаем временную файловую систему для размещения дампа файловых систем на nvme1n1
. Тут мы не будем тратить время на создание партиции, главное не затереть случайно nvme0n1
.
mkfs.ext4 /dev/nvme1n1
mkdir /mnt/dump
mount /dev/nvme1n1 /mnt/dump
Для подключения сетевых папок (если они вам нужны), альтернативно можно воспользоваться, например, такими командами:
#CIFS шара (ее нужно заранее создать)
mount -t cifs //192.168.1.100/dump /mnt/dump -o username=user,password=pas
#через SSH (небыстрый способ)
sshfs login@192.168.1.100:/path/to/dir /mnt/dump
Переходим в подключенную папку и создаем в ней дамп файловых систем.
cd /mnt/dump
#узнаем сколько потоков процессора есть в системе,
#чтобы указать это число в -j (но не больше чем 32)
grep -c processor /proc/cpuinfo
fsarchiver -j32 savefs /mnt/dump/image.fsa \
/dev/nvme0n1p1 \
/dev/nvme0n1p2 \
/dev/mapper/bercutvg-root \
/dev/mapper/bercutvg-var \
/dev/mapper/bercutvg-home \
/dev/mapper/bercutvg-opt \
/dev/mapper/bercutvg-u01
Можно добавить опцию -v
для режима «болтливости», однако я её не рекомендую — вы можете пропустить важное сообщение об ошибке, если вывод будет очень большим.
Команда создает дамп некоторое время, за растущим размером /mnt/dump/image.fsa
можно следить в соседней консоли.
Заглянуть в созданный архив можно с помощью опции archinfo:
fsarchiver archinfo /mnt/dump/image.fsa
Это полезно, если вы забыли в каком порядке перечисляли файловые системы для дампа.
Подготовка устройств для распаковки данных
Мы хотим разместить наши файловые системы на устройстве sda
. Сделаем его разметку.
Посмотрим разметку текущего системного диска /dev/nvme0n1
;
gdisk -l /dev/nvme0n1
И создадим похожую разметку (тут можно менять размеры устройств в соответствии с нашим планом, если нужно):
gdisk /dev/sda
#создание первого раздела, начинающегося на блоке 2048, размер 256МБ,
#тип ФС EF00 (EFI загрузка)
n
1
2048
+256M
EF00
#создание второго раздела, начинающегося на первом свободном блоке, размер 1024МБ,
#тип ФС 8300 (EXT)
n
2
+1024M
8300
#создание третьего раздела, начинающегося на первом свободном блоке,
#размером на все оставшиеся блоки, тип ФС 8E00 (LVM)
n
3
8E00
#записываем изменения на диск и подтверждаем
w
y
Прочитаем новую разметку с только что размеченного диска:
partprobe /dev/sda
gdisk -l /dev/sda
Теперь необходимо создать LVM-группу bercutvg
и тома на ней. Но имя bercutvg
у нас уже занято. Придется старую группу переименовать.
Менять имя VG на новом диске нежелательно, т.к. оно много где фигурирует и без дополнительных изменений система не загрузится.
vgrename bercutvg oldbercutvg
pvcreate /dev/sda3
vgcreate bercutvg /dev/sda3
Создадим с нужными размерами тома:
/dev/mapper/bercutvg-root | / | 25G |
/dev/mapper/bercutvg-var | /var | 10G |
/dev/mapper/bercutvg-home | /home | 20G |
/dev/mapper/bercutvg-opt | /opt | 10G |
/dev/mapper/bercutvg-swap | swap | 17G |
/dev/mapper/bercutvg-u01 | /u01 | все остальные блоки — 100%FREE |
Выполним следующие команды:
lvcreate -n root -L 25G bercutvg
lvcreate -n var -L 10G bercutvg
lvcreate -n home -L 20G bercutvg
lvcreate -n opt -L 10G bercutvg
lvcreate -n swap -L 17G bercutvg
lvcreate -n u01 -l 100%FREE bercutvg
Наполняем устройства данными
Самое простое — создать новый swap.
mkswap /dev/mapper/bercutvg-swap
Далее нам понадобится созданный ранее дамп. Необходимо вспомнить в каком порядке запаковывались файловые системы (или подсмотреть этот порядок через fsarchiver archinfo /mnt/dump/image.fsa
).
Для EFI system partition (/boot/efi
) и ФС загрузчика Grub (/boot
) необходимо при восстановлении сделать новые UUID.
fsarchiver restfs -j32 /mnt/dump/image.fsa \
id=0,dest=/dev/sda1,uuid=$(uuidgen) \
id=1,dest=/dev/sda2,uuid=$(uuidgen) \
id=2,dest=/dev/mapper/bercutvg-root \
id=3,dest=/dev/mapper/bercutvg-var \
id=4,dest=/dev/mapper/bercutvg-home \
id=5,dest=/dev/mapper/bercutvg-opt \
id=6,dest=/dev/mapper/bercutvg-u01
Подключаем развернутые данные:
mkdir /mnt/root
mount /dev/mapper/bercutvg-root /mnt/root
mount /dev/mapper/bercutvg-var /mnt/root/var
mount /dev/mapper/bercutvg-home /mnt/root/home
mount /dev/mapper/bercutvg-opt /mnt/root/opt
mount /dev/mapper/bercutvg-u01 /mnt/root/u01
mount /dev/sda2 /mnt/root/boot
mount /dev/sda1 /mnt/root/boot/efi
mount -o bind /proc /mnt/root/proc
mount -o bind /sys /mnt/root/sys
mount -o bind /sys/firmware/efi/efivars /mnt/root/sys/firmware/efi/efivars
mount -o bind /dev /mnt/root/dev
chroot /mnt/root /bin/bash
Монтирование efivars (строка 11 в командах выше) выполнено для того, чтобы у нас заработала команда efibootmgr
, которую мы будем использовать далее.
Взять новые тэги от ФС /boot/efi
и /boot
, размещенной на /dev/sda1
и на /dev/sda2
и заменить их в /etc/fstab:
blkid -s UUID -o value /dev/sda1
blkid -s UUID -o value /dev/sda2
vim /etc/fstab
Сгенерировать новый конфигурационный файл для grub:
grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
Вот так можно посмотреть информацию о том, какое ядро запустит grub2 и что он найдет:
grubby --default-kernel
grubby --default-index
grubby --info=ALL
Далее необходимо проверить последовательность загрузки efi:
efibootmgr -v
В моем случае вариант по умолчанию, это
Boot0003* Oracle Linux HD(1,GPT,2be2b68a-ab31-48a4-bd15-526cf766174b,0x800,0x80000)/File(\EFI\redhat\shimx64.efi)
Что неправильно, т.к. 2be2b68a-ab31-48a4-bd15-526cf766174b
— это старое NVMe устройство:
blkid | grep 2be2b68a-ab31-48a4-bd15-526cf766174b
/dev/nvme0n1p1: SEC_TYPE="msdos" UUID="F6D4-B2BB" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="2be2b68a-ab31-48a4-bd15-526cf766174b"
Старый вариант загрузки можно было бы удалить командой
efibootmgr -Bb 0003; # пока не запускаем её!
Но оставим это на момент, когда убедимся, что все прошло успешно и не требуется откатываться.
Добавим еще один вариант загрузки с устройства (-d) /dev/sda
c первой партиции (-p):
efibootmgr -c -L 'Oracle Linux 8 sas raid' -d /dev/sda -p 1 -l '\EFI\redhat\shimx64.efi'
Вновь проверяем BootOrder в efibootmgr -v
. Если он нас не устраивает, поменять можно так:
efibootmgr -o 0006,0001,0002,0000,0004,0005,0003
Выходим из chroot, отмонтируем все файловые системы в обратном порядке и перезагружаемся:
exit
for fs in /mnt/root/dev \
/mnt/root/sys/firmware/efi/efivars \
/mnt/root/sys \
/mnt/root/proc \
/mnt/root/boot/efi \
/mnt/root/boot \
/mnt/root/var \
/mnt/root; do umount $fs; done
reboot
Проверка и очистка
Проверяем, что наша система загрузилась с правильного устройства:
mount | egrep 'boot|root'
pvs
Ищем в вариантах загрузки старую запись с NVMe 2be2b68a-ab31-48a4-bd15-526cf766174b
и удаляем её:
# efibootmgr -v # blkid | grep 2be2b68a-ab31-48a4-bd15-526cf766174b
/dev/nvme0n1p1: SEC_TYPE="msdos" UUID="F6D4-B2BB" BLOCK_SIZE="512" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="2be2b68a-ab31-48a4-bd15-526cf766174b"
# efibootmgr -Bb 0003
Проверяем, что у нас сохранился правильный BootOrder.
Позже (через пару дней после основных работ) мы удалим ненужную LVM группу oldbercutvg примерно так:
lvchange -an oldbercutvg/home
lvchange -an oldbercutvg/opt
lvchange -an oldbercutvg/root
lvchange -an oldbercutvg/swap
lvchange -an oldbercutvg/u01
lvchange -an oldbercutvg/var
lvremove oldbercutvg/home
lvremove oldbercutvg/opt
lvremove oldbercutvg/root
lvremove oldbercutvg/swap
lvremove oldbercutvg/u01
lvremove oldbercutvg/var
vgremove oldbercutvg
pvremove /dev/nvme0n1p3
pvs
А затем почистим таблицы разделов, например, так:
# gdisk /dev/nvme0n1
p
d
3
d
2
d
w
Y
partprobe /dev/nvme0n1
Послесловие
/dev/nvme0n1
и /dev/nvme1n1
теперь логически свободны, на /dev/nvme1n1
еще лежит дамп с бэкапом, пусть полежит еще немного.
Теперь сделать контрольную перезагрузку и запускать все сервисы. В моем случае — переходим к следующей задаче по ночным работам. Мы справились.
P.S. Да, можно было переносить данные файловых систем напрямую и обойтись без создания промежуточного дампа с fsarchiver
(как и без самой этой утилиты), но статья знакомит читателей с таким инструментом, и он может давать некоторые дополнительные возможности. Например, мы могли бы переносить данные на этот же системный диск с его переразметкой и у нас сохранился полный «холодный» бэкап системы на /dev/nvme1n1
. Сам сервер резвый, дамп создавался и разворачивался за несколько минут. При планировании ваших работ, конечно, следует учитывать время создания дампа.