Пример создания среды виртуализации в Gentoo

image

«Скажи мне, Рождённый Женщиной,
— вопросил Кришна,
Куда движутся эти миры,
Зачем злой Парвана по ночам охотится
за своей второй сущностью,
И почему у ласточки Бшакти две ноги,
а у Меня двадцать четыре?»
— Элементарно, — сказал Арджуна, — берём вектор Пойнтинга…
БХАГАВАД-ГИТА (мл. тибетская)
Запев 30 тома

Предыстория

Когда-то давно для создания виртуальных машин (далее ВМ) в Gentoo Linux я использовал libvirt, всё бы ничего, но однажды свет увидела версия Qemu с поддержкой чипсета Q35, libvirt же ещё долгое (как мне казалось) время предлагал пользоваться только одним старым добрым i440FX. Хотелось попробовать новомодную функцию, не потеряв при этом в удобстве создания ВМ. После череды проб и ошибок получилась среда виртуализации на чистом Qemu, удобная и простая (по крайней мере, на мой взгляд), пригодная для быстрого создания похожих ВМ, как правило, недолгоживущих, так как специфика моей работы заключается именно в установке операционных систем (далее ОС) и программного обеспечения (далее ПО) на голые сервера. ОС — это обычно CentOS, Astra Linux, Windows Server. Выходит, новая версия ОС или ПО — создаются новые пустые ВМ для отработки установки всей совокупности ПО.

Дано

Настройка ядра

 General setup ---> [*] Configure standard kernel features (expert users) --->

# Выберем такой тип AIO (Asynchronous Input/Output) для ВМ, опять же, ужасно новомодный

 [*] Enable IO uring support
[*] Virtualization --->

# Без KVM, полагаю, ВМ будут о-о-очень медленными

<*> Kernel-based Virtual Machine (KVM) support

# Следующие две настройки зависят от производителя центрального процессора (далее ЦП) хоста (мне достался Intel)

 <*> KVM for Intel (and compatible) processors support < > KVM for AMD processors support
[*] Networking support ---> Networking options --->

# Полезно для сетевого взаимодействия с ВМ

 <*> 802.1d Ethernet Bridging Device Drivers ---> [*] Block devices --->

# Для монтирования ISO-образов, понадобится при сетевой установке гостевых ОС

 <*> Loopback device support [*] Multiple devices driver support (RAID and LVM) ---> <*> Device mapper support

# Для создания снапшотов ВМ

<*> Snapshot target

# Для бережного использования диска хоста

 <*> Thin provisioning target [*] Network device support ---> [*] Network core driver support

# Полезно для сетевого взаимодействия с ВМ

 <*> Universal TUN/TAP device driver support Input device support ---> [*] Miscellaneous devices --->

# Это совсем не обязательно, но пригодится для автоматизации некоторых операций с ВМ

<M> User level driver support

# Для ускорения работы сети в ВМ

 [*] VHOST drivers ---> <*> Host kernel accelerator for virtio net File systems ---> CD-ROM/DVD Filesystems --->

# Для монтирования ISO-образов, понадобится при сетевой установке гостевых ОС

 <M> ISO 9660 CDROM file system support [*] Microsoft Joliet CDROM extensions

# Для монтирования ISO-образов, понадобится при сетевой установке гостевой Windows Server

 <M> UDF file system support Pseudo filesystems --->
<i>

# Очень рекомендуется использовать огромные страницы для ВМ

 [*] HugeTLB file system support [*] Network File Systems --->

# Понадобится при сетевой установке гостевой CentOS

 <M> NFS server support [*] NFS server support for NFS version 4

Для справки привожу выдержку из /usr/src/linux/.config:

CONFIG_IO_URING=y
CONFIG_VIRTUALIZATION=y
CONFIG_KVM=y
CONFIG_KVM_INTEL=y
CONFIG_BRIDGE=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_DM_SNAPSHOT=y
CONFIG_DM_THIN_PROVISIONING=y
CONFIG_TUN=y
CONFIG_INPUT_UINPUT=m
CONFIG_VHOST_NET=y
CONFIG_ISO9660_FS=m
CONFIG_JOLIET=y
CONFIG_UDF_FS=m
CONFIG_HUGETLBFS=y
CONFIG_NFSD=m
CONFIG_NFSD_V4=y

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

Настройка дискового хранилища

Допустим, у нас имеется 500 ГБ свободного пространства в группе томов «vg», создадим в нём так называемый «тонкий пул» с именем «thin»:

lvm lvcreate -c 64M -I 64M -L 500G -Zn --type thin-pool --thinpool thin vg

Ключ -Zn отключает обнуление первого блока создаваемых в этом пуле логических томов.

Настройка оперативной памяти

Мой ЦП поддерживает только двухмегабайтные огромные страницы, для гигабайтных нужны дополнительные настройки загрузчика. Количество огромных страниц зададим в /etc/sysctl.conf:

vm.nr_hugepages = 5000

Hugetlbs автоматически монтируется менеджером служб Systemd в /dev/hugepages.

Настройка сети

До перехода с OpenRC на Systemd никаких сложностей с настройкой сети не было. Так как Systemd может настроить только предельно простую сеть (по крайней мере, пока), после перехода потребовалась установка службы настройки сети, в этом качестве был выбран connman. Однако он не подходит для создания сетевого моста. Systemd мог бы создать своими средствами мост, но он не назначает пустому мосту IP-адрес.

В итоге пришлось создать отдельную службу /etc/systemd/system/create-br0.service:

[Unit]
Description=Bridge creation.
Before=network-pre.target
Before=nftables-restore.service [Service]
Type=simple
ExecStart=/bin/bash /usr/local/sbin/create-br0.sh
RemainAfterExit=yes [Install]
WantedBy=nftables-restore.service
WantedBy=dnsmasq.service

Сценарий /usr/local/sbin/create-br0.sh у меня выглядит так:

#!/bin/sh
ip link add br0 type bridge
ip link set br0 address 8e:fb:65:16:72:38
ip link set br0 up
ip addr add 192.168.120.1/24 dev br0

Чтобы connman не мешал, исключим в его настройках мост и сетевые стыки ВМ, в итоге /etc/connman/main.conf выглядит так:

[General]
AllowDomainnameUpdates=false
AllowHostnameUpdates=false
NetworkInterfaceBlacklist=lo,vnet,br

Настройка nft, за вычетом всего, никак не касающегося ВМ, выглядит так:

#!/sbin/nft -f

define icmp_types = { destination-unreachable, time-exceeded, parameter-problem, timestamp-request, echo-request, echo-reply }
define br = br0
define host = 192.168.120.1
define virtual_machines = 192.168.120.0/24
define br_bcast = 192.168.120.255
define eth = enp0s25
define wifi = wlp2s0
define dhcp_client = 192.168.120.224/27

# ВМ с выходом в Интернет

define privileged_vm = { 192.168.120.22, 192.168.120.127, 192.168.120.129 }
define nat_if = { $eth, $wifi }
define squid = 3128 flush ruleset table ip filter { chain input { type filter hook input priority 0; policy drop; iif lo accept comment "allow loopback" icmp type $icmp_types accept comment "allow important ICMP types" ct state invalid counter drop comment "drop invalid packets" tcp flags syn tcp option maxseg size < 999 counter drop comment "TCP SACK Panic workaround" tcp flags & (syn | ack) == syn ct state untracked counter drop comment "drop initial packets from untracked ports" iif $br ip daddr $host ip saddr $virtual_machines tcp dport { domain, http, https, microsoft-ds, nfs, $squid, ftp } accept comment "allow services for virtual machines" ct state { established, related } accept comment "allow all related connections" iif $br udp dport { domain, bootps, tftp, 4011 } counter accept comment "allow DNS, DHCP, TFTP, proxyDHCP" counter comment "count dropped packets" } chain output { type filter hook output priority 100; policy drop; oif lo accept comment "allow loopback" icmp type $icmp_types counter accept comment "allow important ICMP types" oif { $eth, $wifi } udp dport . udp sport { bootps . bootpc } counter accept comment "allow exchange with DHCP servers" oif $br ip saddr $host ip daddr { $dhcp_client, 255.255.255.255 } udp sport . udp dport { bootps . bootpc } counter accept comment "allow DHCP of dnsmasq" oif $br ip saddr $host ip daddr $virtual_machines udp sport { domain, tftp } counter accept comment "allow DNS, TFTP" oif $br ip saddr $host ip daddr $virtual_machines tcp sport { domain, http, https, microsoft-ds, ftp } accept comment "allow DNS, HTTP, Samba, FTP" meta l4proto { tcp, udp } th sport >= 1025 accept comment "allow unprivileged ports" counter comment "count dropped packets" } chain forward { type filter hook forward priority 0; policy drop; tcp flags syn tcp option maxseg size set rt mtu counter comment "clamp TCP MSS to path MTU" iif $br ip daddr != $host meta l4proto { tcp, udp } th dport domain drop comment "deny DNS to external servers" iif $br ip saddr { $privileged_vm, $dhcp_client } accept comment "allow privileged virtual machine & DHCP clients" oif $br ip daddr { $privileged_vm, $dhcp_client } accept comment "allow privileged virtual machine & DHCP clients" counter comment "count dropped packets" }
} table ip nat { chain postrouting { type nat hook postrouting priority 100; policy accept; oif $nat_if ip saddr { $privileged_vm, $dhcp_client } counter masquerade comment "masquerade virtual machine" }
}

Сценарий запуска ВМ

Для запуска по запросу отдельных ВМ нам понадобится следующий простой сценарий, назовём его /usr/local/sbin/qemu:

