Pull to refresh

Запуск FreeBSD в Linux KVM

Reading time 9 min
Views 41K
Задача: запустить 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 при начальном освоении.

Для более-менее серьёзного использования имеют смысл два варианта:
  1. базовая система предоставляет гостевым прозрачный доступ во внешнюю сеть через т.н. сетевой мост («network bridge»),
  2. базовая система работает как маршрутизатор между внешней и гостевой сетью («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:%02x\n", $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 направляет в создаваемую консоль собственный диагностический мусор и делает её использование неудобным.
Tags:
Hubs:
+16
Comments 26
Comments Comments 26

Articles