Блог Конфуция
Mac OS Leopard + launchd + VBoxHeadless 28.04.2009

Вот и мы решились, наконец, поиграть в виртуализацию. Поставили Ubuntu в VirtualBox на леопарде, а в убунте настроили OpenVZ. Все это крутится на мак мини с 4-мя гигами оперативы. Мак тут, конечно, не особо нужен, но покупать здоровенный сервак или проводастый писюк совсем не хотелось.

Все это добро (Mac mini MB463LL/A, Ubuntu 8.04, VirtualBox 2.2, OpenVZ kernel 2.6.24-23-openvz) завелось с полоборота. Линукс совершенно не напрягает мак мини своим присутствием. Из отведенного гига для виртуальной машины реально расходуется чуть больше 300. Мак летает, линукс летает, контейнеры тоже летают. Все прекрасно. Пока не настает время перезагружаться.

Ясное дело, после загрузки виртуалбокс не запускает никакие машины. Это грустно, но не смертельно. В комплекте виртуалбокса есть консольная утилита для запуска виртальных машин без привязки к пользовательскому интерфейсу. Пользоваться ей очень просто: VBoxHeadless -startvm $VMID. Идентификатор виртуальной машины узнать нетрудно: эта команда покажет подробный список машин VBoxManage list vms.

Но изюминка этой статьи вовсе не в восторгах от современных технологий виртуализации. Дело в том, что запускать виртуалбокс при старте и выключать его при перезагрузке — задача нетривиальная.

Для начала скажу, что мы запускаем VBoxHeadless с помощью встроенного в макос launchd. Он умеет перезапускать процесс, если тот накернился. launchd умеет выдерживать паузы, запускать задания с определнным интервалом, при подключении диска. Да он, вообще, много чего умеет. В этом ему близок полюбившийся мне UpStart.

Чтобы виртуальная машина запускалась при старте, необходимо положить следующий скрипт в папку /Library/LaunchDaemons:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <dict>
    <key>SuccessfulExit</key>
    <false/>
  </dict>
  <key>Label</key>
  <string>ubuntu-openvz</string>
  <key>ProgramArguments</key>
  <array>
    <string>VBoxHeadless</string>
    <string>-startvm</string>
    <string>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</string>
  </array>
  <key>Nice</key>
  <integer>3</integer>
  <key>GroupName</key>
  <string>staff</string>
  <key>UserName</key>
  <string>someuser</string>
</dict>
</plist>
Вместо иксов нужно подставить ид машины, которую будем запускать. И еще надо сменить владельца этого файла на рута. Назвать файл можно как вам удобно, но принят формат домен.задача. Например, ru.company.virtualbox.ubuntu-vz.

А проблема со свзякой ланчд+виртуалбокс в том, что при перезагрузке ланчд шлет процессу виртуалбокса простой SIGTERM. После чего виртуальная машина ресетится и отключается. Состояние линукса внутри виртуалбокса не сохраняется никаким образом. Линукс даже не узнает, что машина скоро погаснет.

В документации так прямо и написано: не умеешь ловить сигтерм — не лезь в ланчд. И как раз обучение виртуалбокса правильно обрабатывать сигтерм и есть та задача, которую решать было интересно.

Сначала заменим в ланчдемонском конфиге эти строки:

  <array>
    <string>VBoxHeadless</string>
    <string>-startvm</string>
    <string>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</string>
  </array>
на эти:
  <array>
      <string>/usr/local/bin/vboxheadless-wrapper</string>
      <string>b25ac594-9b5e-4ac7-bc38-c105947bf2ad</string>
  </array>

Внутри vboxheadless-wrapper написано следующее:

#!/usr/bin/env perl
use strict;

my ($vm) = @ARGV;
our $stopping = 0;

$SIG{TERM} = sub { stop($vm); };

start($vm);
exit 0;


sub start
{
	my $vm = shift;
	# waiting for VB drivers to load
	sleep 1 while scalar(grep { /org.virtualbox/ } `/usr/sbin/kextstat`) < 3;
	my $res = system "/usr/bin/VBoxHeadless -startvm $vm";
	# sleeping until exit()
	unless ($res) { sleep while 1 };
}

sub stop
{
	return if $stopping;
	$stopping = 1;
	my $vm = shift;
	exit system "/usr/bin/VBoxManage controlvm $vm savestate";
}

Этот код делает вот что. Сначала запускает виртуалбокс и засыпает в вызове system. Он, скорее всего, не проснется раньше времени, но на всякий случай следующей строкой мы подстраховываемся. Далее, машина какое-то время работает. Потом кто-то перезагружает макос и скрипту от ланчд приходит сигтерм. Скрипт просит виртуалбокс сохранить состояние машины на диск. После этого процесс виртуальной машины успешно завершается и скрипт выходит с кодом 0.

Все это на самом деле очень просто. Но эта конструкция все равно не заработала. А из-за того, что ланчд запускал наш скрипт еще до того, как ядро макоса загружало дрова виртуалбокса. Как раз от преждевременного запуска нас спасает строка sleep 1 while scalar(grep { /org.virtualbox/ } `/usr/sbin/kextstat`) < 3;. Тут перл ждет, пока не загрузится хотя бы три драйвера с именем, содержащим org.virtualbox. В моем случае это были следующие кексты: org.virtualbox.kext.VBoxDrv, org.virtualbox.kext.VBoxUSB, org.virtualbox.kext.VBoxNetFlt.

После этого, виртуальной машиной можно управлять с помощью ланчд:

launchctl start ubuntu-openvz
launchctl stop ubuntu-openvz

Эх, люблю потанцевать! Особенно проникновенными считаю танцы с бубном :)

Самый кайф в том, что можно подключиться по ссш, запустить, например, ping или ab, перезагрузить железку, и процесс пойдет дальше. При этом ab, пережив перезагрзку компа, не сообщил ни об одной ошибке. Состояние виртуальной машины сохраняется за 4 секунды. Восстанавливается — за 3. Восстановить виртуальную машину можно уже на другом компе, не потеряв ни одного соединения. Горячий перенос, кстати, умеет как сам виртуалбокс, так и опенвз внутри него :)

Удачной виртуализации!

Теги:
  • сервер
  • launchd
  • mac
  • openvz
  • ubuntu
  • virtualbox
Очень жду ваших комментариев на почту или на гитхаб.