#!/bin/bash
VM=$2
if [[ "$1" != status && -z "$VM" ]]; then echo "No VM name was supplied" exit 4
fi
WAIT=60
CFGDIR=/etc/qemu
CHROOT=/var/tmp/qemu/empty
export LVM_SUPPRESS_FD_WARNINGS=1
[ -f /etc/conf.d/qemu.$VM ] && . /etc/conf.d/qemu.$VM
CFG=$CFGDIR/$VM.cfg case $1 in start) if [[ -r $CFG ]]; then CPU=`sed -ne '/#CPU/{s/.*=\(.*\)/\1/p;q}' $CFG` X[0]=`sed -ne '/#MEM/{s/.*=\(.*\)/\1/p;q}' $CFG` if grep '^#kernel=' $CFG &> /dev/null; then X[1]='-kernel' X[2]=`sed -ne '/#kernel/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` X[3]='-append' X[4]=`sed -ne '/#append/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` X[5]='-initrd' X[7]=`sed -ne '/#initrd/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` fi N=`sed -ne '/= "vnet[0-9]/{s/.*"vnet\([0-9]\+\).*/\1/p;q}' $CFG` echo "Starting $VM" taskset -c $CPU qemu-system-x86_64 -name $VM -nodefaults -daemonize -runas qemu -cpu host -machine q35 -readconfig $CFG -m "${X[@]}" \ -mem-prealloc -mem-path /dev/hugepages -rtc base=utc -chroot $CHROOT -vga virtio -audiodev id=none,driver=none \ -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,id=vrng0,max-bytes=1024,period=200 -vnc 127.0.0.1:$N || exit 1 else echo "Unrecognized VM: $VM" exit 3 fi ;; stop) if [[ -r $CFG ]]; then PID=`pgrep -f "qemu-system-x86_64 -name $VM "` if [[ -n $PID ]]; then echo "Stopping $VM" MON=`sed -ne 's/^\s*path\s*=\s*"\(.*\)"/\1/p' $CFG` echo '{ "execute": "qmp_capabilities" } { "execute": "system_powerdown" }' | socat - $MON > /dev/null while ps -p $PID >/dev/null 2>&1; do [[ $((i++)) -ge $WAIT ]] && break || sleep 1 done [[ $i -gt $WAIT ]] && kill $PID fi [[ $i -gt $WAIT ]] && sleep 2 pgrep -f "qemu-system-x86_64 -name $VM " && exit 1 else echo "Unrecognized VM: $VM" exit 3 fi ;; status) ps -eo args | sed -ne 's/.*[q]emu-system-x86_64 -name \([^ ]\+\) .* -vnc \([^ ]\+\)/\1\t\2/p' ;; *) echo "Unrecognized command: $1" exit 2 ;;
esac

Как видно из строки запуска qemu-system-x86_64:

  1. Процесс привязывается к определённому набору логических процессоров (командой taskset). Набор процессоров определяется в настроечном файле ВМ.
  2. Процесс работает с правами пользователя qemu (-runas qemu). Если он не создан, то его нужно создать с домашним каталогом /dev/null и оболочкой /sbin/nologin. Данный пользователь должен входить в группу kvm.
  3. Процессор для ВМ используется хостовый, без изменений (-cpu host). Чипсет — Q35 (-machine q35), из-за чего всё и начиналось.
  4. Большая часть оборудования берётся из настроечного файла ВМ (-readconfig $CFG).
  5. Память ВМ выделяется полностью (-mem-prealloc), для чего используются огромные страницы (-mem-path /dev/hugepages). Размер памяти берётся из настроечного файла ВМ (-m «${X[@]}»). Как вы можете заметить, в массиве $X передаётся не только размер памяти, но и некоторые другие настройки.
  6. Время БИОСа в ВМ будет в часовом поясе UTC (-rtc base=utc), это рекомендуется для Unix-подобных систем. Для Windows это не вызовет сложностей, так как с помощью редактирования реестра можно научить её понимать такое время.
  7. Процесс запирает себя в каталоге /var/tmp/qemu/empty с помощью вызова chroot (-chroot $CHROOT). В этот каталог мы позже поместим образ виртуального съёмного устройства хранения («флешки»).
  8. Audio-устройства ВМ не предоставляются (-audiodev id=none,driver=none). Обычно хватает перенаправления аудио с помощью RDP в случае ВМ на Windows.
  9. Каждой ВМ предоставляется устройство Virtio-RNG — паравиртуальный генератор случайных чисел (-object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,id=vrng0,max-bytes=1024,period=200).
  10. Для доступа к консоли ВМ предоставляется VNC на адресе 127.0.0.1, номер экрана задаётся в настроечном файле (-vnc 127.0.0.1:$N).

Кроме этого сценария есть ещё один вспомогательный, /etc/qemu/ifup.sh:

#!/bin/bash
ip link set $1 master br0
ip link set $1 up

Создание ВМ

Выделение места в дисковом хранилище

Место выделяется в тонком пуле следующей командой:

lvm lvcreate -T vg/thin -V 10G -n ubuntu

Будет создан логический том размером 10 ГБ, путь к блочному устройству /dev/mapper/vg-ubuntu. Пока гостевая ОС не установлена, этот том места на диске не занимает.

Создание настроечного файла ВМ

Для каждой ВМ создаётся отдельный настроечный файл /etc/qemu/Имя_ВМ.cfg. Пример для линуксовой ВМ:

# qemu config file
# Эта директива используется сценарием запуска ВМ
# определяет, к каким процессорам привязывается процесс qemu
#CPU=0,1,2,3
# Эта директива используется сценарием запуска ВМ
# определяет размер памяти ВМ в МБ
#MEM=4096
# Следующие три директивы используются сценарием запуска ВМ
# используются для непосредственного запуска ядра самим qemu
#kernel=/var/tmp/qemu/vmlinuz-3.10.0-957.el7.x86_64
#initrd=/var/tmp/qemu/initramfs-3.10.0-957.el7.x86_64.img
#append=ro root=/dev/vda1 elevator=noop numa=off transparent_hugepage=never nosoftlockup mce=ignore_ce audit=0

# Следующие два блока настраивают первый виртуальный привод DVD
# используются при установке ОС с виртуального привода DVD

[drive "cd0"] file = "/opt/dist/ks-CentOS_2009-tm_7.2.0.155.iso" if = "none" readonly = "on" format = "raw" [device] driver = "ide-cd" drive = "cd0" bus = "ide.0" unit = "0"

# Следующие два блока настраивают первый виртуальный жёсткий диск ВМ

[drive "vhd0"] file = "/dev/mapper/vg-centos" if = "none" format = "raw"

# Кеш в небезопасном режиме для скорости, надёжность хранения не важна в моём случае.
# Либо же, например, можно использовать такой режим при установке гостевой ОС,
# а затем переключиться в режим «none»

cache = "unsafe" cache.direct = "on" aio = "io_uring" [device "virtio0"] driver = "virtio-blk-pci" scsi = "off" bus = "pcie.0" drive = "vhd0"

# Настройка сокета для взаимодействия с qemu с помощью команд QMP (QEMU Machine Protocol)

[chardev "charmonitor"] backend = "socket" path = "/var/tmp/qemu/centos.monitor" server = "on" wait = "off"

# Следующие два блока настраивают первый сетевой стык ВМ

[device "net0"] driver = "virtio-net-pci" netdev = "hostnet0" mac = "52:54:00:07:00:08" bus = "pcie.0"

# Настройка многоочерёдного (mutliqueue) сетевого стыка ВМ
# требуется поддержка и настройка в гостевой ОС (см. далее)
# число должно быть равно 2 * M + 2, где M — число логических процессоров ВМ

 vectors = "10" mq = "on" [netdev "hostnet0"] type = "tap"

# Число после «vnet» определяет номер экрана VNC

 ifname = "vnet6" vhost = "on" script = "/etc/qemu/ifup.sh" downscript = "no"

# Настройка многоочерёдного (multiqueue) сетевого стыка ВМ
# в гостевой ОС включается командой: ethtool -L eth0 combined 4
# число должно быть равно М, где M — число логических процессоров ВМ

queues = "4" [mon] chardev = "charmonitor" mode = "control" [machine] type = "q35" accel = "kvm" usb = "on"

# Конфигурация процессора с точки зрения ВМ

[smp-opts] sockets = "1" cores = "2" threads = "2"

# Следующие два блока переводят ВМ в режим UEFI

[drive] file = "/usr/share/edk2-ovmf/OVMF_CODE.fd" if = "pflash" format = "raw" unit = "0" readonly = "on" [drive] file = "/var/tmp/qemu/OVMF_VARS_tm72p.fd" if = "pflash" format = "raw" unit = "1"

Пример для ВМ c Windows:

# qemu config file
#MEM=3072
#CPU=2,3

# Следующие четыре блока настраивают два виртуальных привода DVD
# используются при установке ОС с виртуального привода DVD

[drive "cd0"] file = "/opt/distr/windows_server.iso" if = "none" readonly = "on" format = "raw" [device] driver = "ide-cd" drive = "cd0" bus = "ide.0" unit = "0" [drive "cd1"]

# Второй привод DVD нужен для предоставления установщику Windows драйверов устройств Virtio

 file = "/opt/distr/virtio-win-0.1.190.iso" if = "none" readonly = "on" format = "raw" [device] driver = "ide-cd" drive = "cd1" bus = "ide.1" unit = "0" [drive "vhd0"] file = "/dev/mapper/vg-windoze" if = "none" format = "raw" cache = "unsafe" cache.direct = "on" aio = "io_uring" [device "virtio0"] driver = "virtio-blk-pci" scsi = "off" bus = "pcie.0" drive = "vhd0" [chardev "charmonitor"] backend = "socket" path = "/var/tmp/qemu/windoze.monitor" server = "on" wait = "off" [device "net0"] driver = "virtio-net-pci" netdev = "hostnet0" mac = "52:54:00:20:12:04" bus = "pcie.0" [netdev "hostnet0"] type = "tap" ifname = "vnet8" vhost = "on" script = "/etc/qemu/ifup.sh" downscript = "no"

# Нужно для комфортной работы через VNC

[device "tablet"] driver = "usb-tablet" [mon] chardev = "charmonitor" mode = "control" [machine] type = "q35" accel = "kvm" usb = "on" [smp-opts] cpus = "2"

Подготовка вспомогательных файлов

При работе ВМ в режиме UEFI для каждой ВМ нужно скопировать /usr/share/edk2-ovmf/OVMF_VARS.fd в отдельный файл в каталоге Qemu, в моём примере /var/tmp/qemu.
Для дополнительных настроек запуска ВМ предусмотрен ещё один конфигурационный файл: /etc/conf.d/qemu.Имя_ВМ (как наследство OpenRC). Содержит одну строку с переменной QEMU_OPTS. Пример для загрузки по сети:

QEMU_OPTS="-boot order=n"

Замечание, касающееся Systemd: до перехода с OpenRC я широко использовал каталог /var/tmp (в моём случае это отдельный раздел) для хранения разнородных данных, например, данных qemu. После перехода на Systemd выяснилось, что в нём имеется служба systemd-tmpfiles-clean, которая удалила, к моему сожалению, из /var/tmp много нужных файлов. Чтобы защитить файлы qemu, был создан настроечный файл /usr/lib/tmpfiles.d/qemu.conf:

d /var/tmp/qemu 0755 qemu qemu -

Запуск и остановка ВМ

Запуск ВМ по требованию выглядит так:

/usr/local/sbin/qemu start Имя_ВМ

Остановка соответственно:

/usr/local/sbin/qemu stop Имя_ВМ

Получение перечня запущенных ВМ:

/usr/local/sbin/qemu status

Подключение к ВМ хостовых устройств USB на постоянной основе

В настроечный файл достаточно добавить блок с указанием VID и PID устройства, например:

[device "usbaudio"] driver = "usb-host" vendorid = "0x041e" productid = "0x30e0"

Поскольку эти настройки, как я заметил, производятся программой qemu до сброса администраторских привилегий, менять владельца устройства на qemu:qemu необязательно.

Подключение к работающей ВМ виртуального съёмного устройства хранения («флешки»)

Предварительно должен быть создан образ виртуального съёмного устройства, например, размером в 1 ГБ, в каталоге $CHROOT (в моём случае это /var/tmp/qemu/empty):

truncate -s 1G /var/tmp/qemu/empty/usb.img
chown qemu:qemu /var/tmp/qemu/empty/usb.img

Поскольку в нашей установке графика у ВМ предоставляется только посредством VNC, то командная строка Qemu Monitor недоступна. Поэтому будем пользоваться протоколом QMP, подключаясь к сокету, указанному в настроечном файле:

socat - /var/tmp/qemu/windoze.monitor

После успешного подключения к сокету в первую очередь даём команду:

{ "execute": "qmp_capabilities" }

Затем следуют команды подключения устройства (отформатировано для удобства просмотра):

{ "execute": "human-monitor-command", "arguments": { "command-line": "drive_add auto if=none,id=hd1,file=/usb.img,cache=writeback,cache.direct=on,aio=io_uring,format=raw" }
}
{ "execute": "device_add", "arguments": { "driver": "usb-storage", "drive": "hd1", "id": "usb1" }
}

Команды отключения устройства будут, соответственно, такими:

{ "execute": "device_del", "arguments": { "id": "usb1" } }
{ "execute": "human-monitor-command", "arguments": { "command-line": "drive_del hd1" } }

Создание снимка (снапшота) выключенной ВМ

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

lvm lvcreate -s vg/centos -n centos_bak

Обратите внимание, что снимок помечается как не подлежащий автоматической активации LVM.

Перенос диска ВМ из одного пула в другой

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

dd if=/dev/vg/some_vm of=/dev/other_vg/some_vm bs=64M conv=sparse

Здесь ключевым является «conv=sparse», нужный для того, чтобы логический том занимал только фактически использованное ВМ место.

Автозапуск ВМ при запуске хоста

Иногда требуется, чтобы ВМ запускалась одновременно с остальными службами хоста. Для таких случаев подготовим другой сценарий, назовём его /usr/local/bin/guests:

