Задача: запустить FreeBSD из-под Linux, желательно с минимумом изменений в Linux-системе при начальной настройке и обновлениях, возможностью запуска на рабочей станции и на сервере, и с минимальной потерей производительности.
В качестве VPS-фермы можно использовать любой распространённый дистрибутив Linux. В нашем случае это будет Ubuntu 12.10 с ядром 3.5.0 для amd64.
Гостевой системой будет FreeBSD 9.1 для i386. Архитектура i386 выбрана из-за существенно меньшего потребления ОЗУ 32-битными приложениями по сравнению с 64-битными.
В качестве системы виртуализации будет использоваться Linux-KVM («Kernel-based Virtual Machine»).
Краткое сравнение KVM с альтернативами
Плюсы KVM:
- не требует предварительной инсталляции гипервизора и планирования ресурсов, в отличие от Xen/ESXi/Hyper-V, и для изучения и тестирования может быть запущен на любом Linux-дистрибутиве, включая настольные,
- в отличие от всех остальных систем виртуализации (кроме LXC и с оговорками OpenVZ), включён в базовое ядро Linux и развивается ключевыми Linux-разработчиками (в первую очередь — RedHat),
- в отличие от LXC и OpenVZ, способен запускать произвольную ОС, в т.ч. Linux с собственным экземпляром ядра и набором драйверов.
Минусы KVM:
- процессор обязан иметь аппаратную поддержку виртуализации,
- отсутствуют удобные графические оболочки для запуска и редактирования виртуальных машин,
- из базовой системы отсутствует прозрачный доступ к файлам, процессам и консолям контейнеров (в LXC и OpenVZ он есть).
Настройка окружения
Дальше будем считать, что все образы дисков и файлы настроек хранятся в домашнем каталоге в подкаталоге virt:
mkdir -p ~/virt && cd ~/virt
Устанавливаем в Linux необходимое ПО:
apt-get update && apt-get -y install kvm
Собственно виртуализацию выполняет модуль в составе ядра, но в пакетах, которые apt-get установит по зависимостям, находятся управляющие и вспомогательные утилиты, KVM-специфичные настройки для основных Linux-сервисов (например, для udev) и т.д.
Проверяем наличие аппаратной поддержки:
kvm-ok
Аппаратная виртуализация (а) должна поддерживаться процессором и (б) должна быть разрешена в BIOS'e. В противном случае KVM работать не сможет и команда запуска гостевой системы будет либо завершаться с ошибкой (если указан ключ "-enable-kvm"), либо переключаться в существенно менее производительный режим программной виртуализации на базе QEMU.
Стандартный shell-сценарий kvm-ok выполняет типовые проверки и при неудачном результате советует способ исправления.
Сеть для контейнеров
KVM поддерживает множество вариантов организации гостевой сети (см. для примера краткий официальный обзор). В данный момент «man kvm» содержит 9 8 вариантов ключа "-net" с несколькими десятками возможных подключей, причём зачастую "-net" нужно указывать в команде запуска контейнера дважды с разными наборами подключей — для создания гостевого интерфейса, и для создания интерфейса в базовой системе для связи с гостем. Возможно, сетевые настройки являются самой неочевидной частью KVM при начальном освоении.
Для более-менее серьёзного использования имеют смысл два варианта:
- базовая система предоставляет гостевым прозрачный доступ во внешнюю сеть через т.н. сетевой мост («network bridge»),
- базовая система работает как маршрутизатор между внешней и гостевой сетью («router»).
Оба требуют суперпользовательских привилегий, имеют в нашем случае одинаковый набор параметров для "-net ...", но отличаются набором действий в сценарии "-net ...,script=...", который KVM вызывать при старте контейнера для настройки сетевого интерфейса, созданного в базовой системе. Вариант с мостом несколько проще, поэтому наш сценарий ~/virt/kvm-ifup-bridge.sh будет делать следующее:
- если мост отсутствует — создаёт его и добавляет в него внешний физический интерфейс,
- назначает мосту такой же IP, как у физического интерфейса,
- перемещает все маршруты с физического интерфейса на мост,
- подключает в мост виртуальный интерфейс для связи с гостевой системой.
#!/bin/sh
# Constants
BRIDGE_IFACE="br0"
# Variables
iface="$1"
gwdev="$(ip route get 8.8.8.8 | grep ' via ' | sed -e 's,.* dev ,,' -e 's, .*,,' | head -1)"
my_ip="$(ip addr list dev $gwdev | grep ' inet ' | sed -e 's,.* inet ,,' -e 's, .*,,' | head -1)"
# Create and configure bridge
if ! ip link list "$BRIDGE_IFACE" >/dev/null 2>&1
then
echo "Create bridge $BRIDGE_IFACE..."
brctl addbr "$BRIDGE_IFACE"
brctl addif "$BRIDGE_IFACE" "$gwdev"
ip link set "$BRIDGE_IFACE" up
ip addr add "$my_ip" dev "$BRIDGE_IFACE"
fi
# Move routes from physical iface to bridge
if test "$gwdev" != "$BRIDGE_IFACE"
then
ip route list dev "$gwdev" | grep -v 'scope link'
| while read line; do
ip route delete $line dev "$gwdev"
ip route add $line dev "$BRIDGE_IFACE"
done
fi
# Add virtual iface to bridge
ip link set "$iface" up
brctl addif "$BRIDGE_IFACE" "$iface"
В различных руководствах рекомендуется настраивать мост заранее, редактируя /etc/network/interfaces, но для тестовых целей на рабочей станции проще создавать его в тот момент, когда он становится действительно нужен, т.е. в момент первого запуска первого контейнера.
Если во внешней сети недопустимо засвечивать дополнительные MAC-адреса, то вместо моста можно использовать маршрутизацию и ProxyARP. Если внешняя сеть разрешает ровно один MAC и один IP, тогда в базовой системе для выхода гостевых систем во внешний мир придётся использовать маршрутизацию, IP-адрес на внутренних интерфейсах и NAT. В обоих случаях потребуется либо настраивать в гостевых системах статические IP, либо настраивать в базовой системе DHCP-сервер для конфигурирования гостей.
MAC-адреса для гостевых сетевых интерфейсов KVM способен генерировать автоматически при старте, но если планируется выпускать гостей во внешний мир через сетевой мост, лучше назначить им постоянные MAC-адреса. В частности, если во внешней сети запущен DHCP-сервер, это поможет гостевой системе получать от него одинаковый IP при каждом запуске. Сначала «сочиним» базовый MAC-адрес:
perl -e '$XEN_RESERVED = "00:16:3e"; printf "%s:%02x:%02x:%02xn", $XEN_RESERVED, int(rand(0x7f)), int(rand(0xff)), int(rand(0xff));'
Для контейнеров будем заменять последнее число на их порядковый номер. Этот же номер будем использовать для их имён и для VNC-консолей. Например, контейнер с номером 25 будет называться «kvm_25», иметь MAC 00:16:3e:xx:xx:25 и слушать VNC-подключения на порту 5925. Чтобы не огрести геморроя с разными системами счисления не иметь лишних проблем, рекомендуется выбирать номера от 10 до 99. Разумеется, такой подход не используется в VDS-хостинге, но для личных нужд он годится.
План действий
1. Загружаемся с образа CD, инсталлируем ОС на пустой образ hdd, выключаем VM.
2. Редактируем сценарий запуска (отключаем CD), загружаемся с hdd, настраиваем в гостевой ОС поддержку virtio, выключаем VM.
3. Редактируем сценарий запуска (типы диска и сети меняем с IDE и Realtek на virtio), загружаемся.
Подготовка к первой загрузке
Скачиваем ISO-образ установочного диска FreeBSD:
wget http://mirror.yandex.ru/freebsd/releases/ISO-IMAGES/9.1/FreeBSD-9.1-RELEASE-i386-disc1.iso
Создаём образ жесткого диска:
kvm-img create -f qcow2 freebsd9.img 8G
kvm-img info freebsd9.img
Формат образа выбирается ключом "-f": raw (default), qcow2, vdi, vmdk, cloop и т.д. Raw понятен любому ПО, но предоставляет минимум возможностей и сразу занимает максимально возможное место. Qcow2 компактнее (поддерживает динамическое увеличение размера) и функциональнее (поддерживает снимки, сжатие, шифрование и т.д.), но распознаётся только системами на основе QEMU.
Первый запуск и инсталляция
Сценарий для запуска ~/virt/freebsd9.start
#!/bin/sh
MACBASE="00:16:3e:33:28"
VM_ID=10
DIR=$HOME/virt
sudo kvm
-net "nic,model=rtl8139,macaddr=$MACBASE:$VM_ID"
-net "tap,ifname=tap$VM_ID,script=$DIR/kvm-ifup-bridge.sh,downscript=/bin/true"
-name "kvm_$VM_ID"
-enable-kvm
-m 512M
-hda $DIR/freebsd9.img
-cdrom "$DIR/FreeBSD-9.1-RELEASE-i386-disc1.iso"
-boot order=d
## END ##
В открывшемся окне должны запуститься CD Loader и установщик FreeBSD. Выполняем установку обычным образом. Почти все параметры можно оставить по умолчанию.
Пояснения к команде запуска
Sudo необходим, т.к. для создания TAP-интерфейса KVM-загрузчику требуются права суперпользователя.
Два ключа "-net" создают два соединенных друг с другом сетевых интерфейса: TAP в базовой системе и виртуальный Realtek-8139 в гостевой.
Ключ "-enable-kvm" гарантирует, что QEMU не выберет автоматически режим программной эмуляции, если KVM не смог запуститься.
Ключ "-name" определяет заголовок консольного окна, может использоваться для поиска в списке процессов и т.д.
Загрузочным диском выбран CD ("-boot order=d"). Опция имеет силу только при включении контейнера, т.е. при перезагрузке поиск системы начнётся с первого диска.
Ключ "-m" задаёт размер гостевого ОЗУ. По умолчанию — 128 мегабайт. Для работы установщика этого может быть достаточно, но уже после успешной установки первая же попытка собрать из портов большой проект при "-m 256M" и разделе подкачки на 512 мегабайт (размер автоматически выбран установщиком) вызвала kernel trap.
Загрузчик KVM работает как обычный пользовательский процесс, поэтому для выключения виртуальной машины достаточно просто нажать в консоли Ctrl+C (естественно, при запущенной гостевой ОС лучше этого не делать и пользоваться poweroff в гостевой консоли). Связь с системой виртуализации в ядре загрузчик осуществляет через символьное псевдоустройство /dev/kvm, поэтому запускать виртуальные машины может любой пользователь, имеющий право писать в него. Как правило, для таких пользователей в системе создаётся группа "kvm".
Для запуска в фоновом режиме у загрузчика есть ключ "-daemonize".
Второй запуск и настройка virtio
Перед запуском в сценарии freebsd9.start необходимо закомментировать строки "boot" и "cdrom". Затем запускаем его и после завершения загрузки FreeBSD входим в её командную строку с правами суперпользователя.
Гостевые драйверы поддержки virtio для FreeBSD пока не включены в базовое ядро, а распространяются в виде порта, поэтому нам потребуется установить дерево портов:
portsnap fetch extract
Для сборки драйверам требуются исходные тексты текущего ядра:
csup -h cvsup2.ru.FreeBSD.org /usr/share/examples/cvsup/standard-supfile
После этого собираем и инсталлируем сами драйверы:
make -C /usr/ports/emulators/virtio-kmod install clean
В /boot/loader.conf обязательно должны быть добавлены следующие строки:
virtio_load="YES"
virtio_blk_load="YES"
virtio_pci_load="YES"
virtio_balloon_load="YES"
if_vtnet_load="YES"
Их можно скопировать из /var/db/pkg/virtio-kmod*/+DISPLAY. Если забудете — ядро FreeBSD вывалится при загрузке в приглашение «mountroot>», потому что не сможет увидеть дисковое устройство с корневой ФС. Потребуется перезагружаться, заходить в командную строку boot-менеджера и вручную загружать перед ядром эти модули командой «load».
В /etc/rc.conf надо вставить одну из двух строк:
ifconfig_vtnet0="DHCP" # ..ifconfig_re0 можно удалить
ifconfig_vtnet0_name="re0" # ..ifconfig_re0 надо оставить!
Если к старому сетевому интерфейсу уже привязано большое количество настроек, второй вариант позволяет избежать их повсеместного изменения. Но он же делает общую схему чуть более запутанной.
В /etc/fstab надо заменить все "/dev/ada" на "/dev/vtbd". Если диск размечался установщиком автоматически, fstab станет таким:
# Device Mountpoint FStype Options Dump Pass#
/dev/vtbd0p2 / ufs rw 1 1
/dev/vtbd0p3 none swap sw 0 0
Если забудете или неправильно отредактируете fstab — при следующей загрузке попадёте в приглашение «mountroot» и будете вынуждены вручную набирать в нём «ufs:/dev/vtbd0p2».
Что такое virtio и зачем он вообще нужен?
Если в контейнер предоставляется виртуальная копия реально существующего устройства (такого как сетевая карта Realtek или SCSI-диск), обращения к нему сначала проходят через драйвер устройства в гостевой системе. Драйвер преобразует высокоуровневые вызовы чтения-записи данных в низкоуровневые операции с прерываниями, регистрами, портами ввода-вывода и т.д. Их перехватывает система виртуализации и выполняет обратную работу — переводит в высокоуровневые вызовы для внешней системы (например, чтения-записи файла-образа диска).
Если в контейнер предоставляется устройство типа virtio, драйвер гостевой системы немедленно передаёт данные во внешнюю систему и обратно. Драйвер упрощается, низкоуровневая виртуализация физических ресурсов не требуется.
Пишут, что переход на virtio ускоряет в гостевой системе диск вдвое, а сеть почти на порядок.
Ещё одна интересная возможность virtio связана с динамическим выделением памяти для гостевой системы ("ballooning") и объединением блоков памяти с одинаковым содержимым (KSM, «Kernel Samepage Merging»).
VirtualBox и KVM используют совместимый механизм virtio, поэтому набор гостевых драйверов для них одинаковый. В Linux гостевые драйверы уже включены в стандартное ядро, для FreeBSD распространяются в виде порта (см.выше), для Windows написаны разработчиками KVM (см.тут).
Третий запуск
Меняем в ~/virt/freebsd9.start строки с указанием сетевого интерфейса и диска:
-net "nic,model=rtl8139,macaddr=$MACBASE:$VM_ID"
-hda $DIR/freebsd9.img
… на следующие:
-net "nic,model=virtio,macaddr=$MACBASE:$VM_ID"
-drive "file=$DIR/freebsd9.img,if=virtio"
Если загрузка FreeBSD пройдёт успешно, можете убедиться с помощью следующих команд, что виртуальные устройства теперь используются:
ifconfig
df; swapinfo
kldstat
dmesg | grep vt
Гостевая консоль
По умолчанию KVM отрисовывает гостевую консоль в графическом окне с помощью библиотеки SDL. Такой вариант плохо подходит для запуска контейнера в фоновом режиме, для запуска на сервере без графики и для доступа к консоли по сети.
Для решения этой задачи KVM-контейнер может предоставлять доступ к гостевой консоли по сетевому протоколу VNC. В ~/virt/freebsd9.start вставьте в параметры запуска:
-vnc localhost:$VM_ID
Теперь при запуске контейнера KVM откроет не графическое окно, а сетевое подключение. Увидеть его можно, например, командой "sudo netstat -ntlp | grep -w kvm".
Установите клиентское приложение (например, tightvncviewer) и подключитесь к консоли:
apt-get install vncviewer
vncviewer :10
Примечание: если в окне VNC нет реакции на клавиатуру, кликните по нему.
VNC-соединение может быть защищено паролем, но назначить пароль непосредственно из командной строки, к сожалению, невозможно. Потребуется либо подключаться к управляющей консоли контейнера через отдельный управляющий сокет (краткое описание, как её настроить и как к ней подключиться), либо открывать её в основном VNC-окне нажатием Ctrl+Alt+Shift+2.
В дополнение к SDL и VNC, поддерживается текстовый интерфейс на базе curses (ключ "-curses" или "-display curses"). Теоретически он мог бы быть удобен для фонового запуска в screen. На практике KVM направляет в создаваемую консоль собственный диагностический мусор и делает её использование неудобным.
Автор: IlyaEvseev