При создании корневых дисков виртуальных машин, обычно, на них с помощью fdisk/gdisk создается таблица разделов с единственным разделом для размещения на нем операционной системы. Это порождает некоторые неприятности на стороне гипервизора, например:
- При монтировании диска нужно помнить, что монтируется не само блочное устройство, а раздел на нем. Проблема усугубляется если используется lvm-диск — ядро не видит разделы на нем без применения средств убеждения в виде kpartx.
- Для восстановления раздела требуется не только резервная копия файловой системы, но и бубен/копия первого трека.
- Изменение размера такого диска требует
еще одного бубналишней операции по изменению размера раздела на диске
Избавиться от таблицы разделов можно прямой загрузкой ядра, но этот метод не безгрешен. Гипервизор должен иметь на своей стороне образы ядер виртуальных машин, которые нужно поддерживать актуальными при обновлениях гостевых ОС.
Хочу рассказать вам об альтернативном варианте загрузки с диска без разделов с помощью EFI и GRUB2.
Все будет происходить в libvirt, qemu, kvm, Ubuntu.
Краткое содержание
Ниже описана загрузка виртуальной машины в EFI-режиме с виртуального USB-диска в который отражена директория с EFI версией grub сконфигурированного на загрузку своего меню с диска без разделов. Уф-ф… Плюс пара граблей на пути.
<domain type='kvm' id='23'>
<name>aster</name>
<uuid>d4ee890e-658c-9ff3-6242-9569be3aa154</uuid>
<memory unit='KiB'>524288</memory>
<currentMemory unit='KiB'>524288</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-1.5'>hvm</type>
<loader>/opt/ovmf/OVMF.fd</loader>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/kvm-spice</emulator>
<disk type='block' device='disk'>
<driver name='qemu' type='raw'/>
<source dev='/dev/mapper/ssd-aster--root'/&qt;
<target dev='vda' bus='virtio'/>
<alias name='virtio-disk0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</disk>
<disk type='dir' device='disk'>
<driver name='qemu'/>
<source dir='/var/spool/efi-grub/'/&qt;
<target dev='sda' bus='usb'/>
<readonly/>
<alias name='usb-disk0'/>
</disk>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='usb' index='0'>
<alias name='usb0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
</memballoon>
</devices>
</domain>
EFI BIOS
Причем здесь EFI? Загрузка в EFI-режиме позволяет нам разместить grub-efi в обычной файловой системе и не требует наличия MBR. Прежде всего нам нужен BIOS для qemu с поддержкой EFI. Называется он OVMF. Из всего скачанного богатства нужен только OVMF.fd (далее подразумевается, что он лежит /opt/ovmf/). OVMF подключается в разделе os домена тегом loader:
<os>
<type arch='x86_64' machine='pc-i440fx-1.5'>hvm</type>
<loader>/opt/ovmf/OVMF.fd</loader>
<boot dev='hd'/>
</os>
Загрузчик
Теперь наша виртуалка умеет грузиться современным способом. Этому способу нужен специальный раздел EFI, где будет лежать загрузчик. Приготовим grub-efi:
mkdir -p /var/spool/efi-grub/efi/boot/
grub-mkimage -O x86_64-efi -d /usr/lib/grub/x86_64-efi/ -o /var/spool/efi-grub/efi/boot/bootx64.efi -p "(hd1)/boot/grub/" part_gpt part_msdos fat ext2 normal chain boot configfile linux multiboot efi_gop efi_uga
Как видите, есть некоторые особенности в месте размещения и имени создаваемого загрузчика. Дело в том, что по умолчанию OVMF-BIOS будет пытаться загрузить с EFI-раздела файл /efi/boot/bootx64.efi. Мы воспользуемся этим и подсунем туда grub-efi, который возьмет свой конфиг со второго диска в системе из /boot/grub/. В последствии этот загрузчик можно использовать для множества виртуальных машин, подключая его на readonly диске и при условии, что диск с /boot/grub будет вторым в системе.
EFI-раздел и грабли
Итак, теперь нам нужен загрузочный EFI-раздел внутри гостя на который мы положим загрузчик. Можно создать небольшой диск, разместить таблицу разделов GPT и EFI-раздел с загрузчиком. Если этот диск подключать в режиме readonly, то можно будет использовать один такой диск для нескольких виртуальных машин. Но тут есть грабли. Если мы расположим его на шине virtio, как любой нормальный диск, то при загрузке нас ожидает сюрприз в виде EFI-shell вместо загруженной системы. Причем этот шелл будет прекрасно видеть наш раздел и грузить загрузчик по команде «bootx64.efi», но без рук фокус сделать не удастся. Вот тут ребята подробно обсуждают и выясняют почему и сходятся, что это баг OVMF.
Поэтому придется подключать диск на шину ide. Это рабочий вариант, но диски не могут быть readonly. Потребуется соблюдать гигиену относительно этого диска внутри гостя и (возможно) распрощаться с идей запуска нескольких машин одним загрузчиком.
Есть вариант лучше.
Отражение директории в диск в Qemu
Qemu умеет создавать виртуальные диски из директорий. Это выглядит вот так:
<disk type='dir' device='disk'>
<source dir='/var/spool/efi-grub/'/&qt;
<target dev='sda' bus='usb'/>
<readonly/>
</disk>
При этом в гостевой системе появится диск с таблицей разделов в MBR, разделом в FAT16 с содержимым директории на нем. У этого диска есть пара важных для нас особенностей — при его подключении необходим атрибут readonly и раздел на нем имеет тип 0x06, а не 0xEF, как того требует EFI. К счастью, загрузиться с такого раздела можно, если повесить диск на шину usb. В этом случае OVMF-BIOS будет искать загрузчик на обычном разделе FAT.
И тут присутствуют еще одни грабли. В лоб ими получаешь, если работает apparmor, а он по умолчанию в Ubuntu работает.
Для каждой виртуалки libvirt создает apparmor-профиль, который дает ей доступ только к указанным в ее конфиге ресурсам. Для диска сделанного из директории правила создаются неправильные, поэтому стоит добавить в /etc/apparmor.d/abstractions/libvirt-qemu строчки:
/var/spool/efi-grub/ r,
/var/spool/efi-grub/** r,
Важные замечания
Теперь можно плодить виртуалки создавая диски напрямую на lvm-томах, легко и безопасно менять их размер, не утруждать себя установкой загрузчика на новых машинах развернутых с помощью debootstrap, делать резервные копии корневых систем помощью dump и восстанавливать их простым restore.
Однако, есть некоторые ограничения. Диски на шинах usb и ide OVMF всегда расположит ранее дисков на virtio, а мы захардкодили в grub на загрузку меню и модулей со второго диска (первым у нас стоит диск с загрузчиком):
grub-mkimage -O x86_64-efi -d /usr/lib/grub/x86_64-efi/ -o /var/spool/efi-grub/efi/boot/bootx64.efi -p "(hd1)/boot/grub/" part_gpt part_msdos fat ext2 normal chain boot configfile linux multiboot efi_gop efi_uga
Сделали мы это параметром -p "(hd1)/boot/grub/". Здесь (hd1) как раз и указывает на второй диск (первый, соответственно (hd0)). Учитывайте это, если будете добавлять в машину диски отличные от virtio.
Так же, нужно помнить, что grub может динамически подгружать дополнительные модули из /boot/grub корневого раздела, а так как гостевые системы могут быть разные, то версии этих модулей могут оказаться несовместимыми с загрузчиком из EFI-раздела. Избавится от этого можно понапихав необходимые модули статически путем добавления их названия в вышеприведенную строку.
Автор: BDenis