#!/bin/bash
export LVM_SUPPRESS_FD_WARNINGS=1
CFGDIR=/etc/qemu case $1 in start) CHROOT=/var/tmp/qemu/empty for CFG in $CFGDIR/*.cfg; do VM=${CFG##*/} VM=${VM%.cfg} [ -f /etc/conf.d/qemu.$VM ] && . /etc/conf.d/qemu.$VM CPU=`sed -ne '/#CPU/{s/.*=\(.*\)/\1/p;q}' $CFG` X[0]=`sed -ne '/#MEM/{s/.*=\(.*\)/\1/p;q}' $CFG` if grep '^#kernel=' $CFG &> /dev/null; then X[1]='-kernel' X[2]=`sed -ne '/#kernel/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` X[3]='-append' X[4]=`sed -ne '/#append/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` X[5]='-initrd' X[7]=`sed -ne '/#initrd/{s/[^=]*=\(.*\)/\1/p;q}' $CFG` fi N=`sed -ne '/= "vnet[0-9]/{s/.*"vnet\([0-9]\+\).*/\1/p;q}' $CFG` echo "Starting $VM" taskset -c $CPU qemu-system-x86_64 -name $VM -nodefaults -daemonize -runas qemu -cpu host -machine q35 -readconfig $CFG -m "${X[@]}" \ -mem-prealloc -mem-path /dev/hugepages -rtc base=utc -chroot $CHROOT -vga virtio -audiodev id=none,driver=none \ -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0,id=vrng0,max-bytes=1024,period=200 -vnc 127.0.0.1:$N done ;; stop) WAIT=60 for CFG in $CFGDIR/*.cfg; do VM=${CFG##*/} VM=${VM%.cfg} PID=`pgrep -f "qemu-system-x86_64 -name $VM "` if [[ -n $PID ]]; then echo "Stopping $VM" MON=`sed -ne 's/^\s*path\s*=\s*"\(.*\)"/\1/p' $CFG` echo '{ "execute": "qmp_capabilities" } { "execute": "system_powerdown" }' | socat - $MON > /dev/null while ps -p $PID >/dev/null 2>&1; do [[ $((i++)) -ge $WAIT ]] && break || sleep 1 done [[ $i -gt $WAIT ]] && kill $PID fi [[ $i -gt $WAIT ]] && sleep 2 pgrep -f "qemu-system-x86_64 -name $VM " && exit 1 done ;; status) ps -eo args | sed -ne 's/.*[q]emu-system-x86_64 -name \([^ ]\+\) .* -vnc \([^ ]\+\)/\1\t\2/p' ;; *) echo "Unrecognized command: $1" exit 2 ;;
esac

Собственно для запуска создаётся служба /usr/lib/systemd/system/guests.service:

[Unit]
Description=Qemu-KVM guests service.
After=network.target [Service]
Type=forking
ExecStart=/bin/bash /usr/local/bin/guests start
ExecStop=/bin/bash /usr/local/bin/guests stop [Install]
WantedBy=multi-user.target

Настройка сетевой установки гостевых ОС

Все файлы, нужные для сетевой установки, поместим в разделе /netboot.

DHCP и TFTP

В качестве основного приложения, обеспечивающего сетевую установку, будем использовать dnsmasq. Привожу часть его конфигурации, касающуюся DHCP и TFTP:

dhcp-range=192.168.120.225,192.168.120.252,3h
dhcp-option=option:netmask,255.255.255.0
dhcp-option=option:router,192.168.120.1
dhcp-option=option:dns-server,192.168.120.1
dhcp-option=option:domain-name,shadow.amn
dhcp-option-force=209,pxelinux.cfg/default
dhcp-authoritative
dhcp-vendorclass=set:uefi,"PXEClient:Arch:00007"
dhcp-match=set:uefi,option:client-arch,7
enable-tftp
tftp-root=/netboot/tftp
dhcp-boot=pxelinux.0,host.shadow.amn,192.168.120.1
dhcp-boot=tag:uefi,grubx64.efi,host.shadow.amn,192.168.120.1
pxe-prompt="Press F8 for PXE Network boot.",10
pxe-service=x86PC,"Boot from local disk"
pxe-service=x86PC,"Install OS via PXE",pxelinux

Корнем TFTP указан каталог /netboot/tftp. Поместим в него следующие файлы:

1. Для PXE в режиме BIOS:

  • pxelinux.0, ldlinux.c32, libcom32.c32, libutil.c32, vesamenu.c32 и memdisk из sys-boot/syslinux.
  • Каталог pxelinux.cfg с файлом default, представляющим собой меню загрузчика pxelinux.

2. Для PXE в режиме UEFI:

  • grubx64.efi с установочного DVD CentOS 7.9 (находится в EFI/BOOT).
  • grub.cfg, представляющим собой меню загрузчика Grub.
  • Символьную ссылку grub.cfg-00000000-0000-0000-0000-000000000000 на файл grub.cfg.

3. Для ВМ на CentOS: каталог centos, содержащий файлы initrd.img и vmlinuz с установочного DVD CentOS 7.9 (находятся в images/pxeboot).

4. Для ВМ на Astra Linux: каталог astra, содержащий файлы initrd.gz и linux с установочного DVD Astra Linux.

5. Для ВМ на Windows:

  • каталог windows, содержащий файлы BCD, bootmgr.exe, boot.sdi, pxeboot.0 и winpe.wim, подготовленные с помощью Windows Assessment and Deployment Kit (Windows ADK), пустые файлы boot.ini и hiberfil.sys, каталог Fonts с файлом wgl4_boot.ttf.
  • Символьную ссылку Boot на каталог windows.
  • Символьную ссылку boot.ini на windows/boot.ini.
  • Символьную ссылку bootmgr.exe на windows/bootmgr.exe.
  • Символьную ссылку hiberfil.sys на windows/hiberfil.sys.

Пример файла grub.cfg:

set timeout=60 menuentry 'Rescue installed system (CentOS)' { linuxefi centos/vmlinuz inst.stage2=nfs:nfsvers=4:192.168.120.1:/netboot/nfs rescue selinux=0 initrdefi centos/initrd.img
} menuentry 'Install CentOS' { linuxefi centos/vmlinuz rd.net.timeout.carrier=15 inst.repo=nfs:nfsvers=4:192.168.120.1:/netboot/nfs inst.ks=http://192.168.120.1/ks.cfg inst.sshd initrdefi centos/initrd.img
} menuentry 'Install Astra Linux' { linuxefi astra/linux auto=true priority=critical debian-installer/locale=ru_RU console-keymaps-at/keymap=ru url=ftp://192.168.120.1/preseed.cfg interface=auto netcfg/dhcp_timeout=60 initrdefi astra/initrd.gz
}

