За кого е предназначена тази статия
Тази статия може да представлява интерес за системни администратори, пред които стои задачата да създадат услуга за „еднократни“ работни места.
Пролог
В IT отдела на младо, динамично развиващо се предприятие с малка регионална мрежа, поискаха да организират „станции за самообслужване“ за използване от техните външни клиенти. Тези станции трябваше да се използват за регистрация на външни портали на компанията, за прехвърляне на данни от външни устройства, за работа с държавни портали.
Важен аспект беше фактът, че голяма част от софтуера е адаптиран за MS Windows (например, „Декларация“), а въпреки движението в посока отворени формати, MS Office остава доминиращият стандарт при обмена на електронни документи. Така че, не можехме да се откажем от MS Windows при решаването на тази задача.
Основният проблем беше възможността за натрупване на различни данни от потребителски сесии, които биха могли да доведат до техния достъп от трети лица. . Но за разлика от квазидържавния (държавно автономно учреждение) МФЦ, неправителствените организации ще бъдат наказани значително по-силно за подобни недостатъци. Следващият по критичност проблем беше изискването за работа с външни носители на данни, на които със сигурност ще има много зловреден софтуер. Вероятността за пренасяне на зловреден софтуер от интернет се разглеждаше като по-малко вероятна, поради ограничаването на достъпа до интернет с бели списъци. В проработването на изискванията се включиха служители от други отдели, които внесоха своите изисквания и желания, а крайният списък с изисквания изглеждаше така:
Изисквания за информационна безопасност
- След използването, всички потребителски данни (включително времеви файлове и ключове от регистъра) трябва да бъдат изтривани.
- Всички процеси, стартирани от потребителя, след приключване на работата трябва да се завършват.
- Достъп до интернет чрез бели списъци.
- Ограничения за възможността за стартиране на външен код.
- При неактивност на сесията над 5 минути, сесията трябва автоматично да приключи, станцията трябва да извърши почистване.
Изисквания на клиента
- Броят на клиентските станции на клон – не повече от 4.
- Минималното време за очакване на готовността на системата, от момента „седнах на стола“ до началото на работа с клиентския софтуер.
- Възможност за свързване на периферни устройства (скенери, флаш памети) директно от мястото на инсталиране на „станцията за самообслужване“.
- Изисквания на клиента
- Демонстрация на рекламни материали (снимки) по време на пауза в комплекса.
Мъки на творчеството
След като се натъкнахме на различни Windows livecd, стигнахме до единодушното заключение, че полученото решение не удовлетворява минимум 3 критични точки. Те или зареждат дълго, или не са напълно live, или персонализирането им беше свързано с огромни трудности. Може би не сме търсили успешно, и вие можете да предложите набор от инструменти, за което ще бъда благодарен.
След това започнахме да разглеждаме VDI, но за тази задача повечето решения са или твърде скъпи, или изискват внимателно наблюдение. Искахме прост инструмент, с минимално количество магия, повечето проблеми от които можеха да бъдат разрешени с проста рестартиране/рестартиране на услугата. За щастие, разполагахме с нискокачествено сървърно оборудване в клоновете, от стартиращата услуга, което можехме да използваме за технологичната база.
Какво всъщност се получи? Ами, какво всъщност се получи, не мога да ви разкажа, защото NDA, но в процеса на търсене разработихме интересна схема, която добре се представи в лабораторните тестове, макар и да не стигна до серийно производство.
Няколко предупреждения: авторът не претендира, че предложеното решение напълно решава всички поставени задачи и прави това доброволно и с песен. Авторът предварително се съгласява с твърдението, че Sein Englishe sprache is zehr schlecht. Тъй като решението вече не се развива, не трябва да очаквате поправки на грешки или промени в функционалността, всичко е във вашите ръце. Авторът предполага, че сте поне малко запознати с KVM и сте прочели обзорна статия за протокола Spice, а също така сте работили малко с Centos или друг GNU Linux дистрибутив.
В тази статия бих искал да разгледам основата на полученото решение, а именно взаимодействието между клиента и сървъра и същността на процесите през жизнения цикъл на виртуалните машини в рамките на обсъжданото решение. Ако статията се окаже интересна за обществеността, ще опиша детайлите на реализацията на live образи за създаване на тънки клиенти на базата на Fedora и ще разкажа за детайлите на настройката на виртуалните машини и KVM сървър за оптимизиране на производителността и защитеността.
Ако вземем цветна хартия,
Боя, четки и лепило,
И още малко сръчност…
Може да направим сто лева!
Схема и описание на тестовия стенд

Всичкото оборудване е разположено вътре в мрежата на филиала, навън излиза само интернет канал. Прокси сървърът исторически вече е имало, нищо екстраординарно не представлява. Но именно на него, наред с другото, ще се извършва филтрация на трафика от виртуалните машини (съкратено ВМ по-долу в текста). Нищо не пречи да се разположи тази услуга на KVM сървъра, единственото, което трябва да се гледа е как ще се промени натоварването от него на дисковата подсистема.
Клиентска станция – всъщност, "станции за самообслужване", "фронтенд" на нашата услуга. Представляват неттопове Lenovo IdeaCentre. Какво е хубавото в този агрегат? Почти всичко, особено радва голямото количество USB портове и четец за карти на предния панел. В нашата схема в четеца за карти е поставена SD карта с включена хардуерна защита от запис, на която е записан модифицираният live образ на Fedora 28. Само по себе си, на неттопа е свързан монитор, клавиатура и мишка.
Превключвател – нищо особено, обикновен апаратен превключвател от второ ниво, стои в сървърната стая и мига с лампички. Не е свързан към никакви мрежи, освен мрежата на "станциите за самообслужване".
KVM_Сървър – основата на схемата, в стендови изпитвания Core 2 Quad Q9650 с 8 ГБ оперативна памет уверено поддържаше 3 виртуални машини с Windows10. Дисковата подсистема – adaptec 3405 2 диска Raid 1 + SSD. В полеви изпитания Xeon 1220 по-сериозният LSI 9260 + SSD спокойно поддържаше 5-6 ВМ. Сървърите ни бяха предоставени от изтеклата услуга, капиталовите разходи щяха да бъдат малко. На тези сървъри е разположена системата за виртуализация KVM с пул от виртуални машини pool_Vm.
ВМ — виртуална машина, бекенд на нашата услуга. В нея протича работата на потребителя.
Enp5s0 – мрежов интерфейс, насочен към мрежата на „станции за самообслужване“, на който работят dhcpd, ntpd, httpd, а xinetd слуша порта „signal“.
Lo0 – псевдоинтерфейс на обратната връзка. Стандартен.
Spice_console – Много интересна функция, защото, за разлика от класическия RDP, когато се разгръща връзката KVM+Spice протокол, се появява допълнителна същност – порт на консолата на виртуалната машина. Практически, свързвайки се към този TCP порт, получаваме конзола на VM, без да е необходимо да се свързваме към VM чрез нейния мрежов интерфейс. Цялото взаимодействие с VM относно предаването на сигнала е поето от сървъра. Най-близкият аналог по функция е IPKVM. Т.е. на този порт се предава изображение на монитора на VM, към него също се изпращат данни за движението на мишката, и (най-важното) взаимодействието чрез Spice протокол позволява безпроблемно пренасочване на USB устройства във виртуалната машина, така сякаш устройството е свързано директно към самата VM. Проверено за флаш драйвове, скенери, уебкамера.
Vnet0, virbr0 и виртуалните мрежови карти на VM формират мрежа от виртуални машини.
Как работи ТОВА
От страна на клиентската станция
Клиентската станция се зарежда в графичен режим от модифициран live образ на Fedora 28, получава ip адрес по dhcp от адресното пространство на мрежата 169.254.24.0/24. По време на зареждане се създават правила на фаервола, позволяващи свързването с портовете „signal“ и „spice“ на сървера. След като зареждането приключи, станцията чака авторизация от потребителя „Client“. След авторизацията на потребителя се стартира мениджърът на работните среди „openbox“ и се изпълнява скриптът за автоматично стартиране autostart от името на авторизирания потребител. Сред другите неща, скриптът за автоматично стартиране стартира скрипта remote.sh.
$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 — папка, от която се взимат изображения за демонстрация на анимацията
background_folder — папка, от която се взимат изображения за демонстрация на презентации в режим на изчакване. Повече за анимацията в следващата част на статията.
Скрипт remote.sh взема настройки от конфигурационния файл \/etc\/client.conf и с помощта на nc установява връзка на порта „vdi_signal_port“ на KVM сървъра, получавайки поток от данни от сървъра, сред които очаква редове „RULE ADDED, CONNECT NOW“. При получаване на искания ред, се стартира процесът remote-viewer в режим на киоск, установявайки връзка на порта „vdi_spice_port“ на сървъра. Изпълнението на скрипта се спира до момента на приключване на remote-viewer.
Remote-viewer, свързвайки се на порта „vdi_spice_port“, чрез редирект на страна на сървъра, попада на порта „spice_console“ на интерфейса lo0, т.е. на конзолата на виртуалната машина, и се осъществява, в действителност, работата на потребителя. По време на изчакването на връзка, на потребителя се показва анимация, в стил bullshit, представляваща слайд шоу от jpeg файлове, а пътят към каталога с изображенията се определя от стойността на променливата animation_folder от конфигурационния файл.
При загуба на връзка с порта „spice_console“ на виртуалната машина, сигнализираща за изключване\/рестартиране на виртуалната машина (т.е. фактическото приключване на сесията на потребителя), се прекратяват всички процеси, стартирани от името на авторизирания потребител, което води до рестартиране на lightdm и връщане на екрана за автентикация.
От страна на KVM сървър
На порта „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.confsrv_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_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 не е било осъществено (подробности за работата вижте vm_connect.sh)
Скриптът vm_manager.sh чете конфигурационния файл от vm_manager.conf и оценява състоянието на виртуалните машини в клона по няколко параметъра, а именно: колко VM са разгрънати, дали има свободни чисти VM. За целта той чете файла clear.list, в който се съдържат номерата на портовете «spice_console» на «новосъздадените» (вижте по-долу цикъла на създаване на В.М.) виртуални машини и проверява наличието на установено свързване с тях. При откритие на порт с активна мрежова връзка (не трябва да се случва) се извежда предупреждение и портът се прехвърля в 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 завършва, и se възобновява изпълнението на 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, която трябва да бъде изтрита.
/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, започва цикъл за създаване на виртуални машини в пула.
Процесът започва с определяне на наличните портове за разполагане „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.
/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
Процесът на създаване на нова виртуална машина
Скриптът 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 от пула и ги изтрива, нулирайки стойностите на списъците. Извикването му в етапа на зареждане позволява на (не)VDI да започне работата си от чисто начало.
/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 и превръщането й в киоск.
Източник: habr.com
