Найти в Дзене
Slonik

Создание виртуальных машин с помощью Qemu KVM

Оглавление
Логотип Qemu и Linux
Логотип Qemu и Linux

В статье опишу мой опыт про подготовку и запуск виртуальных машин (ВМ) с помощью открытых и бесплатных средств виртуализации в Linux.

В качестве среды для выполнения виртуальных машин я буду использовать Fedora Linux 36, аналогично ВМ можно запускать в Centos 8 или Alt Linux p10.

Для запуска ВМ понадобятся пакеты:

  • qemu-kvm (qemu-system-x86) - эмулятор
  • qemu-img - средство создания и управления виртуальными дисками
  • edk2-ovmf - образы прошивки UEFI

Здесь я рассмотрю три типа виртуальных машин:

  1. Виртуальная машина с BIOS
  2. Виртуальная машина с UEFI
  3. Кластер виртуальных машин с общим диском

Подготовка виртуальной сети

В Qemu я использую два режима подключения сети в гостевую ВМ - user networking и с использованием устройств tap.

Первый режим проще, не требует root прав, но у него меньше производительность, в таком режиме не работает протокол ICMP и он не позволяет обратится к ВМ из физической сети.

Второй режим предоставляет большее быстродействие при работе с сетью, его можно гибко настраивать, но он требует root права.

Пользовательская виртуальная сеть

Простейшую ВМ с пользовательской сетью можно запустить так:

qemu-system-x86_64 \
-m 1G \
-device virtio-net-pci,netdev=lan \
-netdev user,id=lan \
-drive file=/tmp/livecd.iso,media=cdrom

Ключевым параметром, который включает пользовательский режим сети это -netdev user.

Такая команда запустит LiveCD внутри ВМ c 1024 МБ ОЗУ, в качестве сетевой карты будет использовано устройство Virtio.

Виртуальная машина с пользовательской сетью, System Rescue CD 9
Виртуальная машина с пользовательской сетью, System Rescue CD 9

Виртуальная машина автоматически получит ip-адрес из подсети 10.0.2.0/24, шлюз - 10.0.2.2, dns-сервер - 10.0.2.3.

К физическому хосту можно обратиться по адресу 10.0.2.2.

ICMP пакеты через такой тип сети не проходят.

Виртуальная сеть с использованием tap-устройств

В данном режиме при запуске ВМ в физической системе будет создано виртуальное сетевое tap-устройство, что потребует root прав.

sudo qemu-system-x86_64 \
-m 1G \
-device virtio-net-pci,netdev=lan \
-netdev tap,id=lan,ifname=tap0 \
-drive file=/tmp/livecd.iso,media=cdrom

QEMU не следует запускать от имени root. Опция -run-with user=... (в ранних версиях -runas=...) позволяет изменить идентификатор пользователя ВМ c root на пользователя, запускающего команду, непосредственно перед запуском виртуальной машины, пользователя можно указать как числовой идентификатор uid:gid. Для простоты я её не указываю.

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

Предположим, что физический хост подключен к сети через адаптер enp3s0 и имеет адрес 192.168.1.50/24, шлюз 192.168.1.1.

Удалим все сетевые настройки у адаптера enp3s0 и создадим сетевой мост с помощью Network Manager:

root# nmcli connection show
root# nmcli connection delete <имя подключения enp3s0>
root# nmcli connection add type bridge ifname br0 con-name bridge bridge.stp false ipv4.method manual ipv4.addresses "192.168.1.50/24" ipv4.gateway 192.168.1.1 ipv6.method disabled

Добавляем физический адаптер хоста в сетевой мост:

nmcli con add type bridge-slave ifname enp3s0 con-name nicHost master br0

Настраиваем скрипт добавления виртуального сетевого tap-устройства к мосту - /etc/qemu-ifup:

#!/bin/sh
/usr/bin/sudo /usr/sbin/brctl addif br0 $1
/usr/bin/sudo /usr/sbin/ip link set dev $1 up

Скрипт удаления tap-устройства из моста /etc/qemu-ifdown:

#!/bin/sh
/usr/bin/sudo /usr/sbin/ip link set dev $1 down
/usr/bin/sudo /usr/sbin/brctl delif br0 $1

