Продолжаем настройку сервиса по сдаче в аренду виртуальных FreeBSD серверов
Евгений Зобнин (j1m@synack.ru)
Рассматривая создание сервиса в прошлой статье, мы остановились на вполне работоспособном каркасе, который хорошо подходил для частного использования, но был далек от продакшн, т.е. от применения в среде производственной эксплуатации. Этот материал продолжит начатую тему, и мы прикрутим к нашему творению ограничение ресурсов для каждого виртуального сервера и полноценный мониторинг хост-системы.
Содержание:
- Работа напильником
- Ограничения
- Мониторинг
- Гетерогенность
- Что дальше?
- Врезка: Ограничения CPU/RAM
- Боковые выносы
Чтобы повысить эффективность сервиса, я решил несколько видоизменить его дизайн. Логика работы и формат конфигурационных файлов остались прежними, однако структура директорий претерпела изменения. Вместо каталогов для виртуальных серверов теперь используются виртуальные диски, подключаемые с помощью команд mdconfig и mount. Это дает большие преимущества:
- Виртуальный диск заметно быстрее копируется, так что на создание нового окружения уйдет не так много времени.
- Виртуальные диски позволят нам ограничивать дисковое пространство для каждого виртуального сервера.
- Они облегчают миграцию виртуальных серверов с одной машины на другую.
Чтобы преобразовать базовый каталог /usr/jailbase/FreeBSD-версия из прошлой статьи в базовый виртуальный диск размером в 2 Гб, выполни следующую последовательность команд:
# dd if=/dev/zero of=/usr/jailbase/FreeBSD-`uname -r`.2g.image \ bs=1m count=2k # bsdlabel -w -f /usr/jailbase/FreeBSD-`uname -r`.2g.image auto # mdconfig -a -t vnode -f /usr/jailbase/FreeBSD-`uname -r`.2g.image -u 0 # newfs md0c # mount /dev/md0c /mnt # cp -a /usr/jailbase/FreeBSD-`uname -r` /mnt # umount /mnt # mdconfig -d -u 0 # rm -Rf /usr/jailbase/FreeBSD-`uname -r`
В прошлой статье я упомянул о каталоге /usr/jailbase/conf, который хранит конфигурационные файлы для разных типов аккаунтов. Каждый такой файл содержит настройки ограничений, которые будут наложены командами addvserver и startvserver на виртуальный сервер во время его создания или запуска. Вот примерное содержимое одного из них:
# vi /usr/jailbase/conf/base # Размер образа (ограничение дискового пространства) SIZE=2g # Ограничение пропускной способности BANDWITH=1Mbit/s
Создай и заполни конфигурационные файлы для всех типов аккаунтов, которые будет поддерживать сервис:
# mkdir /usr/jailbase/conf
# touch /usr/jailbase/conf/{trial,base,extra,vip}
Каких-то особенных рекомендаций относительно значений опций дать не могу. Все зависит от ресурсов сервера, ширины канала и целевой аудитории сервиса.
Чтобы реализовать возможность накладывания ограничений, я модифицировал несколько скриптов. Первый из них — addvserver, который теперь копирует виртуальный диск вместо базового каталога:
# vi /usr/local/bin/addvserver
# Копируем и подключаем образ диска
mkdir $JAILDIR/$IP
cp $JAILBASE/FreeBSD-${OSVER}.${SIZE}.image $JAILDIR/${IP}.image
mdconfig -a -t vnode -f $JAILDIR/${IP}.image -u 99
mount /dev/md99c $JAILDIR/$IP
# Создаем файл инициализации для нового сервера
…
# Копируем публичный ключ в каталог /root/.ssh
…
# Отключаем образ
umount $JAILDIR/$IP
mdconfig -d -u 99
Тебе придется подготовить целый набор базовых виртуальных дисков, по одному на каждый тип аккаунта.
Скрипт startvserver подключает виртуальный диск и создает набор правил для ограничения пропускной способности. Все правила помещаются в отдельный набор (set), номер которого равен номеру виртуального диска плюс один. Так мы перекладываем работу по уникализации номеров наборов правил на плечи команды mdconfig.
# vi /usr/local/bin/startvserver
# Подключаем виртуальный диск сервера
MDNUM=`mdconfig -n -a -t vnode -f $JAILDIR/$IP.image`
mount /dev/md${MD}c $JAILDIR/$IP
echo $MDNUM > $JAILDIR/$IP.run
# Привязываем виртуальный сервер к сетевому интерфейсу…
ifconfig $IF inet alias $IP
# …и накладываем ограничения на пропускную способность
FWSETNUM=$(($MDNUM+1))
ipfw set disable $FWSETNUM
ipfw add set $FWSETNUM pipe ${FWSETNUM}0 ip from any to $IP
ipfw add set $FWSETNUM pipe ${FWSETNUM}1 ip from $IP to any
ipfw pipe ${FWSETNUM}0 config bw $BANDWITH
ipfw pipe ${FWSETNUM}1 config bw $BANDWITH
ipfw set enable $FWSETNUM
# Запускаем сервер
…
Скрипт stopvserver удаляет закрепленный за виртуальным сервером набор правил ipfw и отключает виртуальный диск.
# vi /usr/local/bin/stopvserver # Отключаем виртуальный диск MDNUM=`cat $JAILDIR/$IP.run` umount $JAILDIR/$IP mdconfig -d -u $MDNUM # Удаляем правила ipfw и IP-адрес виртуального сервера FWSETNUM=$((MDNUM+1)) ipfw delete set $FWSETNUM ifconfig $IF inet -alias $IP
Перед тем как приступить к настройке системы мониторинга, давай определимся с тем, что же мы все-таки собираемся мониторить. Дело в том, что одним из требований к нашему сервису была полная свобода клиента в отношении виртуального сервера. Поэтому мы не можем выполнять мониторинг сервисов клиентов, и им придется самим позаботиться об этом. C другой стороны, мы должны обеспечить бесперебойную работу всех виртуальных серверов, что потребует постоянного слежения за их работоспособностью.
Создать базовую систему мониторинга виртуальных серверов не так уж и сложно. Для этого надо всего лишь взять вывод утилиты jls и сравнить его со списком готовых к выполнению серверов (статус ‘ok‘ в /usr/jailbase/db). Если какой-то сервер готов, но не запущен — попытаться его запустить и снова сравнить списки. Если сервера вновь не окажется в списке — отключить его (установить статус ‘disabled‘) и начать бить тревогу (отправить письмо админу и создать специальный файл $JAILDIR/$IP.trouble).
У нас уже есть скрипт для cron, который останавливает и отключает виртуальные серверы, срок аренды которых истек. Добавим нужную функциональность прямо в него. Ниже приведен сокращенный вариант этого скрипта, он содержит базовый набор действий, выполняемых над каждым «активным» сервером:
# vi /usr/local/bin/watchvservers
# Проверяем, запущен ли сервер
check_running()
{
# Покидаем функцию, если сервер работает исправно
jls | grep $IP && return
# Иначе пробуем его запустить
/usr/local/bin/startvserver $IP > /tmp/startvserver.out 2>&1
sleep 15
# Запустился? Тогда покидаем функцию
jls | grep $IP && return
# Нет? Тогда отключаем его, генерируем файл trouble и отправляем
# письмо админу
/usr/local/bin/disablevserver $IP
cp /tmp/startvserver.out $JAILDIR/${IP}.trouble
cat $JAILDIR/${IP}.trouble | mail -s "watchvservers: проблемы \
с сервером $IP" root
exit
}
# Проверяем на истечение срока аренды
check_est_time()
{
# Останавливаем и отключаем сервер, если его срок аренды истек
if [ $CURTIME -ge $ESTTIME ]; then
/usr/local/bin/stopvserver $IP
/usr/local/bin/disablevserver $IP
# Отправляем письмо админу и владельцу
cat $JAILDIR/${IP}.expire | mail -s "watchvservers: истек срок аренды \
сервера $IP" root
cat /usr/jailbase/message_expire | mail -s "www.host.com: срок аренды \
вашего сервера истек"
$ACMAIL
fi
}
Пропишем скрипт в задания cron:
# crontab -e MAILTO=root */20 * * * * /usr/local/bin/watchvservers
Это все. Скрипт будет следить за работоспособностью виртуальных серверов и докладывать об истечении срока аренды или проблемах администратору. В первом случае администратор получит письмо с темой «watchvservers: проблемы с сервером <IP>» и выводом неудавшейся команды startvserver, который также будет записан в файл $JAILDIR/$IP.trouble. Задача админа в этом случае — подключиться к корневой машине, исправить проблему и вновь запустить виртуальный сервер. Во втором случае администратор получит сообщение "watchvservers: истек срок аренды сервера <IP>", после чего он может либо сразу удалить сервер командой delvserver, либо дождаться продления аренды владельцем (на его адрес тоже будет выслано специальное письмо, содержимое которого берется из файла /usr/jailbase/message_expire). В обоих случаях сервер будет переведен в состояние disabled и остановлен.
Команда mail подключается к локальному почтовому серверу для отправки письма, поэтому придется поднять Sendmail/Postfix или простой релей на основе ssmtp:
# cd /usr/ports/mail/ssmtp # make install replace clean
Конфигурируем:
# vi /usr/local/etc/ssmtp/ssmtp.conf # Кому пересылать почту, направленную пользователю root root=admin@host.com # Адрес почтового сервера mailhub=mail.host.com rewritedomain=host.com hostname=_HOSTNAME_
Добавляем в файл обратных адресов запись, указывающую на адрес, с которого будут приходить письма от любого процесса, запущенного с правами root:
# echo root:system@`hostname` > /usr/local/etc/ssmtp/revaliases
Корневая система также нуждается в постоянном наблюдении. Вот несколько параметров, которые требуют внимания:
- Работоспособность корневых сервисов
- Нагрузка на сетевые интерфейсы
- Нагрузка на CPU/RAM
- Нагрузка на диск
- Свободное дисковое пространство
- Целостность
Мы разделим систему наблюдения за хост-системой на две независимые части, первая из которых будет производить мониторинг ресурсов сервера и отдавать данные другой машине. Вторая будет следить за сбоями и аномалиями и оповещать в случае необходимости системного администратора.
Монитор munin, основанный на клиент-серверной архитектуре, прост и удобен в установке и настройке. Мы воспользуемся им для решения первой задачи. Для начала установим ноду munin, которая будет следить за параметрами системы и отдавать накопленные данные серверу:
# cd /usr/port/sysutils/munin-node # make install clean
Создаем новый конфигурационный файл:
# cd /usr/local/etc/munin # cp munin-node.conf.sample munin-node.conf
Открываем файл munin-node.conf и добавляем в него две строки:
# vi /usr/local/etc/munin/munin-node.conf # Имя хоста host_name jail.host.com # Адрес сервера, который будет собирать и отображать статистику allow ^172\.168\.0\.1$"
Нода munin использует плагины для наблюдения за параметрами системы. Плагины лежат в каталоге /usr/local/share/munin/plugins, но реально используются только те из них, ссылки на которые есть в каталоге /usr/local/etc/munin/plugins. Поэтому переходим в этот каталог и создаем ссылки на нужные плагины:
# cd plugins # for i in cpu df df_inode load memory netstat open_files \ swap vmstat; do ln -s /usr/local/share/munin/plugins/$i \ $PWD/$i; done
Также добавим ссылки на плагины if_ и if_errcoll_, которые предназначены для наблюдения за сетевыми интерфейсами (выполни эти команды для всех сетевых интерфейсов):
# ln -s /usr/local/share/munin/plugins/if_ $PWD/if_ed0 # ln -s /usr/local/share/munin/plugins/if_errcol_ $PWD/if_errcol_ed0
Запускаем munin-node и прописываем его в /etc/rc.conf для автоматической загрузки:
# /usr/local/etc/munin-node.sh start # echo "munin_node_enable=\"YES\"" >> /etc/rc.conf
На клиентской стороне все. Осталось настроить службу, которая будет опрашивать ноды и генерировать графики. Поэтому идем на сервер (который может быть той же машиной) и устанавливаем на нем munin-main:
# cd /usr/ports/sysutils/munin-main # make install clean
Открываем файл munin.conf:
# cd /usr/local/etc/munin # cp munin.conf.sample munin.conf
И добавляем в него следующие строки:
# vi /usr/local/etc/munin/munin.conf # Имя клиента c установленным munin-node [jail.host.com] # И его IP address 172.30.5.129 use_node_name yes
Больше ничего трогать не надо. Команда munin-cron, которую система портов заботливо вписала в задания cron, будет запускаться каждые 5 минут, собирать статистику с подчиненных нод, генерировать графики и помещать их в каталог /usr/local/www/munin. Чтобы просматривать графики, ты можешь либо поднять Web-сервер на этой машине, либо просто набрать «file:///usr/local/www/munin/index.html» в адресной строке браузера.
Возвращаемся на хост виртуальных серверов и приступаем к настройке monit, простого и удобного демона, следящего за порядком в нашей системе. Демон monit будет нашим сторожевым псом, которому мы прикажем сигнализировать обо всех неприятных ситуациях, таких как чрезмерная загруженность системы, заканчивающееся дисковое пространство или падение сервисов.
Устанавливаем monit:
# cd /usr/ports/sysutils/monit # make install clean
И добавляем в его конфиг следующее:
# vi /usr/local/etc/monitrc
# Проверка общих параметров системы: loadavg, занятая память,
# нагрузка на процессор
check system myhost.mydomain.tld
if loadavg (1min) > 4 then alert
if loadavg (5min) > 2 then alert
if memory usage > 85% then alert
if cpu usage (user) > 90% then alert
if cpu usage (system) > 30% then alert
if cpu usage (wait) > 20% then alert
# Проверка "замусоренности" файловой системы
# ad0s2 - это диск, смонтированный к /usr или к $JAILDIR
check device usrfs with path /dev/ad0s2
if space usage > 80% then alert
# Следим за работоспособностью демона sshd
check process sshd with pidfile /var/run/sshd.pid
start program = "/etc/rc.d/sshd start"
stop program = "/etc/rc.d/sshd stop"
if failed port 22 protocol ssh then restart
if 5 restarts within 5 cycles then timeout
Прописываем monit в /etc/rc.conf и запускаем:
# echo "monit_enable=\"YES\"" >> /etc/rc.conf # /usr/local/etc/rc.d/monit start
Пятое поле базы виртуальных серверов зарезервировано для хранения версии FreeBSD-окружения. С его помощью мы убиваем сразу двух зайцев. Во-первых, возможность выбора версии FreeBSD-сервера делает наш сервис более привлекательным для клиентов, некоторым из них в силу определенных причин может понадобиться конкретная ревизия ОС. Во-вторых, метод прямого указания версии позволяет сохранить добрую тысячу нервных клеток во время обновления корневой ОС до новой версии. Разработчики FreeBSD следуют давней традиции сохранения совместимости с прошлыми ядрами, так что после обновления ОС существующие окружения останутся полностью работоспособными.
В созданной нами системе существует только один вариант окружения, версия которого соответствует версии корневой ОС. В то же время FreeBSD 7.1 способна исполнять код, скомпилированный для предыдущих версий системы, вплоть до 4.11. Поэтому, если возникнет такая необходимость, просто скачай один из предыдущих релизов, установи его на соседний раздел и скопируй в новый базовый виртуальный диск. Срез нужной версии дерева портов можно получить с помощью cvsup:
# vi ~/ports-supfile
*default host=cvsup2.ru.FreeBSD.org
*default base=/var/db
*default prefix=/usr/jailbase
*default release=cvs tag=RELENG_6_4
*default delete use-rel-suffix
*default compress
ports-all
# cvsup ~/ports-supfile
# mv /usr/jailbase/{ports,ports-6.4-RELEASE}
Итак, наша система управления и поддержки виртуальных серверов полностью готова к использованию, но как применить ее возможности для создания полноценного сервиса? Есть 3 варианта:
-
Все на одной машине. В этом случае BIND, Web-сервер и созданная нами система находятся на одной машине. На Web-сервере крутится сайт сервиса, который предоставляет клиенту возможность создать виртуальный сервер. Для этого он должен заполнить несколько форм, где указать доменное имя, свой почтовый адрес, срок аренды, тип аккаунта и т.д. Скрипт (может быть написан на PHP, Python, Perl) обрабатывает поля формы, формирует из них запрос к скрипту addvserver и вызывает его. Затем он запускает скрипт startvserver, а клиенту возвращает страничку с адресом его сервера и сгенерированным ключом для доступа по SSH. Когда срок аренды истечет, watchvservers автоматически остановит сервер и отправит клиенту уведомление.
-
Масштабируемая система. Одна машина едва ли выдержит более 15 виртуальных серверов, поэтому мы должны иметь возможность в любой момент добавить новую машину в инфраструктуру. Масштабируемая система предполагает наличие центрального сервера, обслуживающего Web-сервер, BIND, Sendmail, сервер munin и отправляющего запросы на создание виртуальных серверов на другие машины. Для ее реализации необходимо всего лишь предустановить на каждую машину нашу систему управления виртуальными серверами, munin-node, minit и ssmtp, а на центральный сервер добавить небольшую обертку для addvserver, которая будет сверяться со специальным списком машин, обслуживающих виртуальные сервера, выбирать из них ту, на которой крутится наименьшее количество серверов, подключаться к ней по SSH и выполнять команду addvserver, а затем снова открывать список и увеличивать число виртуальных серверов для этой машины на единицу (плюс обертка для delvserver).
-
Правильная масштабируемая система. Второй вариант неплох, но предполагает хранение базы серверов /usr/jailbase/db на каждой машине, что усложняет обслуживание и затрудняет миграцию виртуальных серверов между хостами. Правильная масштабируемая система должна использовать единую базу данных с интерфейсом SQL вместо файла db. В этом случае база данных хранится на центральном сервере, а скрипты управления виртуальными серверами подключаются к ней вместо обращения к локальному файлу db. Реализация этой идеи потребует значительной модификации скриптов управления и добавления дополнительной колонки «IP машины, на которой крутится виртуальный сервер» к базе данных, чтобы скрипт мог найти виртуальные сервера, принадлежащие только его машине.
В своем нынешнем состоянии подсистема jail-окружений FreeBSD не поддерживает ограничения на объем виртуальной памяти или время использования процессора. Это серьезный недостаток, над устранением которого работают уже не первый год. В 2006 году Крис Джонс попал с проектом реализации идеи ограничений на Google Summer of Code и даже представил работоспособные патчи для шестой ветки, однако разработчики FreeBSD не приняли их. Позднее Кристофер Тюнс портировал на FreeBSD 7.0 часть этих патчей, отвечающую за ограничение объемов памяти, но не осилил порт остального кода. Наработки обоих энтузиастов доступны на страничке , но не стоит рассчитывать на стабильность или высокую производительность ядра после наложения этих патчей.
DVD
-
Полные версии конфигов и скриптов (с комментариями) ты найдешь на прилагаемом к журналу диске.
Статья опубликована в июньском номере журнала «Xakep» за 2009 год.
-









Подскажите пожалуйста где можно взять скрипты к этой статье?
вот они:
http://www.synack.ru/download/2009/06/synack/article3/scripts/scripts_pack.zip
Доброго времени суток.
Расскажите, как у Вас сетевой интерфейс jail мониторится munin-ом?
У меня JAIL (на ноде vdsmanager), munin мониторит CPU, uptime, LA, но вот if_ и RAM не мониторит..
Откройте ваш секрет :)