Поднимаем сервис по сдаче в аренду виртуальных FreeBSD-серверов
Евгений Зобнин ака j1m (zobnin@gmail.com)
Сегодня мы применим возможности FreeBSD jail в несколько необычном свете и создадим целый сервис по сдаче в аренду виртуальных FreeBSD-машин. Сервис будет полностью автоматизированным, позволит использовать разные версии виртуальных окружений на одной физической машине и обособленные настройки для каждого сервера. И все это — с помощью нескольких простых шелл-скриптов.
Содержание:
- Один из способов поднять $$$ в кризисное время
- Немного теории
- Собираем все вместе
- Скрипты
- Вместо заключения
Один из способов поднять $$$ в кризисное время
Виртуальная FreeBSD-машина есть ни что иное, как полнофункциональное jail-окружение, которому выделен глобально маршрутизируемый IP-адрес. Задумка же сервиса в том, чтобы раздавать такие окружения на манер номеров в отеле. Клиент заходит на сайт нашей компании, заполняет необходимые поля формы, переводит чуток WMZ на наш кошелек и получает в ответ IP-адрес своего сервера, домен N-ого уровня и пароль/ключ к ssh-сервису. После этого клиент может делать со своим окружением все, что душе заблагорассудится ровно до того момента, пока не наступит время съезжать, то есть не истечет срок аренды, указанный клиентом в одном из полей регистрационной формы. По истечению этого срока сервер останавливается и удаляется.
Идея виртуальных отелей далеко не нова и, по сути, представляет собой пример «Облачной обработки данных» (), когда необходимое клиенту программное обеспечение предоставляется как сервис. Чтобы организовать свой бизнес по сдаче в аренду виртуальных FreeBSD-серверов от тебя потребуется наличие N-го количества белых IP-адресов (по одному на каждый виртуальный сервер), домен, закрепленный за одним из них, а также базовое понимание принципов работы FreeBSD.
Пользуясь знаниями, почерпнутыми из прошлой статьи, организовать сервис не так уж и сложно. Окружения jail подкупают своей простотой и легкостью развертывания, несколько скриптов-обвязок, и дело сделано. Вот только долго такой сервис не протянет — администраторы начнут плевать в монитор на второй день работы, клиенты завалят жалобами о низкой производительности, а сервер просто загнется, когда количество виртуальных серверов перевалит за первый десяток. Поэтому перед развертыванием и наймом SEO-шников необходимо тщательно продумать инфраструктуру будущего сервиса.
Итак, для начала сформулируем требования к нашему сервису:
-
Выделенный IP-адрес и доменное имя для каждого окружения. Имя выбирается клиентом во время заполнения формы на нашем сайте, IP-адрес извлекается из специального файла.
-
Полная свобода клиента в отношении jail-окружения. Это значит, никаких unionfs и nullfs для всего, кроме архива портов. Во время создания нового сервера окружение полностью копируется из специального каталога.
-
Общие каталоги /usr/ports/distfiles и /usr/ports/packages для всех окружений, чтобы порт, загруженный одним из клиентов, автоматически был доступен другим.
- Автоматизированное создание, удаление, запуск и мониторинг серверов.
- Поддержка разных версий виртуальных FreeBSD-серверов на одной физической машине.
-
Возможность быстрого переноса виртуального сервера на другой сетевой интерфейс или диск.
- Ежедневный бэкап виртуальных серверов.
-
Доступ по ssh с аутентификацией на основе ключей. Ключ генерируется во время создания виртуального сервера и отдается клиенту по https.
-
Разные типы аккаунтов, trial — для двухдневного испытания, base — обычный, extra, vip и т.д.
Теперь подумаем об инфраструктуре сервиса и о том, как реализовать все перечисленные требования и оставить задел на будущее, то есть обеспечить возможность добавления новых функций в случае необходимости. После долгих размышлений я пришел к такому варианту, который не только нагляден и прост, но и достаточно легок в реализации:
- Собираем FreeBSD-окружение в каталог /usr/jailbase/FreeBSD-версия.
-
Подготавливаем набор скриптов, которые будут управлять виртуальными серверами (создание/удаление, запуск/остановка и т.д.)
- Пишем инициализационный скрипт, который будет запускать все существующие окружения.
Кроме самого FreeBSD-окружения, я поместил в каталог /usr/jailbase еще несколько каталогов и конфигурационных файлов, необходимых для управления виртуальными серверами:
-
/usr/jailbase/FreeBSD-версия — чистая сборка FreeBSD, копируется в каждое окружение при его создании.
-
/usr/jailbase/distfiles-версия — монтируется к /usr/ports/distfiles каждого окружения при помощи nullfs.
-
/usr/jailbase/packages-версия — монтируется к /usr/ports/packages каждого окружения при помощи nullfs.
-
/usr/jailbase/db — база данных jail-окружений, в первой колонке которой перечислены IP-адреса всех окружений, во второй — базовый каталог (например, /usr/jail), в третьей — сетевой интерфейс, в четвертой — доменное имя, в пятой — версия, в шестой — email владельца, в седьмой — тип аккаунта, в восьмой — время истечения строка аренды в формате ГГММДДЧЧММ, в девятой — состояние.
-
/usr/jailbase/defaults — дефолтовые значения для различных полей конфигурационного файла.
-
/usr/jailbase/conf/ — каталог с настройками и ограничениями для разных типов аккаунтов: файлы trial, base, extra, vip, которые будут прочитаны скриптом запуска виртуального сервера.
Файл db — это база данных для всех виртуальных серверов в формате passwd (поля, разделенные двоеточием). Девятое поле «состояние» предназначено для упрощения администрирования серверов и может принимать следующие значения: none — не существует, disabled — временно отключен, ok — окружение создано и готово к запуску (или уже работает). В свежеустановленной системе, когда еще не существует виртуальных серверов, этот файл выполняет роль базы доступных IP-адресов, когда каждая строка не содержит других полей, кроме первого и девятого (примерно так: 192.168.0.1::::::::none). Скрипт, создающий новый сервер, просто находит первую строку с состоянием none и заполняет ее (тогда строка принимает примерно такой вид: 192.168.0.1:/usr/jail:ed0:jail.host.com:7.1-RELEASE:vasya@mail.ru:trial:0903031700:ok). В последующем скрипт запуска виртуальных серверов найдет запись с состоянием ok, перейдет в каталог, прописанный во втором поле, найдет каталог нужного окружения по IP-адресу и запустит в нем виртуальный сервер.
Перво-наперво мы должны создать базовое окружение в каталоге /usr/jailbase/FreeBSD-версия. Для этого переходим в /usr/src и вводим следующую последовательность команд:
# JAIL=/usr/jailbase/FreeBSD-`uname -r` # mkdir -p $JAIL # make world DESTDIR=$JAIL # make distribution DESTDIR=$JAIL
В моем распоряжении находится машина с FreeBSD 7.1, поэтому каталог базового окружения получил имя «/usr/jailbase/FreeBSD-7.1-RELEASE». Войдем в окружение и проведем базовую конфигурацию (доменное имя и IP-адрес на данном этапе не имеют значения):
# jail $JAIL base.jail 192.168.0.1 /bin/sh # touch /etc/fstab # newaliases # tzsetup # echo nameserver 127.0.0.1 > /etc/resolv.conf
Файл rc.conf не трогаем, он будет генерироваться скриптом addvserver автоматически для каждого виртуального сервера. Пароль суперпользователя не устанавливаем, для окружения будет создаваться пара ключей, доступ по паролю по умолчанию закрыт. Отредактируем /etc/motd и включим туда всю необходимую информацию о пользовании сервисом:
# vi /etc/motd
Выйдем из окружения, набрав exit. Скопируем дерево портов из базовой системы в $JAIL/usr:
# cp -a /usr/ports $JAIL/usr
Освободим точки монтирования дистфайлов и пакетов от лишнего мусора:
# rm -Rf $JAIL/usr/ports/distfiles $JAIL/usr/ports/packages # mkdir $JAIL/usr/ports/distfiles $JAIL/usr/ports/packages
Базовое окружение готово, конфигурационный файл будет отличаться для каждого окружения, поэтому заботу о его создании мы возложим на плечи скрипта addvserver.
Пойдем дальше и добавим каталоги /usr/jailbase/distfiles-версия и /usr/jailbase/packages-версия, которые будут подключаться к окружениям с помощью nullfs:
# mkdir /usr/jailbase/{distfiles,packages}-`uname -r`
Создадим файл дефолтовых значений для скрипта создания виртуального сервера (addvserver):
# vim /usr/jailbase/defaults # Дефолтовый сетевой интерфейс. На него будут вешаться вновь созданные окружения. IF=nfe0 # Дефолтовый каталог для виртуальных серверов. JAILDIR=/usr/jail
Формат базы данных виртуальных серверов я описал выше, отмечу лишь, что чистая база должна выглядеть как набор записей вида IP::::::::none, по одной записи на каждый доступный внешний IP-адрес. Всю работу по ее заполнению возьмут на себя соответствующие скрипты.
Теперь у нас есть все необходимое для создания и манипулирования виртуальными серверами, осталось только установить и настроить DNS-сервер на физической машине. Он нужен для привязки доменного имени к каждому виртуальному серверу.
Мы будем использовать BIND9, поскольку это единственный вменяемый DNS-сервер, позволяющий обновлять зоны, не останавливая работу демона. Пройдем через простые этапы его настройки:
1 Сгенерируем пару ключей, необходимых для аутентификации клиента, пожелавшего обновить зоны:
$ dnssec-keygen -a HMAC-MD5 -b 128 -r /dev/urandom -n USER NSUPDATE
Команда запишет в текущий каталог два файла с именами примерно такого вида: Knsupdate.+157+36521.key и Knsupdate.+157+36521.private, они понадобятся скриптам addvserver и delvserver для обновления зон, поэтому скопируем их в каталог /usr/jailbase:
# cp Knsupdate.* /usr/jailbase/
Откроем конфиг /etc/namedb/named.conf и запишем в него следующее:
# vim /etc/namedb/named.conf
// Позволим управлять сервером только с локальной машины
controls {
inet 127.0.0.1 allow { localhost; } keys { "NSUPDATE"; };
};
// Объявим ключ, значение опции secret можно взять из строки 'Key:'
// файла Knsupdate.+157+36521.private
key NSUPDATE {
algorithm HMAC-MD5.SIG-ALG.REG.INT;
secret VF5l+ZIdDcPjhDz1+eG3Jw==;
};
// Объявим зону host.com, которую ты должен заменить на свой домен
zone "host.com" {
type master;
file "host.com.db";
allow-update { key "NSUPDATE"; };
notify yes;
};
// Объявим обратную зону, в которой должен быть прописан сегмент,
// выделенный для твоих внешних IP-адресов
zone "67.16.172.in-addr.arpa" {
type master;
file "host.com-reverse.db";
allow-update { key "NSUPDATE"; };
notify yes;
};
Теперь настроим зоны host.com (локальные уже существуют):
# vim /etc/namedb/host.com.db $TTL 86400 @ IN SOA @ root ( 1 28800 7200 604800 86400 ) IN NS dns dns IN A 172.16.67.1 # vim /etc/namedb/host.com-reverse.db $TTL 86400 @ IN SOA @ root ( 1 28800 7200 604800 86400 ) IN NS dns 1 IN PTR dns.host.com.
Укажи в них все хосты, имеющие статические имена, больше эти файлы мы трогать не будем, за нас все сделают скрипты. Запустим bind:
# /etc/rc.d/named start
Для того чтобы наш сервис соответствовал всем предъявленным требованиям и функционировал правильно, необходимо написать как минимум 8 скриптов:
-
addvserver — создает виртуальный сервер путем добавления нового окружения, обновляет /usr/jailbase/db, добавляет нужную запись в DNS-таблицы.
-
delvserver — удаляет виртуальный сервер, принцип действия обратный.
-
disablevserver — отключает виртуальный сервер, не удаляя его (флаг ‘disabled‘).
-
enablevserver — включает отключенный виртуальный сервер (ставит флаг ‘ok‘).
-
startvserver — запускает существующий виртуальный сервер, сверяясь с /usr/jailbase/db.
-
stopvserver — останавливает существующий виртуальный сервер.
-
vservers — скрипт для каталога /usr/local/etc/rc.d, который запускает виртуальные сервера во время загрузки системы и останавливает во время шатдауна.
-
watchvservers — скрипт, исполняемый cron, следит за состоянием виртуальных серверов и останавливает их в случае необходимости (например, при истечении срока аренды).
По хорошему первые шесть скриптов лучше объединить в один, принимающий аргументы add, del, start и stop, но для сохранения простоты изложения оставим их в отдельных файлах.
Итак, скрипт номер один, addvserver (здесь и далее — только ключевые элементы скриптов, полные версии ты найдешь на прилагаемом к журналу диске):
# vim /usr/local/bin/addvserver
# Настраиваем переменные
HOSTNAME=$1; OSVER=$2; EMAIL=$3; ACTYPE=$4; TIME=$5; KEY=$6
JAILBASE=/usr/jailbase
DB=$JAILBASE/db
# Читаем дефолтовые настройки
. $JAILBASE/defaults
# Находим свободный IP-адрес
STRING=`grep ':none$' $DB`
IP=`echo $STRING | cut -d ':' -f 1`
# Копируем окружение…
cp -a $JAILBASE/FreeBSD-$OSVER $JAILDIR/$IP
# …и создаем для него файл инициализации
RCCONF=$JAILDIR/$IP/etc/rc.conf
echo "hostname=\"$HOSTNAME\"" > $RCCONF
echo "network_interfaces=\"\"" >> $RCCONF
echo "rpcbind_enable=\"NO\"" >> $RCCONF
echo "sshd_enable=\"YES\"" >> $RCCONF
# Копируем публичный ключ в каталог /root/.ssh
mkdir -p $JAILDIR/$IP/root/.ssh
cat $KEY >> /root/.ssh/authorized_keys2
# Добавляем новый сервер в базу DNS
UPREQ=$JAILBASE/upreq
echo "update add $HOSTNAME 86400 A $IP" > $UPREQ
echo "send" >> $UPREQ
nsupdate -k $JAILBASE/Knsupdate.*.private $UPREQ
rm -f $UPREQ
# Записываем информацию об окружении в базу данных
sed "/^$IP.*/s##$IP:$JAILDIR:$IF:$HOSTNAME:$OSVER:$EMAIL:$ACTYPE:$TIME:ok#" \
$DB > ${DB}.new
mv ${DB}.new $DB
# Возвращаем IP сервера
echo $IP
Скрипт принимает 6 аргументов (доменное имя виртуального сервера, версия ОС, email владельца, тип аккаунта, время истечения срока аренды, файл с публичным ключом клиента) и возвращает IP нового сервера.
Скрипт delvserver выполняет обратную процедуру, сначала он находит сервер в базе db, проверяет, существует ли сервер вообще (статус не должен быть none), а затем выполняет следующую последовательность действий (переменная STRING — это строка сервера из db):
# vim /usr/local/bin/delvserver
# Удаляем сервер
JAILDIR=`echo $STRING | cut -d ':' -f 2`
rm -Rf $JAILDIR/$IP $JAILDIR/${IP}.ipfw
# Удаляем сервер из базы DNS
HOSTNAME=`echo $STRING | cut -d ':' -f 4`
UPREQ=$JAILBASE/upreq
echo "update delete $HOSTNAME A" > $UPREQ
echo "send" >> $UPREQ
nsupdate -k $JAILBASE/Knsupdate.*.private $UPREQ
rm -f $UPREQ
# Устанавливаем для сервера статус none
NEWSTRING=`echo $STRING | sed "/$STATUS/s##none#"`
sed "/^$IP.*/s##$NEWSTRING#" $DB > ${DB}.new
mv ${DB}.new $DB
Скрипт disablevserver, предназначенный для временного отключения виртуального сервера, очень похож на delvserver, с тем лишь исключением, что он не удаляет сервер, а просто ставит на него флаг «disabled«, т.е. переменная NEWSTRING примет вид: «NEWSTRING=`echo $STRING | sed «/$STATUS/s##disabled#»`«.
Скрипт enablevserver — брат-близнец disablevserver, единственное отличие которого — установка флага ‘ok‘ вместо ‘disabled‘.
Скрипт startvserver довольно примитивен, он ищет переданный ему IP-адрес в базе, проверяет сервер на готовность (статус ‘ok‘) и запускает его. Набор правил номер 4 (ruleset 4) для команды devfs создан специально для jail-окружений и содержится в файле /etc/defaults/devfs.rules, чтобы скрипт работал корректно, этот файл необходимо положить в /etc.
# vim /usr/local/bin/startvserver # Извлекаем данные, необходимые для запуска JAILDIR=`echo $STRING | cut -d ':' -f 2` IF=`echo $STRING | cut -d ':' -f 3` HOSTNAME=`echo $STRING | cut -d ':' -f 4` OSVER=`echo $STRING | cut -d ':' -f 5` # Запускаем jail-сервер ifconfig $IF inet alias $IP mount -t devfs none $JAILDIR/$IP/dev devfs -m $JAILDIR/$IP/dev ruleset 4 mount -t procfs none $JAILDIR/$IP/proc mount_nullfs $JAILBASE/distfiles-$OSVER $JAILDIR/$IP/usr/ports/distfiles mount_nullfs $JAILBASE/packages-$OSVER $JAILDIR/$IP/usr/ports/packages jail $JAILDIR/$IP $HOSTNAME $IP /bin/sh /etc/rc
Скрипт stopvserver перед остановкой проделывает те же шаги, но плюс к этому проверяет, запущен ли сервер с помощью команды jls и извлекает его JID (первая колонка вывода jls):
# vim /usr/local/bin/stopvserver
# Проверяем, запущен ли сервер
STRING=`jls | grep $IP`
if [ ! "$STRING" ]; then
echo "Сервер $IP не запущен"
exit 3
fi
# Узнаем jid сервера
JID=`echo $STRING | cut -d ' ' -f 1`
# Останавливаем jail-сервер
killall -j $JID -TERM > /dev/null 2>&1
sleep 1
killall -j $JID -KILL > /dev/null 2>&1
umount $JAILDIR/$IP/usr/ports/distfiles
umount $JAILDIR/$IP/usr/ports/packages
umount $JAILDIR/$IP/dev
umount $JAILDIR/$IP/proc
ifconfig $IF inet -alias $IP
Чтобы не заморачиваться с ручным запуском серверов, напишем скрипт vservers, который проверяет опцию vservers_enable в /etc/rc.conf, запускает все готовые виртуальные серверы во время загрузки ОС и останавливает во время шатдауна. Ключевые строки этого файла:
# vim /usr/local/etc/rc.d/vservers
# Получаем список работоспособных серверов
VSERVERS=`cat /usr/jailbase/db | grep -e ':ok$' | cut -d ':' -f 1`
# Процедура запуска серверов
vservers_start()
{
for IP in $VSERVERS; do
/usr/local/bin/startvserver $IP
done
}
# Процедура остановки серверов
vservers_stop()
{
for IP in $VSERVERS; do
/usr/local/bin/stopvserver $IP
done
}
Вот и все. Рассмотренные скрипты автоматизируют всю грязную работу. Больше не нужно компилировать окружение исполнения, добавлять IP-псевдонимы, редактировать файлы зон, достаточно выполнить всего две команды, и виртуальный сервер создан, запущен и полностью готов к использованию:
# IP=`addvserver new.host.com 7.1-RELEASE vasya@mail.ru base \ 0906061200 /публичный/ключ/Васи` # startvserver $IP
Остановить и удалить сервер еще проще:
# stopvserver $IP # delvserver $IP
Осталось только нанять веб-разработчиков, которые создали бы поверх этого хозяйства простой интерфейс для регистрации пользователей, и написать небольшой скрипт, который бы запускался по крону и проверял, не истек ли срок аренды аккаунта (пример подобного скрипта ты найдешь на диске).
Мы создали вполне работоспособный каркас будущего сервиса. В следующей статье мы рассмотрим, как прикрутить к нему полноценный мониторинг, настройки для разных типов аккаунтов, систему бэкапа, наложим всевозможные ограничения и создадим гетерогенную систему, в которой смогут сосуществовать разные версии FreeBSD-окружений.
Статья опубликована в майском номере журнала «Xakep» за 2009 год.






Господа добрый день!
Если не трудно поясните
«достаточно выполнить всего две команды, и виртуальный сервер создан, запущен и полностью готов к использованию:
# IP=`addvserver new.host.com 7.1-RELEASE vasya@mail.ru base \
0906061200 /публичный/ключ/Васи`»
/публичный/ключ/Васи — это что и где его взять может быть его нужно как то сгенерировать.
привет
скрипты для построения сервиса по сдаче в аренду виртуальных FreeBSD-серверов мы выкладывали на прилагаемый к журналу (][_05_2009) DVD-диск
а скачать их можно отсюда:
http://www.synack.ru/download/2009/05/synack/article3/scripts/scripts_pack.zip
Добрый день еще раз.
Скрипты у меня есть, все сделал как описано в статье, журнал с диском и соответственно сами скрипты у меня есть.
ВОПРОС был в другом, /публичный/ключ/Васи – это что и где его взять может быть его нужно как то сгенерировать потому как скрипт addvserver при выполнении пишет ошибку:
addvserver: cannot create /root/.ssh/authorized_keys2: No such file or directory
Спасибо сам разобрался.