[Из песочницы] KVM (недо)VDI с одноразовыми виртуальными машинами с помощью bash
Кому предназначена данная статья
Данная статья может быть интересна системным администраторам, перед которыми вставала задача создать сервис «одноразовых» рабочих мест.
Пролог
В отдел ИТ сопровождения молодой динамично развивающейся компании с небольшой региональной сетью, обратились с просьбой организовать «станции самообслуживания» для использования их внешними клиентами. Данные станции предполагалось использовать для регистрации на внешних порталах компании, загрузки данных с внешних устройств, работы с государственными порталами.
Важным аспектом являлся тот факт, что большая часть программного обеспечения «заточена» под MS Windows (например, «Декларация»), а несмотря на движение в сторону открытых форматов, MS Office остается доминирующим стандартом при обмене электронными документами. Таким образом, отказаться от MS Windows при решении данной задачи мы не могли.
Основной проблемой виделось возможность накопления различных данных пользовательских сеансов, которые могли бы привести к их утечке третьим лицам. Такая ситуация уже подвела МФЦ. Но в отличие от квазигосудаственного (государственное автономное учреждение) МФЦ, за подобные недочеты не государственные организации будут наказаны значительно сильнее. Следующей по критичности проблемой было требование работы с внешними носителями данных, на которых, однозначно, будет куча зловредных зловредов. Вероятность заноса зловредов из сети интернет, рассматривалась как менее возможная, в силу ограничения выхода в интернет по белому списку адресов.К проработке требований подключились сотрудники других отделов, внося свои требования и пожелания, итоговые требования выглядели следующим образом:
Требования ИБ
- После использования все пользовательские данные (включая временные файлы и ключи реестра) должны удаляться.
- Все процессы, запущенные пользователем, по окончанию работы должны завершаться.
- Выход в интернет по белому списку адресов.
- Ограничения на возможность запуска стороннего кода.
- В случае простоя сеанса более 5 минут, сеанс должен автоматически завершаться, станция должна произвести очистку.
Требования заказчика
- Количество клиентских станций на филиал — не более 4-х.
- Минимальное время ожидания готовности системы, от момента «сел за стул» до начала работы с клиентским ПО.
- Возможность подключения периферийных устройств (сканеры, флэшки) непосредственно с места установки «станции самообслуживания».
- Пожелания заказчика
- Демонстрация рекламных материалов (картинки) в момент простоя комплекса.
Муки творчества
Вдоволь наигравшись с виндовыми livecd, мы пришли к единодушному выводу, что получающееся решение не удовлетворяет минимум 3 критичным пунктам. Они либо долго грузятся, либо не совсем live, либо кастомизация их была сопряжена с дикими болями. Возможно, мы плохо искали, и вы сможете посоветовать набор какой-то инструментов, буду благодарен.
Дальше мы стали смотреть в сторону VDI, но для этой задачи большинство решений либо слишком дороги, либо требуют пристального внимания. А хотелось простой инструмент, с минимальным количеством магии, большинство проблем которого можно было бы решить простой перезагрузкой/перезапуском сервиса. К счастью, у нас было серверное оборудование, low end класса в филиалах, от выводящегося из эксплуатации сервиса, которое мы могли использовать для технологической базы.
Что в итоге получилось? А вот, что в итоге получилось, я вам рассказать не смогу, ибо NDA, но в процессе поисков мы разработали интересную схему, которая хорошо себя показала в лабораторных испытаниях, хоть и не пошла в серию.
Немного дисклаймеров: автор не претендует на то, что предложенное решение полностью решает все поставленные задачи и делает это добровольно и с песней. Автор заранее согласен с утверждением, что Sein Englishe sprache is zehr schlecht. Так как решение более не развивается, на bug fix или изменение функционала рассчитывать не приходится, всё в ваших руках. Автор предполагает, что вы хоть немного знакомы с KVM и читали обзорную статью по Spice протоколу ну и немного работали с Centos или иным GNU Linux дистрибутивом.
В этой статье я хотел бы разобрать костяк получившегося решения, а именно взаимодействие клиента и сервера и суть процессов по жизненному циклу виртуальных машин в рамках рассматриваемого решения. Если статья окажется интересна публике, я опишу детали реализации live образов для создания тонких клиентов на базе Fedora и расскажу о деталях настройки виртуальных машин и KVM сервера для оптимизации производительности и защищенности.
Если взять цветной бумаги,
Краски, кисточки и клей,
И еще чуть-чуть сноровки…
Можно сделать сто рублей!
Схема и описание тестового стенда
Все оборудование располагается внутри сети филиала, наружу выходит только интернет канал. Прокси сервер исторически уже был, ничего экстраординарного он из себя не представляет. Но именно на нем, в числе прочего, будет происходить фильтрация траффика от виртуальных машин (сокр. ВМ далее в тексте). Ничего не мешает разместить этот сервис на KVM сервере, единственное, что надо смотреть как изменится нагрузка от него на дисковую подсистему.
Client Station — собственно, «станции самообслуживания», «фронтенд» нашего сервиса. Представляют из себя неттопы Lenovo IdeaCentre. Чем хорош этот агрегат? Да почти всем, особенно радует большое количество USB разъемов и кардридер на лицевой панели. В нашей схеме в кардридер вставлена SD карта с включенной аппаратной защитой от записи, на которою записан модифицированный live образ Fedora 28. Само собой, к неттопу подключен монитор, клавиатура и мышь.
Switch — ничем не примечательный аппаратный switch второго уровня, стоит в серверной и мигает лампочками. Ни к каким сетям, кроме сети «станций самообслуживания» не подключен.
KVM_Server — ядро схемы, в стендовых испытаниях Core 2 Quad Q9650 с 8 Гб оперативной памяти уверенно тянул на себе 3 виртуальных машины с Windows10. Дисковая подсистема — adaptec 3405 2 диска Raid 1 + SSD. В полевых испытаниях Xeon 1220 более серьезный LSI 9260 + SSD легко тянули 5–6 ВМ. Сервера нам достались бы от выбывшего сервиса, капитальных затрат было бы не много. На этом сервере (ах) развернута система виртуализации KVM с пулом виртуальных машин pool_Vm.
Vm — виртуальная машина, бэкэнд нашего сервиса. В ней происходит работа пользователя.
Enp5s0 — сетевой интерфейс смотрящий в сторону сети «станций самообслуживания», на нем живут dhcpd, ntpd, httpd, и xinetd слушает «signal» порт.
Lo0 — псевдоинтерфейс обратной петли. Стандартный.
Spice_console — Очень интересная вещь, дело в том, что в отличие от классического RDP, при развороте связки KVM+Spice protocol, появляется дополнительная сущность — порт консоли виртуальной машины. Фактически, подключаясь на этот TCP порт, мы получаем консоль Vm, без необходимости подключатся к Vm через её сетевой интерфейс. Всё взаимодействие с Vm по передаче сигнала, сервер берет на себя. Ближайший аналог по функции — IPKVM.Т. е. на этот порт передается изображение монитора ВМ, на него же передаются данные о перемещении мыши, и (что самое главное) взаимодействие через Spice протокол позволяет бесшовно перенаправлять USB устройства в виртуальную машину, словно это устройство подключено к самой Vm. Проверено для флэш накопителей, сканеров, вэб-камер.
Vnet0, virbr0 и виртуальные сетевые карты Vm образуют сеть виртуальных машин.
Как ЭТО работает
Со стороны Client Station
Клиентская станция загружается в графическом режиме с модифицированного live образа Fedora 28, получает ip адрес по dhcp из адресного пространства сети 169.254.24.0. В процессе загрузки создаются правила файервола, позволяющие производить соединения к «signal» и «spice» портам сервера. После завершения загрузки станция ждет авторизации пользователя «Client». После авторизации пользователя происходит запуск менеджера рабочих столов «openbox» и выполнение скрипта автозапуска autostart от имени авторизовавшегося пользователя. Среди прочего, скрипт автозапуска запускает скрипт remote.sh.
#!/bin/sh
server_ip=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "server_ip" \
|/usr/bin/cut -d "=" -f2)
vdi_signal_port=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "vdi_signal_port" \
|/usr/bin/cut -d "=" -f2)
vdi_spice_port=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "vdi_spice_port" \
|/usr/bin/cut -d "=" -f2)
animation_folder=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "animation_folder" \
|/usr/bin/cut -d "=" -f2)
process=/usr/bin/remote-viewer
while true
do
if [ -z `/usr/bin/pidof feh` ]
then
/usr/bin/echo $animation_folder
/usr/bin/feh -N -x -D1 $animation_folder &
else
/usr/bin/echo
fi
/usr/bin/nc -i 1 $server_ip $vdi_signal_port |while read line
do
if /usr/bin/echo "$line" |/usr/bin/grep "RULE ADDED, CONNECT NOW!"
then
/usr/bin/killall feh
pid_process=$($process "spice://$server_ip:$vdi_spice_port" \
"--spice-disable-audio" "--spice-disable-effects=animation" \
"--spice-preferred-compression=auto-glz" "-k" \
"--kiosk-quit=on-disconnect" | /bin/echo $!)
/usr/bin/wait $pid_process
/usr/bin/killall -u $USER
exit
else
/usr/bin/echo $line >> /var/log/remote.log
fi
done
done
server_ip=192.168.101.2
vdi_signal_port=5905
vdi_spice_port=5906
animation_folder=/usr/share/backgrounds/animation
background_folder=/usr/share/backgrounds2/fedora-workstation
Скрипт remote.sh берет настройки из файла конфигурации /etc/client.conf и производит с помощью nc подключение на «signal» порт KVM сервера и получает поток данных от сервера, среди которых ожидает строки «RULE ADDED, CONNECT NOW». При получении искомой строки запускается процесс remote-viewer в режиме киоска устанавливая соединение на «spice» порт сервера. Выполнение скрипта приостанавливается до момента окончания исполнения remote-viewer-а.
Remote-viewer подключаясь на «spice» порт, за счет редиректа на стороне сервера, попадает на порт «spice_console» интерфейса lo0 т.е. на консоль виртуальной машины и происходит, непосредственно, работа пользователя. В процессе ожидания подключения, пользователю демонстрируется bullshit animation, в виде слайд-шоу из jpeg файлов, путь к каталогу с картинками определяется значением переменной animation_folder из конфигурационного файла.
При потере соединения с «spice_console» портом виртуальной машины, сигнализирующего об выключении/перезагрузке виртуальной машины (т.е. фактическим окончанием сессии пользователя), происходит завершения всех процессов, запущенных от имени авторизовавшегося пользователя, что приводит к перезапуску lightdm и возврату на экран авторизации.
Со стороны KVM Server
На «signal» порту сетевой карты enp5s0 ждет соединения xinetd. После коннекта на «signal» порт xinetd запускает скрипт vm_manager.sh без передачи ему каких-либо вводных параметров и перенаправляет результат выполнения скрипта в nc сессию Client Station.
service vdi_signal
{
port = 5905
socket_type = stream
protocol = tcp
wait = no
user = root
server = /home/admin/scripts_vdi_new/vm_manager.sh
}
#!/usr/bin/sh
##
SRV_SCRIPTS_DIR=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_scripts_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR"
export SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR
SRV_POOL_SIZE=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_pool_size" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_POOL_SIZE=$SRV_POOL_SIZE"
export "SRV_POOL_SIZE=$SRV_POOL_SIZE"
SRV_START_PORT_POOL=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_start_port_pool" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo SRV_START_PORT_POOL=$SRV_START_PORT_POOL
export SRV_START_PORT_POOL=$SRV_START_PORT_POOL
SRV_TMP_DIR=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_tmp_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_TMP_DIR=$SRV_TMP_DIR"
export SRV_TMP_DIR=$SRV_TMP_DIR
date=$(/usr/bin/date)
# #
/usr/bin/echo "# $date START EXECUTE VM_MANAGER.SH #"
make_connect_to_vm() {
##
/usr/bin/echo "READING CLEAN.LIST AND CHECK PORT STATE"
##
if [ -z `/usr/bin/cat $SRV_TMP_DIR/clear.list` ]
then
/usr/bin/echo "NO AVALIBLE PORTS IN CLEAN.LIST FOUND"
/usr/bin/echo "Will try to make housekeeper, and create new vm"
make_housekeeper
else
##
/usr/bin/cat $SRV_TMP_DIR/clear.list |while read line
do
clear_vm_port=$(($line))
/bin/echo "FOUND PORT $clear_vm_port IN CLEAN.LIST. TRY NETSTAT" \
"CHECK FOR PORT=$clear_vm_port"
##
if /usr/bin/netstat -lnt |/usr/bin/grep ":$clear_vm_port" > /dev/null
then
/bin/echo "$clear_vm_port IS LISTEN"
##
if /usr/bin/netstat -nt |/usr/bin/grep ":$clear_vm_port" \
|/usr/bin/grep "ESTABLISHED" > /dev/null
then
##
/bin/echo "$clear_vm_port IS ALREADY CONNECTED, MOVE PORT TO WASTE.LIST"
/usr/bin/sed -i "/$clear_vm_port/d" $SRV_TMP_DIR/clear.list
/usr/bin/echo $clear_vm_port >> $SRV_TMP_DIR/waste.list
else
###
/usr/bin/echo "OK, $clear_vm_port IS NOT ALREADY CONNECTED"
/usr/bin/sed -i "/$clear_vm_port/d" $SRV_TMP_DIR/clear.list
/usr/bin/echo $clear_vm_port >> $SRV_TMP_DIR/conn_wait.list
$SRV_SCRIPTS_DIR/vm_connect.sh $clear_vm_port
##
/bin/echo "TRY TO CLEAN VM IN WASTE.LIST AND CREATE NEW VM"
make_housekeeper
/usr/bin/echo "# $date STOP EXECUTE VM_MANAGER.SH#"
exit
fi
else
##
/bin/echo " "$clear_vm_port" is NOT LISTEN. REMOVE PORT FROM CLEAR.LIST"
/usr/bin/sed -i "/$clear_vm_port/d" $SRV_TMP_DIR/clear.list
/usr/bin/echo $clear_vm_port >> $SRV_TMP_DIR/waste.list
make_housekeeper
fi
done
fi
}
make_housekeeper() {
/usr/bin/echo "=Execute housekeeper="
/usr/bin/cat $SRV_TMP_DIR/waste.list |while read line
do
/usr/bin/echo "$line"
if /usr/bin/netstat -lnt |/usr/bin/grep ":$line" > /dev/null
then
/bin/echo "port_alive, vm is running"
if /usr/bin/netstat -nt |/usr/bin/grep ":$line" \
|/usr/bin/grep "ESTABLISHED" > /dev/null
then
/bin/echo "port_in_use can't delete vm!!!"
else
/bin/echo "port_not in use. Deleting vm"
/usr/bin/sed -i "/$line/d" $SRV_TMP_DIR/waste.list
/usr/bin/echo $line >> $SRV_TMP_DIR/recycle.list
$SRV_SCRIPTS_DIR/vm_delete.sh $line
fi
else
/usr/bin/echo "posible vm is already off. Deleting vm"
/usr/bin/echo "MOVE VM IN OFF STATE $line FROM WASTE.LIST TO" \
"RECYCLE.LIST AND DELETE VM"
/usr/bin/sed -i "/$line/d" $SRV_TMP_DIR/waste.list
/usr/bin/echo $line >> $SRV_TMP_DIR/recycle.list
$SRV_SCRIPTS_DIR/vm_delete.sh "$line"
fi
done
create_clear_vm
}
create_clear_vm() {
/usr/bin/echo "=Create new VM="
while [ $SRV_POOL_SIZE -gt 0 ]
do
new_vm_port=$(($SRV_START_PORT_POOL+$SRV_POOL_SIZE))
/usr/bin/echo "new_vm_port=$new_vm_port"
if /usr/bin/grep "$new_vm_port" $SRV_TMP_DIR/clear.list > /dev/null
then
/usr/bin/echo "$new_vm_port port is already defined in clear.list"
else
if /usr/bin/grep "$new_vm_port" $SRV_TMP_DIR/waste.list > /dev/null
then
/usr/bin/echo "$new_vm_port port is already defined in waste.list"
else
if /usr/bin/grep "$new_vm_port" $SRV_TMP_DIR/recycle.list > /dev/null
then
/usr/bin/echo "$new_vm_port PORT IS ALREADY DEFINED IN RECYCLE LIST"
else
if /usr/bin/grep "$new_vm_port" $SRV_TMP_DIR/conn_wait.list > /dev/null
then
/usr/bin/echo "$new_vm_port PORT IS ALREADY DEFINED IN CONN_WAIT LIST"
else
/usr/bin/echo "PORT IN NOT DEFINED IN NO ONE LIST WILL CREATE" \
"VM ON PORT $new_vm_port"
/usr/bin/echo $new_vm_port >> $SRV_TMP_DIR/recycle.list
$SRV_SCRIPTS_DIR/vm_create.sh $new_vm_port
fi
fi
fi
fi
SRV_POOL_SIZE=$(($SRV_POOL_SIZE-1))
done
/usr/bin/echo "# $date STOP EXECUTE VM_MANAGER.SH #"
}
make_connect_to_vm |/usr/bin/tee -a /var/log/vm_manager.log
Скрипт vm_manager.sh производит чтение файла конфигурации, производит оценку состояния виртуальных машин в пуле по нескольким параметрам, а именно: сколько VM развернуто, есть ли свободные чистые VM. Для этого он читает файл clear.list в котором содержатся номера «spice_console» портов «свежесозданных» (см. ниже цикл создания В.М.) виртуальных машин и проверяет наличие установленного соединения с ними. При обнаружении порта с установленным сетевым соединением, (чего быть категорически не должно) выводится предупреждение и порт переносится в waste.list При обнаружении первого порта из файла clear.list с которым в настоящий момент нет соединения vm_manager.sh вызывает скрипт vm_connect.sh и передает ему в качестве параметра номер этого порта.
#!/bin/sh
date=$(/usr/bin/date)
/usr/bin/echo "#" "$date" "START EXECUTE VM_CONNECT.SH#"
##
free_port="$1"
input_iface=$(/usr/bin/cat /etc/vm_manager.conf |/usr/bin/grep "input_iface" \
|/usr/bin/cut -d "=" -f2)
/usr/bin/echo "input_iface=$input_iface"
vdi_spice_port=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "vdi_spice_port" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "vdi_spice_port=$vdi_spice_port"
count_conn_tryes=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "count_conn_tryes" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "count_conn_tryes=$count_conn_tryes"
# #
##
/usr/bin/echo "create rule for port" $free_port
/usr/sbin/iptables -I INPUT -i $input_iface -p tcp -m tcp --dport \
$free_port -j ACCEPT
/usr/sbin/iptables -I OUTPUT -o $input_iface -p tcp -m tcp --sport \
$free_port -j ACCEPT
/usr/sbin/iptables -t nat -I PREROUTING -p tcp -i $input_iface --dport \
$vdi_spice_port -j DNAT --to-destination 127.0.0.1:$free_port
/usr/bin/echo "RULE ADDED, CONNECT NOW!"
# #
##
while [ $count_conn_tryes -gt 0 ]
do
if /usr/bin/netstat -nt |/usr/bin/grep ":$free_port" \
|/usr/bin/grep "ESTABLISHED" > /dev/null
then
/bin/echo "$free_port NOW in use!!!"
/usr/bin/sleep 1s
/usr/sbin/iptables -t nat -D PREROUTING -p tcp -i $input_iface --dport \
$vdi_spice_port -j DNAT --to-destination 127.0.0.1:$free_port
/usr/sbin/iptables -D INPUT -i $input_iface -p tcp -m tcp --dport \
$free_port -j ACCEPT
/usr/sbin/iptables -D OUTPUT -o $input_iface -p tcp -m tcp --sport \
$free_port -j ACCEPT
/usr/bin/sed -i "/$free_port/d" $SRV_TMP_DIR/conn_wait.list
/usr/bin/echo $free_port >> $SRV_TMP_DIR/waste.list
return
else
/usr/bin/echo "$free_port NOT IN USE"
/usr/bin/echo "RULE ADDED, CONNECT NOW!"
/usr/bin/sleep 1s
fi
count_conn_tryes=$((count_conn_tryes-1))
done
# #
##
/usr/bin/echo "REVERT IPTABLES RULE AND REVERT VM TO CLEAN \
LIST $free_port"
/usr/sbin/iptables -t nat -D PREROUTING -p tcp -i $input_iface --dport \
$vdi_spice_port -j DNAT --to-destination 127.0.0.1:$free_port
/usr/sbin/iptables -D INPUT -i $input_iface -p tcp -m tcp --dport $free_port \
-j ACCEPT
/usr/sbin/iptables -D OUTPUT -o $input_iface -p tcp -m tcp --sport \
$free_port -j ACCEPT
/usr/bin/sed -i "/$free_port/d" $SRV_TMP_DIR/conn_wait.list
/usr/bin/echo $free_port >> $SRV_TMP_DIR/clear.list
##
/usr/bin/echo "#" "$date" "END EXECUTE VM_CONNECT.SH#"
# Attention! Must Be! sysctl net.ipv4.conf.all.route_localnet=1
Скрипт vm_connect.sh вносит правила файерволла которое создают редирект «spice» порта сервера интерфейса enp5s0 на «spice console port» VM, расположенном на lo0 интерфейсе сервера, переданный в качестве параметра запуска. Порт переносится в conn_wait.list, VM считается ожидающей соединения. В сессию Client Station на «signal» порту сервера передается строка «RULE ADDED, CONNECT NOW», которую ожидает запущенный на ней скрипт remote.sh. Начинается цикл ожидания соединения с количеством попыток, определяемым значением переменной «count_conn_tryes» из конфигурационного файла. Каждую секунду в nc сессию будет отдаваться строка «RULE ADDED, CONNECT NOW» и проверяться наличие установленного соединения до «spice_console» порта.
Если за установленное количество попыток, соединения не произошло, «spice_console» порт переносится обратно в clear.list Исполнение vm_connect.sh завершается, возобновляется выполнение vm_manager.sh, который запускает цикл очистки.
Если фиксируется подключение Client Station к «spice_console» порту на интерфейсе lo0, правила файерволла создающие редирект между «spice» портом сервера и «spice_console» портом удаляются и дальнейшее поддержание соединения происходит за счет механизма определения состояния файерволла. В случае разрыва соединения, повторно установить связь с «spice_console» портом, не удастся. Порт «spice_console» переносится в waste.list, VM считается «грязной» и вернуться в пул «чистых» виртуальных машин без прохождения очистки она не сможет. Исполнение vm_connect.sh завершается, возобновляется выполнение vm_manager.sh, который запускает цикл очистки.
Цикл очистки начинается с просмотра файла waste.list в который переносятся номера «spice_console» портов виртуальных машин к которым устанавливалось соединение. Определяется наличие активного соединения на каждом «spice_console» порту из списка. Если соединение отсутствует, считается, что виртуальная машина более не используется и порт переносится в recycle.list и запускается процесс удаления виртуальной машины (см. ниже), которой принадлежал этот порт. Если обнаружено активное сетевое соединение на порту, считается, что виртуальная машина используется, никаких действий для нее не предпринимается. Если порт не прослушивается, считается, что VM выключена и более не нужна. Порт переносится в recycle.list и запускается процесс удаления виртуальной машины. Для этого вызывается скрипт vm_delete.sh, которому в качестве параметра передается номер «spice_console» порту VM, которую необходимо удалить.
#!/bin/sh
##
port_to_delete="$1"
date=$(/usr/bin/date)
# #
/usr/bin/echo "# $date START EXECUTE VM_DELETE.SH#"
/usr/bin/echo "TRY DELETE VM ON PORT: $vm_port"
##
vm_name_part1=$(/usr/bin/cat /etc/vm_manager.conf |/usr/bin/grep 'base_host' \
|/usr/bin/cut -d'=' -f2)
vm_name=$(/usr/bin/echo "$vm_name_part1""-""$port_to_delete")
# #
##
/usr/bin/virsh destroy $vm_name
/usr/bin/virsh undefine $vm_name
/usr/bin/rm -f /var/lib/libvirt/images_write/$vm_name.qcow2
/usr/bin/sed -i "/$port_to_delete/d" $SRV_TMP_DIR/recycle.list
# #
/usr/bin/echo "VM ON PORT $vm_port HAS BEEN DELETE AND REMOVE" \
"FROM RECYCLE.LIST. EXIT FROM VM_DELETE.SH"
/usr/bin/echo "# $date STOP EXECUTE VM_DELETE.SH#"
exit
Удаление виртуальной машины — достаточно тривиальная операция, скрипт vm_delete.sh производит определение имени виртуальной машины, которой принадлежит порт, переданный в качестве параметра запуска. Производится принудительный останов VM, удаление VM из гипервизора, удаляется виртуальный жесткий диск данной VM. Порт «spice_console» удаляется из recycle.list. Исполнение vm_delete.sh завершается, возобновляется исполнение vm_manager.sh
Скрипт vm_manager.sh, по окончанию операций по очистке лишних виртуальных машин из списка waste.list начинает цикл создания виртуальных машин в пул.
Процесс начинается с того, что происходит определение доступных для размещения портов «spice_console». Для этого исходя из параметра конфигурационного файла «srv_start_port_pool» который задает начальный порт для пула «spice_console» виртуальных машин и параметра «srv_pool_size», определяющего предельное количество виртуальных машин происходит последовательный перебор всех возможных вариантов портов. Для каждого определенного порта происходит поиск его в clear.list, waste.list, conn_wait.list, recycle.list. Если порт обнаружен в любом из данных файлов, порт считается занятым и пропускается. Если порт в указанных файлах не обнаружен, он вносится в файл recycle.list и начинается процесс создания новой виртуальной машины. Для этого вызывается скрипт vm_create.sh которому передается в качестве параметра номер «spice_console» порта для которого необходимо создать VM.
#!/bin/sh
/usr/bin/echo "#" "$date" "START RUNNING VM_CREATE.SH#"
new_vm_port=$1
date=$(/usr/bin/date)
a=0
/usr/bin/echo SRV_TMP_DIR=$SRV_TMP_DIR
##
base_host=$(/usr/bin/cat /etc/vm_manager.conf |/usr/bin/grep "base_host" \
|/usr/bin/cut -d "=" -f2)
/usr/bin/echo "base_host=$base_host"
# #
hdd_image_locate() {
/bin/echo "Run STEP 1 - hdd_image_locate"
hdd_base_image=$(/usr/bin/virsh dumpxml $base_host \
|/usr/bin/grep "source file" |/usr/bin/grep "qcow2" |/usr/bin/head -n 1 \
|/usr/bin/cut -d "'" -f2)
if [ -z "$hdd_base_image" ]
then
/bin/echo "base hdd image not found!"
else
/usr/bin/echo "hdd_base_image found is a $hdd_base_image. Run next step 2"
#< CHECK FOR SNAPSHOT ON BASE HDD >#
if [ 0 -eq `/usr/bin/qemu-img info "$hdd_base_image" | /usr/bin/grep -c "Snapshot"` ]
then
/usr/bin/echo "base image haven't snapshot, run NEXT STEP 3"
else
/usr/bin/echo "base hdd image have a snapshot, can't use this image"
exit
fi
# CHECK FOR SNAPSHOT ON BASE HDD >#
#< CHECK FOR HDD IMAGE IS LINK CLONE >#
if [ 0 -eq `/usr/bin/qemu-img info "$hdd_base_image" |/usr/bin/grep -c "backing file"
then
/usr/bin/echo "base image is not a linked clone, NEXT STEP 4"
/usr/bin/echo "Base image check complete!"
else
/usr/bin/echo "base hdd image is a linked clone, can't use this image"
exit
fi
fi
# CHECK FOR HDD IMAGE IS LINK CLONE >#
cloning
}
cloning() {
# #
/usr/bin/virsh shutdown $base_host > /dev/null 2>&1
# #
##
/usr/bin/echo "Free port for Spice VM is $new_vm_port"
##
new_vm_name=$(/bin/echo $base_host"-"$new_vm_port)
# #
##
/usr/bin/virsh dumpxml $base_host > $SRV_TMP_DIR/$new_vm_name.xml
##
####
/usr/bin/sed -i "s%$base_host %$new_vm_name %g" $SRV_TMP_DIR/$new_vm_name.xml
# #
##
old_uuid=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml |/usr/bin/grep "")
/usr/bin/echo old UUID $old_uuid
new_uuid_part1=$(/usr/bin/echo "$old_uuid" |/usr/bin/cut -d "-" -f 1,2)
new_uuid_part2=$(/usr/bin/echo "$old_uuid" |/usr/bin/cut -d "-" -f 4,5)
new_uuid=$(/bin/echo $new_uuid_part1"-"$new_vm_port"-"$new_uuid_part2)
/usr/bin/echo $new_uuid
/usr/bin/sed -i "s%$old_uuid%$new_uuid%g" $SRV_TMP_DIR/$new_vm_name.xml
# #
##
old_spice_port=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml \
|/usr/bin/grep "graphics type='spice' port=")
/bin/echo old spice port $old_spice_port
new_spice_port=$(/usr/bin/echo "")
/bin/echo $new_spice_port
/usr/bin/sed -i "s%$old_spice_port%$new_spice_port%g" $SRV_TMP_DIR/$new_vm_name.xml
# #
##
mac_new=$(/usr/bin/hexdump -n6 -e '/1 ":%02X"' /dev/random|/usr/bin/sed s/^://g)
/usr/bin/echo New Mac is $mac_new
# #
##
mac_old=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml |/usr/bin/grep "mac address=")
/usr/bin/echo old mac is $mac_old
/usr/bin/sed -i "s%$mac_old%$mac_new%g" $SRV_TMP_DIR/$new_vm_name.xml
##
##
/usr/bin/qemu-img create -f qcow2 -b $hdd_base_image /var/lib/libvirt/images_write/$new_vm_name.qcow2
# #
##
/usr/bin/echo hdd base image is $hdd_base_image
/usr/bin/sed -i "s%%%g" $SRV_TMP_DIR/$new_vm_name.xml
# #
starting_vm
##
}
starting_vm() {
/usr/bin/virsh define $SRV_TMP_DIR/$new_vm_name.xml
/usr/bin/virsh start $new_vm_name
while [ $a -ne 1 ]
do
if /usr/bin/virsh list --all |/usr/bin/grep "$new_vm_name" |/usr/bin/grep "running" > /dev/null 2>&1
then
a=1
/usr/bin/sed -i "/$new_vm_port/d" $SRV_TMP_DIR/recycle.list
/usr/bin/echo $new_vm_port >> $SRV_TMP_DIR/clear.list
/usr/bin/echo "#" "$date" "VM $new_vm_name IS STARTED #"
else
/usr/bin/echo "#VM $new_vm_name is not ready#"
a=0
/usr/bin/sleep 2s
fi
done
/usr/bin/echo "#$date EXIT FROM VM_CREATE.SH#"
exit
}
hdd_image_locate
Процесс создания новой виртуальной машины
Скрипт vm_create.sh считывает из конфигурационного файла значение переменной «base_host» которой определяется образец виртуальной машины, на основе которой будет делаться клон. Производит выгрузку xml конфигурации VM из базы гипервизора, производит ряд проверок qcow образа диска VM и при успешном их завершении создает xml конфигурационный файл для новой VM и «linked clone» образ диска новой VM. После чего xml конфиг новой VM загружается в базу гипервизора и VM запускается. Порт «spice_console» переносится из recycle.list в clear.list. Заканчивается исполнение vm_create.sh и завершается исполнение vm_manager.sh.
При следующем подключении всё начинается с начала.
Для аварийных случаев в комплекте есть скрипт vm_clear.sh который принудительно пробегает по всем VM из пула и удаляет их с обнулением значений list-ов. Вызов его на этапе загрузки позволяет начать работу (недо)VDI с чистого листа.
#!/usr/bin/sh
#set VARIABLES#
SRV_SCRIPTS_DIR=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_scripts_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR"
export SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR
SRV_TMP_DIR=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_tmp_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_TMP_DIR=$SRV_TMP_DIR"
export SRV_TMP_DIR=$SRV_TMP_DIR
SRV_POOL_SIZE=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_pool_size" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_POOL_SIZE=$SRV_POOL_SIZE"
SRV_START_PORT_POOL=$(/usr/bin/cat /etc/vm_manager.conf \
|/usr/bin/grep "srv_start_port_pool" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo SRV_START_PORT_POOL=$SRV_START_PORT_POOL
#Set VARIABLES#
/usr/bin/echo "= Cleanup ALL VM="
/usr/bin/mkdir $SRV_TMP_DIR
/usr/sbin/service iptables restart
/usr/bin/cat /dev/null > $SRV_TMP_DIR/clear.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/waste.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/recycle.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/conn_wait.list
port_to_delete=$(($SRV_START_PORT_POOL+$SRV_POOL_SIZE))
while [ "$port_to_delete" -gt "$SRV_START_PORT_POOL" ]
do
$SRV_SCRIPTS_DIR/vm_delete.sh $port_to_delete
port_to_delete=$(($port_to_delete-1))
done
/usr/bin/echo "= EXIT FROM VM_CLEAR.SH="
На этом я хотел бы закончить первую часть своего рассказа. Изложенного должно быть достаточно для системных администраторов, чтобы попробовать недоVDI в деле. Если сообщество найдет данную тему интересной, во второй части я расскажу про модификацию livecd Fedora и превращения ее в киоск.