Пример файла pxelinux.cfg/default:

default vesamenu.c32 label CentOS
kernel centos/vmlinuz
append initrd=centos/initrd.img rd.net.timeout.carrier=15 inst.repo=nfs:nfsvers=4:192.168.120.1:/netboot/nfs inst.ks=http://192.168.120.1/ks.cfg inst.sshd label Astra
menu label Astra Linux
kernel astra/linux
append initrd=astra/initrd.gz auto=true priority=critical debian-installer/locale=ru_RU console-keymaps-at/keymap=ru url=ftp://192.168.120.1/preseed.cfg interface=auto netcfg/dhcp_timeout=60 label Windows
menu label Windows Server
kernel windows/pxeboot.0

NFS

Каталог /netboot/nfs будет точкой монтирования ISO-образа установочного DVD CentOS.

Настроечный файл /etc/exports.d/netboot.exports для сетевой установки CentOS через NFS выглядит так:

/netboot/nfs -sec=sys,mp,async,no_root_squash,no_subtree_check 192.168.120.0/24

HTTP и FTP

В каталогe /netboot/http будет размещаться ks.cfg — кикстарт для CentOS, в каталоге /netboot/ftp будет размещаться preseed.cfg — аналог кикстарта для Astra Linux и пустой каталог astra — точка монтирования ISO-образа установочного DVD Astra Linux.

Я решил не усложнять задачу установкой полновесных веб- и FTP-серверов, воспользовался апплетами busybox’а. Для запуска их как служб очень хорошо подошёл Systemd.

Создадим службу /etc/systemd/system/httpd@.service:

[Unit]
Description=Busybox HTTPd
Requires=httpd.socket [Service]
Type=simple
ExecStart=/sbin/httpd -h /netboot/http/ -vvfi
StandardInput=socket
StandardError=journal
TimeoutStopSec=60 [Install]
WantedBy=multi-user.target

И соответствующий ей сокет /etc/systemd/system/httpd.socket:

[Unit]
Description=Busybox HTTPd [Socket]
ListenStream=192.168.120.1:80
Accept=yes [Install]
WantedBy=sockets.target

Создадим службу /etc/systemd/system/ftpd@.service:

[Unit]
Description=Busybox FTPd
Requires=ftpd.socket [Service]
Type=simple
ExecStart=/sbin/ftpd /netboot/ftp
StandardInput=socket
StandardError=journal
TimeoutStopSec=60 [Install]
WantedBy=multi-user.target

И соответствующий ей сокет /etc/systemd/system/ftpd.socket:

[Unit]
Description=Busybox FTPd [Socket]
ListenStream=192.168.120.1:21
Accept=yes [Install]
WantedBy=sockets.target

Теперь запуск httpd и ftpd становится просто запуском сокета Systemd:

systemctl start httpd.socket
systemctl start ftpd.socket

CIFS

В каталоге /netboot/smb создадим два пустых каталога (например, 2k16 и virtio), которые будут точками монтирования ISO-образов установочного DVD Windows Server и ISO-образа с драйверами Virtio для Windows. Правда, с установкой сетевых драйверов при сетевой установке Windows Server были сложности, решение, видимо, состоит в том, чтобы включить эти драйвера заранее в ISO-образ установочного DVD Windows Server.

Также в /netboot/smb может находиться файл autounattend.xml для автоматической установки Windows Server.

Часть настроечного файла /etc/samba/smb.conf, касающаяся сетевой установки Windows Server, выглядит так:

[global]
workgroup = AMN
netbios name = HOST
interfaces = 192.168.120.1/255.255.255.0
bind interfaces only = yes
load printers = no
show add printer wizard = no
server string = My notebook
disable netbios = yes [public]
comment = Public
hosts allow = 192.168.120.0/255.255.255.0
path = /netboot/smb
force user = nobody
force group = nobody
create mask = 0644
directory mask = 0755
read only = yes
guest ok = yes
oplocks = no
level2 oplocks = no
locking = no
acl allow execute always = yes

Как уже было указано, файл winpe.wim был подготовлен с помощью ADK, в него были включены драйвера Virtio (обязательно viostor и netkvm), а также сценарий startnet.cmd:

@echo off
chcp 65001

echo.
echo Запускаю wpeinit.
echo.
wpeinit

echo На выбор доступно три режима работы WinPE:
echo 1) Монтирование сетевого ресурса и возврат в оболочку.
echo.
echo 2) Монтирование сетевого ресурса и запуск установки
echo Windows Server 2016 Standard.
echo.
echo 3) Монтирование сетевого ресурса и запуск автоматической
echo установки Windows Server 2016 Standard
echo.
echo Для выбора пункта меню введите соответствующую ему цифру,
echo а затем нажмите клавишу Enter (ошибочный ввод = 1 пункт).
set /p ID=
echo.

