Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
«Скажи мне, Рождённый Женщиной,
— вопросил Кришна,
Куда движутся эти миры,
Зачем злой Парвана по ночам охотится
за своей второй сущностью,
И почему у ласточки Бшакти две ноги,
а у Меня двадцать четыре?»
— Элементарно, — сказал Арджуна, — берём вектор Пойнтинга…
БХАГАВАД-ГИТА (мл. тибетская)
Запев 30 тома
Предыстория
Когда-то давно для создания виртуальных машин (далее ВМ) в Gentoo Linux я использовал libvirt, всё бы ничего, но однажды свет увидела версия Qemu с поддержкой чипсета Q35, libvirt же ещё долгое (как мне казалось) время предлагал пользоваться только одним старым добрым i440FX. Хотелось попробовать новомодную функцию, не потеряв при этом в удобстве создания ВМ. После череды проб и ошибок получилась среда виртуализации на чистом Qemu, удобная и простая (по крайней мере, на мой взгляд), пригодная для быстрого создания похожих ВМ, как правило, недолгоживущих, так как специфика моей работы заключается именно в установке операционных систем (далее ОС) и программного обеспечения (далее ПО) на голые сервера. ОС — это обычно CentOS, Astra Linux, Windows Server. Выходит, новая версия ОС или ПО — создаются новые пустые ВМ для отработки установки всей совокупности ПО.
Дано
ОС хоста | Gentoo Linux Профиль default/linux/amd64/17.1/no-multilib |
Ядро | sys-kernel/gentoo-sources-5.13.0-r1 USE=«experimental symlink» Здесь и далее снятые USE-флаги не показаны. Внимание: версия ядра 5.13.0 содержала ошибку в коде сервера NFS (см., например, elrepo.org/bugs/view.php?id=1116), поэтому под ним не работала сетевая установка CentOS с помощью NFS |
Менеджер служб | sys-apps/systemd-248.3-r1 USE=«gcrypt hwdb kmod lz4 pam pcre policykit seccomp split-usr sysv-utils zstd» |
Управление логическими томами | sys-fs/lvm2-2.02.188 USE=«systemd thin udev» |
Пользовательские инструменты виртуализации | app-emulation/qemu-6.0.0-r50 USE=«caps io-uring usb vhost-net vnc» QEMU_SOFTMMU_TARGETS=«x86_64» QEMU_USER_TARGETS="" |
Просмотрщик VNC | net-libs/gtk-vnc-1.2.0 USE=«introspection vala» К сожалению, по непонятным причинам этот ebuild с некоторых пор не устанавливает собственно клиент VNC, его приходится собирать отдельно такими командами: ebuild /путь/до/дерева/портежей/net-libs/gtk-vnc/gtk-vnc-1.2.0.ebuild unpack ebuild /путь/до/дерева/портежей/net-libs/gtk-vnc/gtk-vnc-1.2.0.ebuild compile После этого шага собранный gvncviewer копируется из временного каталога сборки в /usr/local/bin, например |
Управление сетью | net-misc/connman-1.40 USE=«ethernet nftables wifi» |
Сетевой экран | net-firewall/nftables-0.9.9 USE=«gmp modern-kernel» |
Сервер DNS, DHCP и TFTP | net-dns/dnsmasq-2.85 USE=«dhcp idn inotify libidn2 tftp» |
Сервер HTTP и FTP | sys-apps/busybox-1.33.1 USE=«savedconfig systemd» Вполне хватает для сетевой установки гостевых ОС |
Сервер CIFS | net-fs/samba-4.14.5-r1 USE=«system-mitkrb5 systemd» Нужен для сетевой установки гостевого Windows Server |
Средство для подключения к сокетам Unix | net-misc/socat-1.7.4.1 USE="" |
Оконный менеджер | gui-wm/sway-1.6.1 USE=«X man swaybar swaybg swayidle swaylock swaymsg swaynag tray» Указываю здесь на всякий случай, так как следующий пункт с этим связан |
Средство автоматизации | gui-apps/ydotool-0.1.9 Этот ebuild отсутствует в официальном дереве портежей, его нужно взять из оверлея pg_overlay, оттуда же берутся две зависимости: dev-libs/libevdevplus-0.1.1 dev-libs/libuinputplus-0.1.4 |
Настройка ядра
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:
- Процесс привязывается к определённому набору логических процессоров (командой taskset). Набор процессоров определяется в настроечном файле ВМ.
- Процесс работает с правами пользователя qemu (-runas qemu). Если он не создан, то его нужно создать с домашним каталогом /dev/null и оболочкой /sbin/nologin. Данный пользователь должен входить в группу kvm.
- Процессор для ВМ используется хостовый, без изменений (-cpu host). Чипсет — Q35 (-machine q35), из-за чего всё и начиналось.
- Большая часть оборудования берётся из настроечного файла ВМ (-readconfig $CFG).
- Память ВМ выделяется полностью (-mem-prealloc), для чего используются огромные страницы (-mem-path /dev/hugepages). Размер памяти берётся из настроечного файла ВМ (-m "${X[@]}"). Как вы можете заметить, в массиве $X передаётся не только размер памяти, но и некоторые другие настройки.
- Время БИОСа в ВМ будет в часовом поясе UTC (-rtc base=utc), это рекомендуется для Unix-подобных систем. Для Windows это не вызовет сложностей, так как с помощью редактирования реестра можно научить её понимать такое время.
- Процесс запирает себя в каталоге /var/tmp/qemu/empty с помощью вызова chroot (-chroot $CHROOT). В этот каталог мы позже поместим образ виртуального съёмного устройства хранения («флешки»).
- Audio-устройства ВМ не предоставляются (-audiodev id=none,driver=none). Обычно хватает перенаправления аудио с помощью RDP в случае ВМ на Windows.
- Каждой ВМ предоставляется устройство Virtio-RNG — паравиртуальный генератор случайных чисел (-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, номер экрана задаётся в настроечном файле (-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
Данные команды произведут следующие действия:
- переключение на окно gvncviewer,
- выбор пункта меню «Send Key → Ctrl+Alt+Del»,
- пятисекундное ожидание появления окна ввода пароля администратора,
- ввод пароля,
- нажатие клавиши Enter.
Ссылки
- pavsh.ru/official/non-koncerts/stemsite-frolov/public/prosp/legendy.htm — эпиграф.
- gpo.zugaina.org — поиск по оверлеям Gentoo, Funtoo и Zentoo.
- gitlab.com/Perfect_Gentleman/PG_Overlay — домашняя страница оверлея pg_overlay.
- qemu-project.gitlab.io/qemu — документация Qemu.
- habr.com/ru/company/infowatch/blog/492260 — описание настройки nft, уже немного устаревшее, так как создавалось до перехода на Systemd.
- fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio — драйвера Virtio для Windows.
- docs.microsoft.com/ru-ru/windows-hardware/get-started/adk-install — средства для развёртывания и оценки Windows (Windows ADK).
Автор статьи: Шамиль Саитов Nikodim_Tychoblin
«В лето 7529»