KVM/Helper

Материал из ALT Linux Wiki
< KVM

KVM Helper

Описание прототипа функционального аналога guestfs, собираемого пользователем в Hasher без жёстких ограничений пакетным менеджментом. Адрес проекта: http://git.altlinux.org/people/klark/packages/kvm-helper.git

Пример работы в интерактивной оболочке KVM-Helper'а

Первоначальные цели проекта

  • Проверить идею Сергея Большакова с отказом от stage2 "поместить всю систему в initrd".
  • Провести эксперименты с devtmpfs без udev, при этом не отказываясь от модульного ядра.
  • Минимальными усилиями создать рабочий прототип initrd (rootfs) для будущего UML-Helper'а.

Планируется его использовать в будущей "разбивалке дисков", как один из Helper'ов. В 2018 году уже использовался для создания нестандартных загрузочных образов и в автоматизированном процессе подготовки образов для ноутбуков подмосковных школ. В 2019 году станет частью системы массового развёртывания в качестве одного из под-проектов. Таким образом, даже в действующем виде "Proof of Concept" уже востребован в трёх моих проектах. Надеюсь, сгодится и вам!

Зачем это надо, когда уже есть guestfs?

guestfs ограничивается возможностями, закладываемыми апстримом и мэйнтейнером пакета. Но цели использования guestfs могут быть разными, сам образ guestfs должен быть как можно меньше. Если один человек убедит мэйнтейнера включить те или иные опции, другому уже не получится убедить его в обратном. Лично меня побудило заняться альтернативой куцее наполнение утилитами для работы с файловыми системами. Даже то, что было в образе, имело ограниченный функционал и не подходило для моих задач, а отрывать от серьёзной работы уважаемого человека было неудобно.

Поэтому я посчитал, что такой инструмент не должен иметь жёсткой конструкции. Напротив, любой пользователь должен иметь возможность создать себе образ за считанные секунды по заранее определённой конфигурации, иметь возможность эту конфигурацию легко изменять под свои нужды. Второй причиной является то, что guestfs определяет собственную систему команд, тогда как для всех моих задач было предпочтительно сохранить оригинальную семантику, определяемую стандартными утилитами для разметки дисков и работы с файловыми системами.

Зачем это надо вообще?

Hasher не обеспечивает возможности для прямой работы с накопителями (дисками), loop-устройствами, не позволяет монтировать что-то такое в публичной сборочнице. Схожие ограничения есть и у контейнеров. Всё перечисленное реализуется средствами полноценной виртуальной машины, и, что очень важно, для этого пользователю не нужно получать привилегии root. Скрипты для сборки или подготовки специальных образов могут работать в достаточно изолированной виртуальной среде и пользователю не надо бояться, что они испортят что-то в его физической host-системе. Однако полноценная виртуализация -- это довольно большие накладные расходы. Поэтому такая вспомогательная виртуалка должна быть минималистична с точки зрения наполнения.

Несколько слов о названии

На данный момент используется qemu-kvm, в идеале же стоит перейти на kvm-tools, поэтому "KVM Helper". Если возможности железа ограничены, можно отказаться от KVM, тогда логичнее это назвать "QEMU Helper". Первоначальное название "UML Helper" несёт в себе лишь общий смысл -- User-Mode Linux используется как принцип, но не как конкретная одноимённая технология виртуализации в ядре.

Будущее UML Helper'а

Для уменьшения образа основой должен быть BusyBox, слинкованный с glibc, поскольку в конечном итоге это процесс на полноценной сборочной машине, а не Embedded-применение, к тому же, в составе BusyBox практически нет полноценных утилит для работы с файловыми системами и разметки дисков, их всё равно придётся дотягивать с зависимостями на glibc. Для создания образа initrd не должно быть зависимости от текущей реализации make-initrd, это попросту чревато. Как вариант, можно рассчитывать на стабильные возможности make-initrd, которых в нём пока не реализовывалось. Модульность и конфигурируемость в прототипе UML Helper'а также отсутствует, пока это скорее "Proof of Concept".

Отказ от stage2

Действительно не во всех ситуациях нужна двухстадийная загрузка. Так, список всего "железа" для виртуалки известен заранее. Нынешний инструментарий ALT практически не приспособлен создавать системы, работающие в stage1. Тем не менее, данный проект показывает общий принцип и что это всё-таки возможно!

Про эксперименты с devtmpfs без udev

Документация к ядру объявляет два интересных постулата, которые хотелось пощупать на практике:

1. devtmpfs работает независимо от udev, инициализируется на ранней стадии и наполняется по мере обнаружения устройств. Единственное, чего не окажется в /dev без udev, это каких-то особых прав, все устройства будут иметь права доступа 0600 и принадлежать root'у. Зачем тогда, спрашивается, костыль под названием udev?

2. В ядро всегда вкомпилируется пустой образ initrd, это занимает считанные байты. Но в параметрах загрузки ядра можно указать альтернативный путь, и тогда ядро будет использовать указанный initrd. Если ядро не найдёт в initrd /init, оно использует собственный fallback-механизм для обнаружения корневого устройства. При этом нигде не говорится, что содержимое initrd отбрасывается -- ведь кроме скриптов инициализации, там могут быть модули ядра и firmware. Выходит, если указать в параметрах загрузки всё необходимое для поиска относительно стандартного rootfs, можно обойтись без скриптов и программ в initrd. Так ли это?

По факту оказалось: devtmpfs действительно заполняется автоматически после загрузки нужных модулей. Возможно, если бы все необходимые модули были бы уже вкомпилированы в ядро, без udev'а, скриптов инициализации и каких-либо утилит в user-space'е действительно можно было бы обойтись. Но в случае модульного ядра кто-то должен дать команду insmod (modprobe) и он должен знать, какие модули нужно грузить на данной системе. А это значит: либо udev, либо статическая загрузка модулей скриптом по заранее созданному списку. Разумеется, речь о надёжности и не о десктопном применении. Предположение от vt@: если модули в ядро вкомпилированы, оно делает insmod по ним по всем, почему бы не сделать так же скриптом в initrd?