if %ID%==1 goto :first
if %ID%==2 goto :second
if %ID%==3 goto :third
if %ID% GTR 3 goto :failure
if %ID% LSS 3 goto :failure
exit /b

:second
echo Выбран пункт меню под номером 2
echo.
echo Монтирую сетевой ресурс.
net use S: \\192.168.120.1\public
echo Запускаю S:\2k16\setup.exe
S:\2k16\setup.exe
exit /b

:third
echo Выбран пункт меню под номером 3
echo.
echo Монтирую сетевой ресурс.
net use S: \\192.168.120.1\public
echo Запускаю автоматическую установку: S:\2k16\setup.exe /unattend:S:\autounattend.xml
S:\2k16\setup.exe /unattend:S:\autounattend.xml
exit /b

:first
echo Выбран пункт меню под номером 1
echo.

:failure
echo.
echo Монтирую сетевой ресурс.
net use S: \\192.168.120.1\public
exit /b

Небольшая автоматизация

При установке Windows Server некоторую часть настроек приходится выполнять через VNC. Хотелось бы автоматизировать ввод длинных строк или паролей в окне просмотра VNC, чтобы сократить вероятность опечатки. Для Wayland’а имеется утилита ydotool, которая, правда, давно не обновлялась. Ею особенно, на мой взгляд, удобно пользоваться в плиточном оконном менеджере sway, так как запущенное из командной строки приложение gvncviewer всегда оказывается в одном и том же месте экрана.

Ydotool требует модуль uinput, загрузим его и дадим права на чтение и запись группе wheel, в которую входит обычная учётная запись (далее УЗ):

modprobe uinput
chgrp wheel /dev/uinput
chmod g+rw /dev/uinput

Затем, уже от имени обычной УЗ запустим демон ydotoold:

ydotoold &

Готово. Теперь запускаем gvncviewer с указанием адреса и номера экрана нашей ВМ. Возвращаемся обратно в командную строку и даём одной строкой череду команд (добавлены переводы строк для удобства чтения):

ydotool mousemove 350 15;
ydotool click 1;
ydotool key Alt+s;
ydotool type d;
sleep 5;
ydotool type P@ssw0rd;
ydotool key Enter

Данные команды произведут следующие действия:

  1. переключение на окно gvncviewer,
  2. выбор пункта меню «Send Key → Ctrl+Alt+Del»,
  3. пятисекундное ожидание появления окна ввода пароля администратора,
  4. ввод пароля,
  5. нажатие клавиши Enter.

Ссылки

  1. pavsh.ru/official/non-koncerts/stemsite-frolov/public/prosp/legendy.htm — эпиграф.
  2. gpo.zugaina.org — поиск по оверлеям Gentoo, Funtoo и Zentoo.
  3. gitlab.com/Perfect_Gentleman/PG_Overlay — домашняя страница оверлея pg_overlay.
  4. qemu-project.gitlab.io/qemu — документация Qemu.
  5. habr.com/ru/company/infowatch/blog/492260 — описание настройки nft, уже немного устаревшее, так как создавалось до перехода на Systemd.
  6. fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio — драйвера Virtio для Windows.
  7. docs.microsoft.com/ru-ru/windows-hardware/get-started/adk-install — средства для развёртывания и оценки Windows (Windows ADK).

Автор статьи: Шамиль Саитов Nikodim_Tychoblin
«В лето 7529»

Читайте так же:

  • Как научиться наполнять сайт товарамиКак научиться наполнять сайт товарами Независимо от того, заинтересованы ли вы в создании собственных веб-сайтов или продолжаете карьеру веб-разработчика. Знание HTML является важным навыком. Язык гипертекстовой разметки, или HTML, является одной из трех основных технологий. Обеспечивающих работу практически каждого […]
  • Websocket API на nodejs по новомуWebsocket API на nodejs по новому О чем эта статья?uWebsockets.js - высокопроизводительная реализация http/websocket сервера для nodejsAsyncAPI - спецификация для асинхронного API, с помощью которой можно создать описание Websocket APIПростой пример websocket API с использованием библиотеки wsapix:создадим websocket […]
  • Huawei показала флагман P50 с камерой Leica, но не рассказала, когда он выйдетHuawei показала флагман P50 с камерой Leica, но не рассказала, когда он выйдет Во время сегодняшней большой презентации генеральный директор Huawei Ю Чэндон (Yu Chengdong) официально анонсировал флагманский смартфон P50. Теперь мы точно знаем. Как он выглядит. В компании «формулу» основной камеры называют «3+1»: в верхнем круглом этаже расположились три модуля. В […]
  • IT-фестиваль «Код Васильевского». Первый этап: «View» Описание Программа фестиваля:“IT-рынок 2021”. Спикер: Екатерина Соловьёва. Руководитель отдела маркетинга по СЗФО компании https://vk.com/headhunter">hh.ru.“3 шага от теории к практике”: Личный опыт создания интернет-маркетинговой […]