Что за зверь StratoVirt

StratoVirt — это монитор виртуальных машин (VMM) корпоративного класса, ориентированный на облачные центры обработки данных в компьютерной отрасли. Он использует унифицированную архитектуру для поддержки сценариев виртуальных машин, контейнеров и без серверных систем. StratoVirt имеет конкурентные преимущества в таких ключевых технологиях, как легкость и низкий уровень шума, синергия программного и аппаратного обеспечения, а также безопасность на уровне языка Rust. StratoVirt резервирует возможности сборки на основе компонентов и API-интерфейсы при проектировании архитектуры. Расширенные функции можно гибко комбинировать по мере необходимости, пока они не будут развиты для поддержки стандартной виртуализации. Таким образом, StratoVirt может найти баланс между требованиями к функциям, сценариями применения и гибкостью.

Базовая архитектура StratoVirt состоит из трех слоев сверху вниз:

  • Внешний API: совместим с протоколом монитора QEMU (QMP), имеет полную совместимость с OCI и поддерживает взаимодействие с libvirt.
  • BootLoader: отказывается от традиционного режима загрузки BIOS+GRUB для обеспечения быстрой загрузки в облегченных сценариях и обеспечивает поддержку загрузки UEFI для стандартных виртуальных машин.
  • Эмулируемая материнская плата:
    • MicroVM: полностью использует возможности программного и аппаратного обеспечения совместной работы, упрощает модели устройств и обеспечивает возможности масштабирования ресурсов с малой задержкой.
    • Стандартная виртуальная машина: реализует загрузку UEFI с помощью созданных таблиц ACPI. Можно подключить устройства Virtio-pci и VFIO, чтобы значительно повысить производительность ввода-вывода виртуальной машины.

В этой статье будет проведение первичное функциональное тестирование и оценена пригодность к запуску ВМ.

Установка

Знакомство со StratoVirt начнем в виртуальной машине под управлением openScaler 22.03 SP3. Для этого создаем ВМ с параметрами не хуже:

  • 2 ядра
  • 4 ГБ памяти
  • 16 ГБ диск

StratoVirt доступен в репозитории по умолчанию и ничего дополнительно прописывать не нужно, установка производится в одну команду:
dnf install stratovirt

Подготовка окружения

Установка пакета является недостаточным этапом для запуска ВМ, необходимо сделать еще несколько приседаний. Это следующие телодвижения:

  • наличие устройства с реализацией MMIO /dev/vhost-vsock
  • загрузочный образ ядра и rootfs

MMIO – это возможность получения доступа к портам ввода/вывода через доступ к памяти. Для проверки что эта возможность доступна убедимся в наличии устройства /dev/vhost-vsock:
ls /dev/vhost-vsock

Если устройства нет, то может потребоваться загрузить модуль vhost_vsock

modprobe vhost_vsock

В документации рекомендуют собрать ядро из исходных кодов, что на мой взгляд в бинарном дистрибутиве моветон, по этому будем использовать ядро из каталога boot. В качестве гостевой ОС для первого знакомства запустим alpine linux.

Скачиваем минимальный образ alpine linux:

wget https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz

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

dd if=/dev/zero of=rootfs.ext4 bs=1G count=2
mkfs.ext4 rootfs.ext4

Создаем каталог для его монтирования и монтируем:

mkdir rootfs
mount rootfs.ext4 rootfs

Копируем скачанный архив в корень rootfs и перемещаемся туда же:

cp alpine-minirootfs-3.19.1-x86_64.tar.gz rootfs/
cd rootfs

Производим распаковку, предварительно установив tar:

tar xvf alpine-minirootfs-3.19.1-x86_64.tar.gz
rm -rf alpine-minirootfs-3.19.1-x86_64.tar.gz

Так как alpine linux используется в основном для контейнеров, то в нем нет скрипта init, а есть symlink на busybox, создадим свой init:

rm sbin/init
touch sbin/init
cat > sbin/init <<EOF
#! /bin/sh
mount -t devtmpfs dev /dev
mount -t proc proc /proc
mount -t sysfs sysfs /sys
ip link set up dev lo

exec /sbin/getty -n -l /bin/sh 115200 /dev/ttyS0
poweroff -f
EOF
sudo chmod +x sbin/init

Выйдем из каталога и отмонтируем образ:

cd ..
umount rootfs

Запуск

Попробуем запустить ВМ, для этого выполним команду:

stratovirt -kernel /boot/vmlinuz-5.10.0-185.0.0.98.os2203sp3.x86_64 -initrd /boot/initramfs-5.10.0-185.0.0.98.os2203sp3.x86_64.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=rootfs.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

И… в ответ ничего, полная тишина, ни консоли ВМ, ни сообщения об ошибке, хм? Воспользуемся помощью strace:

strace stratovirt -kernel /boot/vmlinuz-5.10.0-185.0.0.98.os2203sp3.x86_64 -initrd /boot/initramfs-5.10.0-185.0.0.98.os2203sp3.x86_64.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=rootfs.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

В самом конце видим такие строки:

munmap(0x7f8c517bb000, 4096) = 0
mmap(NULL, 268435456, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8c417ba000
open("/dev/kvm", O_RDWR|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (Нет такого файла или каталога)
mmap(NULL, 28672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8c417b3000
unlink("/tmp/stratovirt.socket") = 0
exit_group(1) = ?
+++ exited with 1 +++

Значит для работы не хватает поддержки kvm, для случая с ВМ необходимо осуществить вложенную виртуализацию, для реального сервера включить опцию в BIOS. Производим необходимые манипуляции, и после перезагрузки проверяем наличие /dev/kvm:

ls /dev/kvm

Пробуем снова запустить ВМ:

stratovirt -kernel /boot/vmlinuz-5.10.0-185.0.0.98.os2203sp3.x86_64 -initrd /boot/initramfs-5.10.0-185.0.0.98.os2203sp3.x86_64.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=rootfs.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

И видим что ВМ успешно стартует:

Но дальше загрузка останавливается и чего-то ждёт. По прошествии таймаута получаем приглашение в консоль восстановления:

Вводим пароль рута и пытаемся понять чего не хватает:

А не хватает диска vda… Значит ядро основной ОС не пригодно для использования со stratovirt. Попробуем понять, что с этим можно сделать.
В документации к stratovirt рекомендуют скачать исходники ядра openEuler через git, а потом собрать с применением конфига от проекта stratovirt.

Сборка ядра

Попробуем несколько ускорить и упростить задачу, для этого скачаем последнее ядро с kernel.org и соберем его. Качаем:

wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.9.tar.xz

Теперь необходим конфигурационный файл, для этого берем и скачиваем исходники stratovirt:

git clone https://gitee.com/openeuler/stratovirt

Распаковываем ядро, добавляем конфигурационный файл и пытаемся все это собрать:

tar xv linux-6.9.tar.xz
cd linux-6.9
cp ../stratovirt/docs/kernel_config/micro_vm/kernel_config_5.10_x86_64 .config
make oldconfig
make -j4 bzImage

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

dnf install make gcc bison flex openssl-devel elfutils-devel

По прошествии некоторого времени ядро будет собрано:

Попробуем загрузить ВМ с новым ядром, для этого сделаем копию в /root и остановим текущий процесс stratovirt:

cp arch/x86/boot/bzImage /root/bzImage-6.9
ps axuf | grep [s]tra
root 744569 1.4 1.3 268736 205668 pts/0 Sl+ 11:50 0:22 | \_ stratovirt -kernel /boot/vmlinuz-5.10.0-185.0.0.98.os2203sp3.x86_64 -initrd /boot/initramfs-5.10.0-185.0.0.98.os2203sp3.x86_64.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=rootfs.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio
kill 744569

Для запуска будем использовать команду:

stratovirt -kernel /root/bzImage-6.9 -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=rootfs.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

Берем только что собранное ядро, скрипач initrd уже не нужен:

ВМ успешно запустилась с новым ядро и диск на месте. Попробуем понять, чего не хватает стандартному ядру для работы, для этого подготовим еще один образ диска, на этот раз с openScaler на борту.
Для этого создаем файл образа, создаем на нем ФС, монтируем и устанавливаем минимальный набор пакетов, чтобы сделать его еще более минимальным добавим строку:

install_weak_deps=False

в конец файла /etc/yum.conf.

Необходимые команды:

dd if=/dev/zero of=sc_root.ext4 bs=1G count=4
mkfs.ext4 sc_root.ext4
mount sc_root.ext4 rootfs
rpm --root /root/rootfs --initdb
dnf install --installroot /root/rootfs dnf yum haveged vim net-tools iproute iputils NetworkManager openssh-server openssh-clients passwd hostname parted linux-firmware gdisk chrony bc systemd-timesyncd openScaler-repos dracut vim glibc-all-langpacks

После установки пакетов необходимо задать пароль root и прописать /dev/vda в /etc/fstab. Для получения UUID файловой системы используем команду blkid, а для задания пароля выполним команду passwd в chroot окружении, предварительно отключив selinux:

blkid | grep loop
/dev/loop0: UUID="b1f6aa34-e3fa-4c6b-9808-63b25aaf00d7" BLOCK_SIZE="4096" TYPE="ext4"
echo UUID=b1f6aa34-e3fa-4c6b-9808-63b25aaf00d7 / ext4 defaults 1 1 >> rootfs/etc/fstab
setenforce 0
chroot rootfs /bin/bash
passwd
exit
umount rootfs

Теперь можно запустить ВМ с новым ядром и образом openScaler:

stratovirt -kernel /root/bzImage-6.9 -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=sc_root.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

ВМ успешно запустилась. Попробуем понять, как у нас подключен диск /dev/vda:

udevadm info /dev/vda

В первой строке видно, что используется устройство virtio-mmio-cmdline, попробуем посмотреть какие опции отвечают за включение этого устройства:

grep -i mmio /root/linux-6.9/.config

А что по этому поводу скажет конфиг ядра openScaler? Проверим:

zcat /proc/config.gz | grep -i mmio

Как видно поддержки virtio_mmio нет.

Сила сообщества

Мы сообщество или где?

Властью данной мне Попробуем добавить поддержку в новую версию ядра в репозитории update.
Делаем приготовления на кухне сборки openScaler и получаем новое ядро версии kernel-5.10.0-199.0.0.113.os2203sp3.x86_64. Производим обновление ОС, с целью получить новое ядро:

dnf update

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

rpm -ql kernel | grep mmio

Попробуем запустить ВМ с обновленным ядром:

stratovirt -kernel /boot/vmlinuz-5.10.0-199.0.0.113.os2203sp3.x86_64 -initrd /boot/initramfs-5.10.0-199.0.0.113.os2203sp3.x86_64.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=sc_root.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

И опять ОС не грузится и чего-то ждем, неужели что-то забыли? Ждем положенный таймаут, вводим пароль и начинаем диагностику:

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

dracut /root/initram-5.10-199.img --force-drivers virtio_mmio --kver 5.10.0-199.0.0.113.os2203sp3.x86_64

На выходе получаем initrd, с которым и попробуем запустить ВМ:

stratovirt -kernel /boot/vmlinuz-5.10.0-199.0.0.113.os2203sp3.x86_64 -initrd /root/initram-5.10-199.img -append console=ttyS0 root=/dev/vda rw -drive id=disk_id,file=sc_root.ext4,readonly=false -device virtio-blk-device,drive=disk_id,id=blkid -qmp unix:/tmp/stratovirt.socket,server,nowait -serial stdio

И наконец ВМ успешно запустилась.

Как видно stratovirt вполне успешно работает, но в некоторых местах еще требуется обработка напильником. Со своей стороны, как сообщество, попробуем передать в upstream openEuler изменение в конфигурационном файле ядра, для поддержки работы с virtio_mmio из коробки.