В скриптах $1 указывает на имя виртуального сетевого адаптера (tap0, tap1 и т.п.).

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

sudo qemu-system-x86_64 \
-m 1G \
-device virtio-net-pci,netdev=lan \
-netdev tap,id=lan,ifname=tap0,script=/etc/qemu-ifup,downscript=/etc/qemu-ifdown \
-drive file=/tmp/livecd.iso,media=cdrom

Запускаем виртуальную машину и проверяем сеть.

Сетевые настройки ВМ с tap подключением, System Rescue+ 9
Сетевые настройки ВМ с tap подключением, System Rescue+ 9

ВМ получила адрес от DHCP сервера локальной сети.

Проверяем состав сетевого моста на физическом хосте с помощью команды brctl show:

-4

В хост-системе появилось устройство tap0, и оно автоматически добавилось в сетевой мост.

По-умолчанию, все виртуальные сетевые адаптеры получают MAC адрес 52:54:00:12:34:56, если потребуется запустить несколько виртуальных машин на хосте, то необходимо указать разные адреса с помощью параметра mac:

sudo qemu-system-x86_64 \
-m 1G \
-device virtio-net-pci,mac=52:54:00:00:00:01,netdev=lan \
-netdev tap,id=lan,ifname=tap0,script=/etc/qemu-ifup,downscript=/etc/qemu-ifdown \
-drive file=/tmp/livecd.iso,media=cdrom

Подготовка виртуальных дисков

Ранее мы запускали ВМ с LiveCD. Для того чтобы установить операционную систему в виртуальную машину нам понадобится создать виртуальные диски для хранения данных.

Создание виртуальных дисков

qemu-img create -f qcow2 vmpath/disk.qcow2 20G

Данная команда создаст "тонкий" диск в формате qcow2, объёмом 20 Гб. Файл диска будет увеличиваться в размере по мере записи на виртуальный диск.

qemu-img create -f qcow2 vmpath/disk.qcow2 -o compression_type=zstd,cluster_size=1M,preallocation=falloc 30G

Данная команда создаст диск в формате qcow2 объёмом 30 Гб, с сжатием zstd и размером кластера 1 МБ. Файл диска займёт все указанное место сразу при создании. Сжатые сектора доступны только для чтения. Это означает, что если сжатый сектор перезаписывается, то он перезаписывается как несжатые данные.

Если потребуется создать кластерный диск, то необходимо для него всё место выделить заранее и использовать тип диска raw:

qemu-img create -f raw -o preallocation=falloc shared/shared.raw 10G

Использование снимков состояния дисков

Формат дисков qcow2 позволяет создавать и удалять внутри диска снимки состояний (снапшоты):

создание снимка:

qemu-img snapshot -c snapshotName disk.qcow2

список снимков:

qemu-img snapshot -l disk.qcow2

применить снимок (вернуть диск в состояние на момент снимка):

qemu-img snapshot -a snapshotName disk.qcow2

удалить снимок:

qemu-img snapshot -d snapshotName disk.qcow2

Вся работа со снимками будет производится внутри одного образа, что может потребовать времени.

Другим способом создания снимков состояний является создание промежуточного файла - при создании такого файла вся запись будет вестись в него вместо базового образа (backing file):

qemu-img create -f qcow2 -F qcow2 -b disk.qcow2 diffDisk.qcow2

В основной диск (backing file) не будут вносится какие-либо изменения, пока не будет применена команда commit в мониторе qemu (или команда qemu-img commit).

При запуске ВМ указываем в качестве диска промежуточный файл:

qemu-system-x86_64 -m 1G -drive file=diffDisk.qcow2,media=disk

Чтобы откатить снапшот, достаточно перенастроить запуск на изначальный образ диска (backing file) и удалить снапшот.

qemu-system-x86_64 -m 1G -drive file=disk.qcow2 ,media=disk
rm diffDisk.qcow2

Изменение первоначального образа приведёт к повреждению снапшота, поэтому после запуска ВМ с оригинальным диском все снапшоты станут бесполезными.

Изменение размера виртуального диска

Для увеличения размера диска disk.qcow2, например, на 10 ГБ необходимо воспользоваться командой:

qemu-img resize disk.qcow2 +10G

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

qemu-img resize --shrink disk.qcow2 -10G

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

Подключение разделов виртуального диска к хост-системе

Если возникнет необходимость обратиться к файлам на виртуальном диске напрямую с хост-системы, то необходимо сначала смонтировать образ. Для того, чтобы смонтировать разделы виртуального диска к хост-системе необходимо воспользоваться утилитой qemu-nbd.

1. Загружаем модуль ядра nbd

root# modprobe nbd max_part=8

2. Подключаем образ виртуального диска как сетевое блочное устройство:

root# qemu-nbd --connect=/dev/nbd0 vmpath/disk.qcow2

3. Выполняем нужные нам операции с устройством:

root# fdisk /dev/nbd0 -l
root# mount /dev/nbd0p1 /mnt/volume/
root# umount /dev/nbd0p1

4. Отключаем устройство

root# qemu-nbd --disconnect /dev/nbd0
root# rmmod nbd

Запуск виртуальной машины с BIOS в Qemu KVM

По-умолчанию Qemu KVM запускает виртуальные машины с BIOS, поэтому особых настроек не требуется.

Готовим диск для гостевой системы:

qemu-img create -f qcow2 /mnt/virtual/bios/bios.qcow2 20G

Запускаем ВМ:

sudo qemu-system-x86_64 \
-boot menu=on \
-run-with user=$USER \
-machine q35 \
-smbios type=1,manufacturer=Qemu,product=KVM,version=1 \
-cpu host \
-accel kvm \
-smp cpus=2,sockets=1,cores=2 \
-m 1G \
-k en-us \
-vga virtio \
-usb -device usb-tablet \
-rtc base=utc \
-netdev tap,id=lan,ifname=tap0,script=/etc/qemu-ifup,downscript=/etc/qemu-ifdown \
-device virtio-net-pci,mac=52:54:00:00:00:01,netdev=lan \
-drive file=/mnt/virtual/bios/bios.qcow2,if=virtio,media=disk,index=0 \
-drive file=/mnt/iso/linuxOS.iso,media=cdrom,index=1

Параметры:

  • -boot menu=on - включает загрузочное меню, для выбора варианта загрузки
  • -run-with user=... (в ранних версиях -runas=...) - QEMU не следует запускать от имени root. Опция изменит идентификатор пользователя ВМ c root на пользователя, запускающего команду, непосредственно перед запуском виртуальной машины, пользователя можно указать как числовой идентификатор uid:gid, переменная окружения $USER - указывает на текущего пользователя
  • -machine - свойства чипсета, основные это pc (совместимый со многими ОС, поддерживает подключние дисковых устройств через шину IDE) и q35 (поддерживает проброс PCI устройств в ВМ)
  • -smbios - описание BIOS
  • -cpu - тип эмулируемого ЦП, host указывает использовать как в физической системе
  • -accel - тип ускорения виртуализации
  • -smp - описываем кол-во виртуальных ядер ЦП и их распределение по сокетам
  • -m - объём ОЗУ ВМ
  • -k - раскладка клавиатуры консоли
  • -vga - тип виртуального видеоадаптера: основные это std, qxl ,virtio, none.
    std - позволяет получить разрешение до 2560 x 1600 пикселей, не требуя гостевых драйверов. Это значение используется по умолчанию начиная с QEMU 2.2.
    qxl - это паравиртуальный графический драйвер с поддержкой 2D, при подключении к экрану по протоколу Spice желательно использовать его.
    virtio является паравиртуальным графическим драйвером 3D, чтобы включить 3D-ускорение в гостевой системе, выберите этот vga с помощью -device virtio-vga-gl и включите контекст opengl в устройстве отображения с помощью -display sdl,gl=on или -display gtk,gl=on для вывода на дисплей sdl и gtk соответственно.
  • -usb -device usb-tablet - позволяет курсору мыши свободно перемещаться между ВМ и хостом, не требуя нажатия комбинации клавиш для освобождения (требует работы QEMU guest agent внутри ВМ)
  • -rtc base - настройки часов RTC: ОС Linux как правило ожидает, кто часы RTC настроены на часовой пояс UTC (-rtc base=utc), а ОС MS Windows, что - на местный часовой пояс (-rtc base=localtime)
  • -netdev/device и drive - описание сетевой карты и виртуальных дисков

Загружаемся с CD-ROM и устанавливаем ОС.

Если вам необходимо заменить дискеты или компакт-диски в процессе установки, вы можете использовать QEMU machine monitor (нажмите Ctrl+Alt+2 в окне виртуальной машины), чтобы удалить и подключить устройства хранения к виртуальной машине. Введите info block, чтобы увидеть блочные устройства, и используйте change команду для замены устройства. Нажмите Ctrl+Alt+1, чтобы вернуться к виртуальной машине.

Запуск виртуальной машины с UEFI в Qemu KVM

OVMF - это проект TianoCore, обеспечивающий поддержку UEFI для виртуальных машин. Его можно установить с помощью пакета edk2-ovmf.

Для запуска виртуальной машины с UEFI нам потребуются прошивки из пакета edk2-ovmf.

Для правильной работы UEFI требуется доступная для записи память, поэтому небходимо сэмулировать системную флэш-память ПК (pflash).

Используем /usr/share/edk2/ovmf/OVMF_CODE.fd (код UEFI) в качестве первого диска pflash, доступного только для чтения, а /usr/share/edk2/ovmf/OVMF_VARS.fd (переменные NVRAM) сделаем доступным для записи и используем в качестве второго диска.

Готовим виртуальный диск для гостевой системы:

qemu-img create -f qcow2 /mnt/virtual/uefi/uefi.qcow2 20G

Из пакета edk2-ovmf копируем образы UEFI и NVRAM:

cp /usr/share/edk2/ovmf/OVMF_CODE.fd /mnt/virtual/uefi
cp /usr/share/edk2/ovmf/OVMF_VARS.fd /mnt/virtual/uefi

Запускаем ВМ:

sudo qemu-system-x86_64 \
-run-with user=$USER \
-machine q35 \
-smbios type=1,manufacturer=Qemu,product=KVM,version=1 \
-cpu host \
-accel kvm \
-smp cpus=4,sockets=1,cores=4 \
-m 4G \
-k en-us \
-vga virtio \
-usb -device usb-tablet \
-device virtio-net-pci,mac=52:54:00:00:00:02,netdev=lan \
-netdev tap,id=lan,ifname=tap1,script=/etc/qemu-ifup,downscript=/etc/qemu-ifdown \
-drive if=pflash,format=raw,readonly=on,file=/mnt/virtual/uefi/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=/mnt/virtual/uefi/OVMF_VARS.fd \
-drive file=/mnt/virtual/uefi/uefi.qcow2,if=virtio,media=disk \
-drive file=/mnt/iso/linuxOS.iso,media=cdrom

Подключаем прошивку UEFI для чтения с помощью drive if=pflash с указанием readonly=on, а также файл для хранения NVRAM, но уже в режиме записи.

Загружаемся с CD-ROM и устанавливаем ОС.

Примечание: параметр -boot menu=on в режиме загрузке UEFI не учитывается

Запуск виртуальных машин с UEFI с общим кластерным диском в Qemu KVM

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

Подключать диски будем с помощью устройства -device virtio-blk-pci, оно позволяет включить совместную запись на диск с нескольких виртуальных машин.

Готовим диски для гостевых систем:

qemu-img create -f qcow2 /mnt/virtual/cluster/vm1-system.qcow2 20G
qemu-img create -f qcow2 /mnt/virtual/cluster/vm2-system.qcow2 20G

Для кластерного диска используем формат raw и заранее резервируем место.

qemu-img create -f raw -o preallocation=falloc /mnt/virtual/cluster/shared.raw 10G

Копируем образы прошивки UEFI и NVRAM:

cp /usr/share/edk2/ovmf/OVMF_CODE.fd /mnt/virtual/cluster
cp /usr/share/edk2/ovmf/OVMF_VARS.fd /mnt/virtual/cluster/OVMF_VARS_VM1.fd
cp /usr/share/edk2/ovmf/OVMF_VARS.fd /mnt/virtual/cluster/OVMF_VARS_VM2.fd

Запускаем ВМ 1:

sudo qemu-system-x86_64 \
-run-with user=$USER \
-machine q35 \
-smbios type=1,manufacturer=Qemu,product=KVM,version=1 \
-cpu host \
-accel kvm \
-smp cpus=2,sockets=1,cores=2 \
-m 2G \
-k en-us \
-vga virtio \
-usb -device usb-tablet \
-device virtio-net-pci,netdev=lan,mac=52:54:00:00:00:11 \
-netdev tap,id=lan,ifname=tap11 \
-drive if=pflash,format=raw,readonly=on,file=/mnt/virtual/cluster/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=/mnt/virtual/cluster/OVMF_VARS_VM1.fd \
-device virtio-blk-pci,drive=drive0,share-rw=off \
-drive file=/mnt/virtual/cluster/vm1-system.qcow2,id=drive0,format=qcow2,if=none,cache=writeback \
-device virtio-blk-pci,drive=drive1,share-rw=on \
-drive file=/mnt/virtual/cluster/shared.raw,id=drive1,format=raw,if=none,cache=writeback \
-drive file=/mnt/iso/linuxOS.iso,media=cdrom

Запускаем ВМ 2:

sudo qemu-system-x86_64 \
-run-with user=$USER \
-machine q35 \
-smbios type=1,manufacturer=Qemu,product=KVM,version=1 \
-cpu host \
-accel kvm \
-smp cpus=2,sockets=1,cores=2 \
-m 2G \
-k en-us \
-vga virtio \
-usb -device usb-tablet \
-device virtio-net-pci,netdev=lan,mac=52:54:00:00:00:12 \
-netdev tap,id=lan,ifname=tap12 \
-drive if=pflash,format=raw,readonly=on,file=/mnt/virtual/cluster/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=/mnt/virtual/cluster/OVMF_VARS_VM2.fd \
-device virtio-blk-pci,drive=drive0,share-rw=off \
-drive file=/mnt/virtual/cluster/vm2-system.qcow2,id=drive0,format=qcow2,if=none,cache=writeback \
-device virtio-blk-pci,drive=drive1,share-rw=on \
-drive file=/mnt/virtual/cluster/shared.raw,id=drive1,format=raw,if=none,cache=writeback \
-drive file=/mnt/iso/linuxOS.iso,media=cdrom

Далее нужно установить ОС внутри ВМ и настроить работу с кластерным диском.

Пример развёртывания кластерной файловой системой GFS2 приведён в моём руководстве - "Использование кластерной файловой системы GFS2 в ОС GNU Debian 12"

Пример реализации отказоустойчивой СУБД Postgresql с кластерной файловой системой OCFS2 приведён другом руководстве - "Настройка работы СУБД PostgreSQL 13 под управлением менеджера ресурсов высокой доступности Pacemaker/Corosync в Debian 11"

Повышение производительности виртуальной машины Qemu

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

  1. Включите параметр accel=kvm для полной виртуализации.
  2. Используйте параметр -cpu host, чтобы заставить QEMU эмулировать процессор хоста, а не общий процессор.
    Специально для ВМ с Windows включите функции Hyper-V:
    -cpu host,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time.
  3. Если хост-машина имеет несколько ядер, назначьте ВМ больше ядер, используя параметр -smp.
  4. Убедитесь, что вы выделили виртуальной машине достаточно памяти. По умолчанию QEMU выделяет каждой виртуальной машине только 128 Мб памяти. Используйте опцию -m, чтобы выделить больше памяти.
  5. Если поддерживается драйверами гостевой операционной системы, используйте virtio для сетевых и/или блочных устройств.
  6. Используйте устройства TAP вместо подключения к сети в пользовательском режиме.
  7. Если гостевая ОС выполняет интенсивную запись на свой диск, вы можете воспользоваться определенными параметрами монтирования в файловой системе хоста. Например, вы можете смонтировать файловую систему ext4 с параметром barrier=0.