Как не следует использовать make-initrd

make-initrd умеет делать несколько полезных вещей: определять модули ядра, необходимые для монтирования корня на конкретной системе, строить зависимости одних модулей ядра от других, строить всю цепочку библиотечных зависимостей для помещаемых в образ программ. Есть в нём и другие полезные вещи, но мне совершенно точно не требовалось помещать в initrd скрипты legion@ самого make-initrd вместе со всей udev-обвязкой, да и реализовывать повторно все эти полезные фишки тоже было лениво. Пришлось на лету менять пару скриптов от текущей версии make-initrd и целый каталог удалять, заменяя своим. Это весьма хлипкая конструкция, понятное дело, не для долгоживущего решения. Не стоит повторять такое в production!

Инструкция по применению

На рабочей машине (x86_64) должны быть установлены и настроены git-core, hasher, qemu-system-x86_64. Убедитесь, что в BIOS'е включена технология KVM и загружены соответствующие модули ядра (lsmod | grep kvm). Иначе придётся подправить скрипт runme1st под свою конфигурацию хост-системы. В скриптах также используются такие утилиты, как realpath и readlink. По умолчанию работа ведётся на tmpfs, а созданная виртуалка запускается с параметром -m 2048, т.е. ей потребуется 2G ОЗУ, что можно поменять в том же скрипте.

Клонируем к себе репозиторий

$ git clone git://git.altlinux.org/people/klark/packages/kvm-helper.git
Cloning into 'kvm-helper'...
remote: Counting objects: 39, done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 39 (delta 0), reused 39 (delta 0)
Receiving objects: 100% (39/39), 11.56 KiB | 0 bytes/s, done.

Переходим в созданный каталог

$ cd kvm-helper

Меняем настройки под свои нужды

$ mv local.conf.example local.conf
$ $EDITOR local.conf

Менять можно всё, что определено по умолчанию (head -n18 mkhelper). Хотя, скорее всего, ввиду зависимости от текущей версии make-initrd, это будет работать только с Сизифом, так что branch лучше не менять.

Запускаем всё одной командой

$ ./runme1st

Обращаем внимание на размер получившегося образа

$ du -csh .work/results/{vmlinuz,initrd.img}
4,8M .work/results/vmlinuz
11M  .work/results/initrd.img
16M  итого

Обращаем внимание (см. скриншот), что удаление модулей освобождает 2Мб из занятых 43Мб. Программ понапихано чересчур много и есть ещё, куда оптимизировать. Листинг содержимого лежит рядышком: .work/results/initcpio.

В дальнейшем, чтобы запустить всё с нуля, используем ./runme1st --clean. Повторный запуск этого скрипта без параметров не приведёт к пересборке ранее созданного образа. Принудительно пересобрать образ без запуска самой виртуалки можно вторым скриптом: ./mkhelper. Исходников не так много, только чтобы показать общий принцип, так что внесение изменений будет не сложным делом. Обращу лишь внимание на модули ядра. Если в файл features/base-system/files.list добавить строчку /etc/modules, будут загружены модули всех виртуальных устройств Qemu/KVM, попадающих в прошивку:

GUEST> lsmod
Module                  Size  Used by
sd_mod                 49152  0
joydev                 24576  0
iTCO_wdt               16384  0
virtio_rng             16384  0
iTCO_vendor_support    16384  1 iTCO_wdt
rng_core               16384  1 virtio_rng
9pnet_virtio           20480  1
virtio_balloon         20480  0
9p                     61440  1
9pnet                  86016  2 9p,9pnet_virtio
ahci                   40960  0
libahci                40960  1 ahci
i2c_i801               32768  0
libata                278528  2 libahci,ahci
i2c_smbus              16384  0
evdev                  28672  0
input_leds             16384  0
qemu_fw_cfg            16384  0
psmouse               147456  0
i2c_core               77824  3 i2c_smbus,i2c_i801,psmouse
serio_raw              16384  0
button                 16384  0
scsi_mod              253952  2 sd_mod,libata
pcspkr                 16384  0
lpc_ich                28672  0
intel_agp              24576  0
intel_gtt              24576  1 intel_agp
virtio_pci             28672  0
virtio_ring            28672  4 virtio_rng,virtio_balloon,9pnet_virtio,virtio_pci
virtio                 16384  4 virtio_rng,virtio_balloon,9pnet_virtio,virtio_pci

Взаимодействие между хостовой и гостевой системами

Хотя в примере и на скриншоте запускается оболочка в интерактивном режиме, задача KVM Helper'а безусловно в другом: выполнять внутри виртуалки не интерактивные команды, определяемые в хост-системе. Замените содержимое скрипта guest и посмотрите, что получится. Ещё более интересным видится вариант разового запуска KVM Helper'а со скриптом, организующем собственный цикл исполнения запросов, которые могут поступать через ту же plan9-шару в виде отдельных команд или скриптов. Пример есть в руководстве по Rescue/Launcher'у, только там полноценная Rescue-система с двухстадийной загрузкой, которая весит 700Мб в сжатом виде, здесь же 16Мб в сжатом, а реально -- до 43Мб в ОЗУ, и временем от запуска до выключения в 1.6 секунды (скрипт set_hwclock замедляет загрузку для наглядности, на самом деле его лучше убрать). И ещё раз: приблизить начало выполнения полезной нагрузки и снизить загрузку процессора до нуля можно организацией собственного цикла обработки запросов.

Enjoy! ;-)