Booting Linux virtual machines from a disk without partitions

When creating root disks of virtual machines, usually, using fdisk / gdisk, a partition table is created on them with a single partition to host the operating system on it. This causes some trouble on the hypervisor side, for example:

  • When mounting a disk, you need to remember that it is not the block device itself that is mounted, but the partition on it. The problem is aggravated if an lvm disk is used - the kernel does not see the partitions on it without using persuasion tools in the form of kpartx.
  • Restoring a partition requires not only a backup of the file system, but also a tambourine / copy of the first track.
  • Changing the size of such a disk requires another tambourine of unnecessary operation to resize a partition on a disk

You can get rid of the partition table by directly loading the kernel, but this method is not sinless. The hypervisor should have on its side images of the virtual machine cores that need to be kept up-to-date when updating guest OSs.
I want to tell you about an alternative option to boot from a disk without partitions using EFI and GRUB2.

Everything will happen in libvirt, qemu, kvm, Ubuntu.


The following describes loading a virtual machine in EFI mode from a virtual USB disk into which the directory with the EFI version of grub configured to load its menu from a disk without partitions is displayed. Phew ... Plus a couple of rakes on the way.
setting up the libvirt domain that will be discussed


And here is EFI? Downloading in EFI mode allows us to place grub-efi on a regular file system and does not require MBR. First of all, we need a BIOS for qemu with EFI support. It is called OVMF . Of all the downloaded wealth, only OVMF.fd is needed (hereinafter, it is assumed that it lies / opt / ovmf /). OVMF is connected in the os section of the domain with the loader tag:



Now our virtual machine can load in a modern way. This method needs a special EFI section where the bootloader will lie. Prepare 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 

As you can see, there are some features in the placement and name of the bootloader being created. The fact is that, by default, the OVMF-BIOS will try to load the /efi/boot/bootx64.efi file from the EFI partition. We will use this and slip grub-efi there, which will take its config from the second disk in the system from / boot / grub /. Later this bootloader can be used for many virtual machines, connecting it to a readonly disk and provided that the disk with / boot / grub is the second in the system.

EFI partition and rake

So, now we need a bootable EFI-partition inside the guest on which we put the bootloader. You can create a small disk, place the GPT partition table and the EFI partition with the bootloader. If you connect this drive in readonly mode, you can use one such drive for several virtual machines. But there is a rake. If we place it on the virtio bus, like any normal disk, then at boot we will be surprised by an EFI-shell instead of a loaded system. Moreover, this shell will be fine to see our section and load the bootloader with the command "bootx64.efi", but without hands it will not be possible to focus. Here there guys discuss in detail and find out why, and agree that this is a bug OVMF.
Therefore, you will have to connect the disk to the ide bus. This is a working option, but drives cannot be readonly. It will be necessary to observe hygiene regarding this disk inside the guest and (possibly) say goodbye to the ideas of starting several machines with one loader.
There is a better option.

Reflecting a directory to disk in Qemu

Qemu can create virtual disks from directories. It looks like this:

In this case, a disk with a partition table in MBR, a partition in FAT16 with the contents of the directory on it will appear in the guest system. This drive has a couple of important features for us - when it is connected, the readonly attribute is required and the partition on it is of type 0x06, not 0xEF, as required by EFI. Fortunately, you can boot from this section if you hang the disk on the usb bus. In this case, the OVMF-BIOS will search for the bootloader on the regular FAT partition.

And here there is another rake. You get them on the forehead if apparmor works, and it works by default in Ubuntu.
For each virtual machine, libvirt creates an apparmor profile, which gives it access only to the resources specified in its config. Wrong rules are created for a disk made from a directory, so you should add the following lines to /etc/apparmor.d/abstractions/libvirt-qemu:

/var/spool/efi-grub/ r,
/var/spool/efi-grub/** r,

Important notes

Now you can create virtual machines by creating disks directly on lvm volumes, easily and safely change their size, not bother installing the bootloader on new machines deployed using debootstrap, make backup copies of root systems using dump and restore them with simple restore.

However, there are some limitations. The disks on the usb and ide buses OVMF will always place the disks on the virtio before, and we hardcoded in grub to load the menus and modules from the second disk (the first one is the disk with the bootloader):

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 

We did this with the -p option ((hd1) / boot / grub / ". Here (hd1) just points to the second disk (the first, respectively (hd0)). Keep this in mind if you add disks other than virtio to the machine.

Also, you need to remember that grub can dynamically load additional modules from the / boot / grub root partition, and since guest systems can be different, the versions of these modules may not be compatible with the bootloader from the EFI partition. You can get rid of this by pushing the necessary modules statically by adding their name to the above line.

Also popular now: