Хабрахабр

[Из песочницы] KVM (недо)VDI с одноразовыми виртуальными машинами с помощью bash

Кому предназначена данная статья

Данная статья может быть интересна системным администраторам, перед которыми вставала задача создать сервис «одноразовых» рабочих мест.

Пролог

В отдел ИТ сопровождения молодой динамично развивающейся компании с небольшой региональной сетью, обратились с просьбой организовать «станции самообслуживания» для использования их внешними клиентами. Данные станции предполагалось использовать для регистрации на внешних порталах компании, загрузки данных с внешних устройств, работы с государственными порталами.

Таким образом, отказаться от MS Windows при решении данной задачи мы не могли.
Основной проблемой виделось возможность накопления различных данных пользовательских сеансов, которые могли бы привести к их утечке третьим лицам. Важным аспектом являлся тот факт, что большая часть программного обеспечения «заточена» под MS Windows (например, «Декларация»), а несмотря на движение в сторону открытых форматов, MS Office остается доминирующим стандартом при обмене электронными документами. Но в отличие от квазигосудаственного (государственное автономное учреждение) МФЦ, за подобные недочеты не государственные организации будут наказаны значительно сильнее. Такая ситуация уже подвела МФЦ. Вероятность заноса зловредов из сети интернет, рассматривалась как менее возможная, в силу ограничения выхода в интернет по белому списку адресов.К проработке требований подключились сотрудники других отделов, внося свои требования и пожелания, итоговые требования выглядели следующим образом: Следующей по критичности проблемой было требование работы с внешними носителями данных, на которых, однозначно, будет куча зловредных зловредов.

Требования ИБ

  • После использования все пользовательские данные (включая временные файлы и ключи реестра) должны удаляться.
  • Все процессы, запущенные пользователем, по окончанию работы должны завершаться.
  • Выход в интернет по белому списку адресов.
  • Ограничения на возможность запуска стороннего кода.
  • В случае простоя сеанса более 5 минут, сеанс должен автоматически завершаться, станция должна произвести очистку.

Требования заказчика

  • Количество клиентских станций на филиал – не более 4-х.
  • Минимальное время ожидания готовности системы, от момента «сел за стул» до начала работы с клиентским ПО.
  • Возможность подключения периферийных устройств (сканеры, флэшки) непосредственно с места установки «станции самообслуживания».
  • Пожелания заказчика
  • Демонстрация рекламных материалов (картинки) в момент простоя комплекса.

Муки творчества

Вдоволь наигравшись с виндовыми livecd, мы пришли к единодушному выводу, что получающееся решение не удовлетворяет минимум 3 критичным пунктам. Они либо долго грузятся, либо не совсем live, либо кастомизация их была сопряжена с дикими болями. Возможно, мы плохо искали, и вы сможете посоветовать набор какой-то инструментов, буду благодарен.

А хотелось простой инструмент, с минимальным количеством магии, большинство проблем которого можно было бы решить простой перезагрузкой/перезапуском сервиса. Дальше мы стали смотреть в сторону VDI, но для этой задачи большинство решений либо слишком дороги, либо требуют пристального внимания. К счастью, у нас было серверное оборудование, low end класса в филиалах, от выводящегося из эксплуатации сервиса, которое мы могли использовать для технологической базы.

А вот, что в итоге получилось, я вам рассказать не смогу, ибо NDA, но в процессе поисков мы разработали интересную схему, которая хорошо себя показала в лабораторных испытаниях, хоть и не пошла в серию. Что в итоге получилось?

Автор заранее согласен с утверждением, что Sein Englishe sprache is zehr schlecht. Немного дисклаймеров: автор не претендует на то, что предложенное решение полностью решает все поставленные задачи и делает это добровольно и с песней. Автор предполагает, что вы хоть немного знакомы с KVM и читали обзорную статью по Spice протоколу ну и немного работали с Centos или иным GNU Linux дистрибутивом. Так как решение более не развивается, на bug fix или изменение функционала рассчитывать не приходится, всё в ваших руках.

Если статья окажется интересна публике, я опишу детали реализации live образов для создания тонких клиентов на базе Fedora и расскажу о деталях настройки виртуальных машин и KVM сервера для оптимизации производительности и защищенности. В этой статье я хотел бы разобрать костяк получившегося решения, а именно взаимодействие клиента и сервера и суть процессов по жизненному циклу виртуальных машин в рамках рассматриваемого решения.

Если взять цветной бумаги,
Краски, кисточки и клей,
И еще чуть-чуть сноровки…
Можно сделать сто рублей!

Схема и описание тестового стенда

Прокси сервер исторически уже был, ничего экстраординарного он из себя не представляет. Все оборудование располагается внутри сети филиала, наружу выходит только интернет канал. ВМ далее в тексте). Но именно на нем, в числе прочего, будет происходить фильтрация траффика от виртуальных машин (сокр. Ничего не мешает разместить этот сервис на KVM сервере, единственное, что надо смотреть как изменится нагрузка от него на дисковую подсистему.

Представляют из себя неттопы Lenovo IdeaCentre. Client Station – собственно, «станции самообслуживания», «фронтенд» нашего сервиса. Да почти всем, особенно радует большое количество USB разъемов и кардридер на лицевой панели. Чем хорош этот агрегат? Само собой, к неттопу подключен монитор, клавиатура и мышь. В нашей схеме в кардридер вставлена SD карта с включенной аппаратной защитой от записи, на которою записан модифицированный live образ Fedora 28.

Ни к каким сетям, кроме сети «станций самообслуживания» не подключен. Switch – ничем не примечательный аппаратный switch второго уровня, стоит в серверной и мигает лампочками.

Дисковая подсистема – adaptec 3405 2 диска Raid 1 + SSD. KVM_Server – ядро схемы, в стендовых испытаниях Core 2 Quad Q9650 с 8 Гб оперативной памяти уверенно тянул на себе 3 виртуальных машины с Windows10. Сервера нам достались бы от выбывшего сервиса, капитальных затрат было бы не много. В полевых испытаниях Xeon 1220 более серьезный LSI 9260 + SSD легко тянули 5-6 ВМ. На этом сервере(ах) развернута система виртуализации KVM с пулом виртуальных машин pool_Vm.

В ней происходит работа пользователя. Vm — виртуальная машина, бэкэнд нашего сервиса.

Enp5s0 – сетевой интерфейс смотрящий в сторону сети «станций самообслуживания», на нем живут dhcpd, ntpd, httpd, и xinetd слушает «signal» порт.

Стандартный. Lo0 – псевдоинтерфейс обратной петли.

Фактически, подключаясь на этот TCP порт, мы получаем консоль Vm, без необходимости подключатся к Vm через её сетевой интерфейс. Spice_console – Очень интересная вещь, дело в том, что в отличие от классического RDP, при развороте связки KVM+Spice protocol, появляется дополнительная сущность – порт консоли виртуальной машины. Ближайший аналог по функции – IPKVM. Всё взаимодействие с Vm по передаче сигнала, сервер берет на себя. на этот порт передается изображение монитора ВМ, на него же передаются данные о перемещении мыши, и (что самое главное) взаимодействие через Spice протокол позволяет бесшовно перенаправлять USB устройства в виртуальную машину, словно это устройство подключено к самой Vm. Т.е. Проверено для флэш накопителей, сканеров, вэб-камер.

Vnet0, virbr0 и виртуальные сетевые карты Vm образуют сеть виртуальных машин.

Как ЭТО работает

Со стороны Client Station

254. Клиентская станция загружается в графическом режиме с модифицированного live образа Fedora 28, получает ip адрес по dhcp из адресного пространства сети 169. 0/24. 24. После завершения загрузки станция ждет авторизации пользователя «Client». В процессе загрузки создаются правила файервола, позволяющие производить соединения к «signal» и «spice» портам сервера. Среди прочего, скрипт автозапуска запускает скрипт remote.sh. После авторизации пользователя происходит запуск менеджера рабочих столов «openbox» и выполнение скрипта автозапуска autostart от имени авторизовавшегося пользователя.

$HOME/.config/openbox/scripts/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

/etc/client.conf

server_ip=169.254.24.1
vdi_signal_port=5905
vdi_spice_port=5906
animation_folder=/usr/share/backgrounds/animation
background_folder=/usr/share/backgrounds2/fedora-workstation

Описание переменных файла client.conf
server_ip — адрес KVM_Server
vdi_signal_port — порт KVM_Server на котором «сидит» xinetd
vdi_spice_port — сетевой порт KVM_Server, с которого будет происходить перенаправление запроса на соединение от remote-viewer клиента к spice порту выделенной Vm (подробности ниже)
animation_folder — папка, откуда берутся изображения для демонстрации bullshit animation
background_folder — папка, откуда берутся изображения для демонстрации презентаций в режиме ожидания. Подробнее об анимации в следующей части статьи.

При получении искомой строки запускается процесс remote-viewer в режиме киоска устанавливая соединение на «vdi_spice_port» порт сервера. Скрипт remote.sh берет настройки из файла конфигурации /etc/client.conf и производит с помощью nc подключение на «vdi_signal_port» порт KVM сервера и получает поток данных от сервера, среди которых ожидает строки «RULE ADDED, CONNECT NOW». Выполнение скрипта приостанавливается до момента окончания исполнения remote-viewer-а.

на консоль виртуальной машины и происходит, непосредственно, работа пользователя. Remote-viewer подключаясь на «vdi_spice_port» порт, за счет редиректа на стороне сервера, попадает на порт «spice_console» интерфейса lo0 т.е. В процессе ожидания подключения, пользователю демонстрируется bullshit animation, в виде слайд-шоу из jpeg файлов, путь к каталогу с картинками определяется значением переменной animation_folder из конфигурационного файла.

фактическим окончанием сессии пользователя), происходит завершения всех процессов, запущенных от имени авторизовавшегося пользователя, что приводит к перезапуску lightdm и возврату на экран авторизации. При потере соединения с «spice_console» портом виртуальной машины, сигнализирующего об выключении/перезагрузке виртуальной машины (т.е.

Со стороны KVM Server

На «signal» порту сетевой карты enp5s0 ждет соединения xinetd. После коннекта на «signal» порт xinetd запускает скрипт vm_manager.sh без передачи ему каких-либо вводных параметров и перенаправляет результат выполнения скрипта в nc сессию Client Station.

/etc/xinetd.d/test-server

service vdi_signal {
port = 5905
socket_type = stream
protocol = tcp
wait = no
user = root
server = /home/admin/scripts_vdi_new/vm_manager.sh
}

/home/admin/scripts_vdi_new/vm_manager.sh


#!/usr/bin/sh #<SET LOCAL VARIABLES FOR SCRIPT>#
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)
#</SET LOCAL VARIABLES FOR SCRIPT># /usr/bin/echo "# $date START EXECUTE VM_MANAGER.SH #" make_connect_to_vm() { #<READING CLEAR.LIST AND CHECK PORT FOR NETWORK STATE>#
/usr/bin/echo "READING CLEAN.LIST AND CHECK PORT STATE"
#<CHECK FOR NO ONE PORT IN CLEAR.LIST># 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 #<MINIMUN ONE PORT IN CLEAR.LIST FOUND># /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" #<NETSTAT LISTEN CHECK FOR PORT FROM CLEAN.LIST># if /usr/bin/netstat -lnt |/usr/bin/grep ":$clear_vm_port" > /dev/null then /bin/echo "$clear_vm_port IS LISTEN" #<PORT IS LISTEN. CHECK FOR IS CONNECTED NOW># if /usr/bin/netstat -nt |/usr/bin/grep ":$clear_vm_port" \ |/usr/bin/grep "ESTABLISHED" > /dev/null then
#<PORT LISTEN AND ALREADY CONNECTED! MOVE PORT FROM CLEAR.LIST # TO WASTE.LIST># /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
#<PORT LISTEN AND NO ONE CONNECT NOW. MOVE PORT FROM CLEAR.LIST TO # CONN_WAIT.LIST AND CREATE IPTABLES RULES>## /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
#<TRY TO CLEAN VM IN WASTE.LIST AND CREATE NEW WM># /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 #<PORT IS NOT A LISTEN. MOVE PORT FROM CLEAR.LIST TO WASTE.LIST># /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

/etc/vm_manager.conf

srv_scripts_dir=/home/admin/scripts_vdi_new
srv_pool_size=4
srv_start_port_pool=5920
srv_tmp_dir=/tmp/vm_state
base_host=win10_2
input_iface=enp5s0
vdi_spice_port=5906
count_conn_tryes=10

vm_connect.sh) Описание переменных конфигурационного файла vm_manager.conf
srv_scripts_dir — папка расположения скриптов vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — размер пула Vm
srv_start_port_pool — начальный порт, после которого начнется расположение портов spice консолей виртуальных машин
srv_tmp_dir — папка для размещения временных файлов
base_host — базовая Vm (золотой образ) с которого будут делаться клоны Vm в пул
input_iface — сетевой интерфейс сервера, смотрящий в сторону Client Stations
vdi_spice_port — сетевой порт сервера, с которого будет происходить перенаправление запроса на соединение от remote-viewer клиента к spice порту выделенной Vm
count_conn_tryes — таймер ожидания, по истечению которого считается, что соединения к Vm не произошло (подробности работы см.

Для этого он читает файл clear.list в котором содержатся номера «spice_console» портов «свежесозданных» (см. Скрипт vm_manager.sh производит чтение файла конфигурации из файла vm_manager.conf, производит оценку состояния виртуальных машин в пуле по нескольким параметрам, а именно: сколько VM развернуто, есть ли свободные чистые VM. При обнаружении порта с установленным сетевым соединением, (чего быть категорически не должно) выводится предупреждение и порт переносится в waste.list При обнаружении первого порта из файла clear.list с которым в настоящий момент нет соединения vm_manager.sh вызывает скрипт vm_connect.sh и передает ему в качестве параметра номер этого порта. ниже цикл создания В.М.) виртуальных машин и проверяет наличие установленного соединения с ними.

/home/admin/scripts_vdi_new/vm_connect.sh

#!/bin/sh date=$(/usr/bin/date) /usr/bin/echo "#" "$date" "START EXECUTE VM_CONNECT.SH#" #<SET LOCAL VARIABLES FOR SCRIPT>#
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"
#</SET LOCAL VARIABLES FOR SCRIPT># #<CREATE IPTABLES RULES AND SEND SIGNAL TO CONNECT>#
/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!"
#</CREATE IPTABLES RULES AND SEND SIGNAL TO CONNECT># #<WAIT CONNECT ESTABLISHED AND ACTIVATE CONNECT TIMER>#
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
#</WAIT CONNECT ESTABLISED AND ACTIVATE CONNECT TIMER># #<IF COUNT HAS EXPIRED. REMOVE IPTABLES RULE AND REVERT \
# VM TO CLEAR.LIST>#
/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
#</COUNT HAS EXPIRED. REMOVE IPTABLES RULE AND REVERT VM \
#TO 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 вносит правила файерволла которое создают редирект «vdi_spice_port» порта сервера интерфейса 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, который запускает цикл очистки.

В случае разрыва соединения, повторно установить связь с «spice_console» портом, не удастся. Если фиксируется подключение Client Station к «spice_console» порту на интерфейсе lo0, правила файерволла создающие редирект между «spice» портом сервера и «spice_console» портом удаляются и дальнейшее поддержание соединения происходит за счет механизма определения состояния файерволла. Исполнение vm_connect.sh завершается, возобновляется выполнение vm_manager.sh, который запускает цикл очистки. Порт «spice_console» переносится в waste.list, VM считается «грязной» и вернуться в пул «чистых» виртуальных машин без прохождения очистки она не сможет.

Определяется наличие активного соединения на каждом «spice_console» порту из списка. Цикл очистки начинается с просмотра файла waste.list в который переносятся номера «spice_console» портов виртуальных машин к которым устанавливалось соединение. ниже), которой принадлежал этот порт. Если соединение отсутствует, считается, что виртуальная машина более не используется и порт переносится в recycle.list и запускается процесс удаления виртуальной машины(см. Если порт не прослушивается, считается, что VM выключена и более не нужна. Если обнаружено активное сетевое соединение на порту, считается, что виртуальная машина используется, никаких действий для нее не предпринимается. Для этого вызывается скрипт vm_delete.sh, которому в качестве параметра передается номер «spice_console» порту VM, которую необходимо удалить. Порт переносится в recycle.list и запускается процесс удаления виртуальной машины.

/home/admin/scripts_vdi_new/vm_delete.sh


#!/bin/sh #<Set local VARIABLES>#
port_to_delete="$1"
date=$(/usr/bin/date)
#</Set local VARIABLES># /usr/bin/echo "# $date START EXECUTE VM_DELETE.SH#"
/usr/bin/echo "TRY DELETE VM ON PORT: $vm_port" #<VM NAME SETUP>#
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")
#</VM NAME SETUP># #<SHUTDOWN AND DELETE VM>#
/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
#</SHUTDOWN AND DELETE VM># /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 начинает цикл создания виртуальных машин в пул.

Для этого исходя из параметра конфигурационного файла «srv_start_port_pool» который задает начальный порт для пула «spice_console» виртуальных машин и параметра «srv_pool_size», определяющего предельное количество виртуальных машин происходит последовательный перебор всех возможных вариантов портов. Процесс начинается с того, что происходит определение доступных для размещения портов «spice_console». Если порт обнаружен в любом из данных файлов, порт считается занятым и пропускается. Для каждого определенного порта происходит поиск его в clear.list, waste.list, conn_wait.list, recycle.list. Для этого вызывается скрипт vm_create.sh которому передается в качестве параметра номер «spice_console» порта для которого необходимо создать VM. Если порт в указанных файлах не обнаружен, он вносится в файл recycle.list и начинается процесс создания новой виртуальной машины.

/home/admin/scripts_vdi_new/vm_create.sh


#!/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 #<SET LOCAL VARIABLES FOR SCRIPT>#
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"
#</SET LOCAL VARIABLES FOR SCRIPT># 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() {
# <Step_1 turn the base VM off ># /usr/bin/virsh shutdown $base_host > /dev/null 2>&1 # </Step_1 turn the base VM off ># #<Create_vm_config># /usr/bin/echo "Free port for Spice VM is $new_vm_port" #<Setup_name_for_new_VM>#
new_vm_name=$(/bin/echo $base_host"-"$new_vm_port)
#</Setup_name_for_new_VM># #<Make_base_config_as_clone_base_VM>#
/usr/bin/virsh dumpxml $base_host > $SRV_TMP_DIR/$new_vm_name.xml
#<Make_base_config_as_clone_base_VM># ##<Setup_New_VM_Name_in_config>##
/usr/bin/sed -i "s%<name>$base_host</name>%<name>$new_vm_name</name>%g" $SRV_TMP_DIR/$new_vm_name.xml
#</Setup_New_VM_Name_in_config># #<UUID Changing>#
old_uuid=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml |/usr/bin/grep "<uuid>")
/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
#</UUID Changing># #<Spice port replace>#
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 "<graphics type='spice' port='$new_vm_port' autoport='no' listen='127.0.0.1'>")
/bin/echo $new_spice_port
/usr/bin/sed -i "s%$old_spice_port%$new_spice_port%g" $SRV_TMP_DIR/$new_vm_name.xml
#</Spice port replace># #<MAC_ADDR_GENERATE>#
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_ADDR_GENERATE># #<GET OLD MAC AND REPLACE>#
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
#<GET OLD MAC AND REPLACE># #<new_disk_create>#
/usr/bin/qemu-img create -f qcow2 -b $hdd_base_image /var/lib/libvirt/images_write/$new_vm_name.qcow2
#</new_disk_create># #<attach_new_disk_in_confiig>#
/usr/bin/echo hdd base image is $hdd_base_image
/usr/bin/sed -i "s%<source file='$hdd_base_image'/>%<source file='/var/lib/libvirt/images_write/$new_vm_name.qcow2'/>%g" $SRV_TMP_DIR/$new_vm_name.xml
#</attach_new_disk_in_confiig># starting_vm #</Create_vm config>#
} 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

Процесс создания новой виртуальной машины

Производит выгрузку xml конфигурации VM из базы гипервизора, производит ряд проверок qcow образа диска VM и при успешном их завершении создает xml конфигурационный файл для новой VM и «linked clone» образ диска новой VM. Скрипт vm_create.sh считывает из конфигурационного файла значение переменной «base_host» которой определяется образец виртуальной машины, на основе которой будет делаться клон. Порт «spice_console» переносится из recycle.list в clear.list. После чего xml конфиг новой VM загружается в базу гипервизора и VM запускается. Заканчивается исполнение vm_create.sh и завершается исполнение vm_manager.sh.
При следующем подключении всё начинается с начала.

Вызов его на этапе загрузки позволяет начать работу (недо)VDI с чистого листа. Для аварийных случаев в комплекте есть скрипт vm_clear.sh который принудительно пробегает по всем VM из пула и удаляет их с обнулением значений list-ов.

/home/admin/scripts_vdi_new/vm_clear.sh

#!/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 и превращения ее в киоск.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть