Cui este destinat acest articol
Acest articol poate fi de interes pentru administratorii de sistem, care s-au confruntat cu sarcina de a crea un serviciu de „locuri de muncă temporare”.
Prolog
Departamentul de IT al unei companii tinere, dinamice și în dezvoltare rapidă, cu o mică rețea regională, a solicitat organizarea „stațiilor de autoservire” pentru utilizarea de către clienții externi. Aceste stații au fost planificate pentru a fi utilizate pentru înregistrarea pe portalurile externe ale companiei, încărcarea de date de pe dispozitive externe și interacțiunea cu portalurile guvernamentale.
Un aspect important a fost faptul că majoritatea software-ului era „adaptat” pentru MS Windows (de exemplu, „Declarația”), iar, în ciuda direcției către formate deschise, MS Office rămâne standardul dominant în schimbul documentelor electronice. Astfel, nu ne-am putut permite să renunțăm la MS Windows în rezolvarea acestei sarcini.
Principala problemă era posibila acumulare a diverselor date ale sesiunii utilizatorului, care ar putea duce la scurgerea acestora către terți. . Însă, spre deosebire de MFC-ul cvasigovernamental (instituție autonomă de stat), organizațiile nonguvernamentale vor fi sancționate mult mai sever pentru astfel de neajunsuri. Următoarea problemă ca gravitate a fost cerința de a lucra cu suporturi externe de date, care, fără îndoială, vor avea un număr mare de malware. Probabilitatea de infectare cu malware din rețeaua de internet a fost considerată mai puțin posibilă, datorită restricțiilor de acces pe baza unei liste albe de adrese. La elaborarea cerințelor s-au alăturat angajați din alte departamente, aducându-și cerințele și dorințele, iar cerințele finale arătau astfel:
Cerințele de securitate informațională
- După utilizare, toate datele utilizatorului (inclusiv fișierele temporare și cheile din registry) trebuie să fie șterse.
- Toate procesele lansate de utilizator trebuie să se oprească la finalizarea lucrului.
- Accesul la internet pe baza unei liste albe de adrese.
- Restricții privind posibilitatea de a lansa cod extern.
- În cazul unei perioade de inactivitate a sesiunii de peste 5 minute, sesiunea trebuie să se încheie automat, iar stația trebuie să efectueze o curățare.
Cerințele clientului
- Numărul stațiilor client pentru fiecare filială – nu mai mult de 4.
- Timpul minim de așteptare pentru disponibilitatea sistemului, de la momentul „m-am așezat pe scaun” până la începutul lucrului cu software-ul client.
- Posibilitatea de conectare a dispozitivelor periferice (scanere, flash drive-uri) direct de la locul de instalare al „stației de self-service”.
- Solicitările clientului
- Demonstrția materialelor publicitare (imagini) în timpul perioadei de inactivitate a complexului.
Durerile critice ale creației
După ce ne-am jucat îndeajuns cu livecd-uri Windows, am ajuns la un consens că soluția obținută nu satisface minimum 3 puncte critice. Acestea fie se încarcă lent, fie nu sunt complet live, fie personalizarea lor a fost asociată cu dureri mari. Poate că am căutat prost, iar tu ai putea să ne recomanzi un set de instrumente, aș fi recunoscător.
În continuare, am început să ne îndreptăm spre VDI, dar pentru această sarcină, majoritatea soluțiilor sunt fie prea scumpe, fie necesită o atenție deosebită. Ne-am dorit un instrument simplu, cu un minim de magie, majoritatea problemelor putând fi rezolvate printr-o simplă repornire/restartare a serviciului. Din fericire, aveam echipamente server în filiale, de clasă low-end, de la un serviciu care a fost scos din exploatare, pe care le-am putut folosi ca bază tehnologică.
Ce a rezultat în final? Din păcate, nu pot să-ți spun ce a rezultat deoarece sunt sub NDA, dar în timpul căutărilor am dezvoltat un plan interesant care s-a dovedit eficient în teste de laborator, deși nu a fost pus în aplicare în serie.
Câteva disclaimere: autorul nu pretinde că soluția propusă rezolvă complet toate sarcinile propuse și o face pro bono și cu cântec. Autorul este de acord cu afirmația că „Sein Englishe sprache is zehr schlecht”. Deoarece soluția nu mai este dezvoltată, nu se pot aștepta corecturi de bug-uri sau modificări ale funcționalității, totul este în mâinile tale. Autorul presupune că ești familiarizat, măcar puțin, cu KVM, ai citit un articol introductiv despre protocolul Spice și ai lucrat puțin cu CentOS sau altă distribuție GNU Linux.
În acest articol, aș dori să discut despre structura soluției obținute, și anume interacțiunea dintre client și server, precum și natura proceselor din ciclul de viață al mașinilor virtuale în cadrul soluției prezentate. Dacă articolul va fi interesant pentru public, voi detalia implementarea imaginilor live pentru crearea clienților subțiri bazate pe Fedora și voi vorbi despre detaliile configurării mașinilor virtuale și servere KVM pentru optimizarea performanței și securității.
Dacă luăm hârtie colorată,
vopsele, pensule și lipici,
și puțin din ceva îndemânare...
se pot face o sută de lei!
Schema și descrierea bancului de testare

Toate echipamentele sunt amplasate în interiorul rețelei filialelor, singurul canal de ieșire este cel de internet. Serverul proxy a fost istoric deja existent, nu reprezintă nimic extraordinar. Dar anume pe el, printre altele, va avea loc filtrarea traficului de la mașinile virtuale (abreviat VMs în text). Nimic nu împiedică plasarea acestui serviciu pe serverul KVM, singurul lucru de urmărit este cum se va schimba sarcina pe subsistemul de disc.
Client Station – mai exact, „stațiile de autoservire”, „frontend-ul” serviciului nostru. Acestea sunt nettop-uri Lenovo IdeaCentre. Ce este atât de bun la acest dispozitiv? Aproape totul, dar în special mă bucură numărul mare de porturi USB și cititorul de carduri de pe panoul frontal. În schema noastră, în cititorul de carduri este introdus un card SD cu protecție hardware împotriva scrierii, pe care este scris un imagine live modificată a Fedora 28. Firește, nettop-ul este conectat la un monitor, tastatură și mouse.
Switch – un switch hardware de nivel 2, neavând nimic deosebit, se află în server room și clipește cu lumini. Nu este conectat la alte rețele decât la rețeaua „stațiilor de autoservire”.
KVM_Server – nucleul schemei, în testele de bancă, Core 2 Quad Q9650 cu 8 GB de memorie RAM a susținut fără probleme 3 mașini virtuale cu Windows 10. Subsistemul de disc – adaptec 3405 cu 2 discuri Raid 1 + SSD. În testele de teren, Xeon 1220, un LSI 9260 mai serios + SSD, a gestionat cu ușurință 5-6 VMs. Serverele ne-au fost oferite de la un serviciu care a fost eliminat, costurile capitolului ar fi fost minime. Pe acest server(e) este desfășurată sistemul de virtualizare KVM cu un pool de mașini virtuale pool_Vm.
Vm – mașina virtuală, backend-ul serviciului nostru. Aici are loc activitatea utilizatorului.
Enp5s0 – interfața de rețea care se îndreaptă spre rețeaua „stațiilor de auto-servire”, pe care rulează dhcpd, ntpd, httpd, iar xinetd ascultă portul „signal.”
Lo0 – interfața pseudo-loopback. Standard.
Spice_console – O caracteristică foarte interesantă, deoarece, spre deosebire de RDP-ul clasic, când se desfășoară combinația KVM + Spice protocol, apare o entitate suplimentară – portul consolei mașinii virtuale. Practic, conectându-ne la acest port TCP, obținem consola Vm-ului, fără a fi necesar să ne conectăm la Vm prin interfața sa de rețea. Toate interacțiunile cu Vm pentru transmiterea semnalului sunt gestionate de server. Cel mai apropiat analog în funcționalitate este IPKVM. Adică, pe acest port se transmite imaginea monitorului Vm-ului, iar pe acesta sunt trimise datele despre mișcarea mouse-ului, iar (ceea ce este cel mai important) interacțiunea prin protocolul Spice permite redirecționarea fără întreruperi a dispozitivelor USB în mașina virtuală, ca și cum acest dispozitiv ar fi conectat direct la Vm. Testat pentru unități flash, scanere, camere web.
Vnet0, virbr0 și plăcile de rețea virtuale Vm formează o rețea de mașini virtuale.
Cum FUNCTIONEAZĂ ACEASTA
Din perspectiva Client Station
Stația client se încarcă în modul grafic dintr-o imagine live modificată Fedora 28, obținând o adresă IP prin DHCP din spațiul de adresare al rețelei 169.254.24.0/24. În timpul încărcării, se creează reguli de firewall care permit conexiuni la porturile „signal” și „spice” ale serverului. După finalizarea încărcării, stația așteaptă autorizarea utilizatorului „Client”. După autorizarea utilizatorului, se lansează managerul desktop-urilor „openbox” și se execută scriptul de autostart autostart în numele utilizatorului autorizat. Printre altele, scriptul de autostart lansează scriptul 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
Descrierea variabilelor din fișierul client.conf
server_ip — adresa KVM_Server
vdi_signal_port — portul KVM_Server pe care rulează xinetd
vdi_spice_port — portul de rețea KVM_Server de la care va fi redirecționat cererea de conectare de la clientul remote-viewer către portul spice al Vm-ului dedicate (detalii mai jos)
animation_folder — folderul din care se obțin imaginile pentru a demonstra animația bullshit
background_folder — folderul din care se obțin imaginile pentru a demonstra prezentările în modul standby. Mai multe despre animație în partea următoare a articolului.
Scriptul remote.sh preia setările din fișierul de configurare /etc/client.conf și efectuează o conexiune utilizing nc pe portul "vdi_signal_port" al serverului KVM, primind un flux de date de la server, printre care așteaptă liniile "RULE ADDED, CONNECT NOW". La primirea liniei căutate, se lansează procesul remote-viewer în modul kiosk, stabilind o conexiune pe portul "vdi_spice_port" al serverului. Execuția scriptului este suspendată până la finalizarea execuției remote-viewer-ului.
Remote-viewer, conectându-se pe portul "vdi_spice_port", ajunge, prin redirecționare pe partea serverului, pe portul "spice_console" al interfeței lo0, adică pe consola mașinii virtuale, și se desfășoară, în mod direct, activitatea utilizatorului. În timpul așteptării conexiunii, utilizatorului i se afișează o animație de tip bullshit, sub forma unui slideshow din fișiere jpeg, calea către catalogul cu imagini fiind determinată de valoarea variabilei animation_folder din fișierul de configurare.
În cazul pierderii conexiunii cu portul "spice_console" al mașinii virtuale, semnalizând o oprire/reboot al mașinii virtuale (adică finalizarea efectivă a sesiunii utilizatorului), toate procesele lansate în numele utilizatorului autorizat se încheie, ceea ce duce la repornirea lightdm și la revenirea la ecranul de autentificare.
Din partea serverului KVM
Pe portul "signal" al plăcii de rețea enp5s0 așteaptă conexiuni xinetd. După conectare la portul "signal", xinetd lansează scriptul vm_manager.sh fără a-i transmite vreo parametru de intrare și redirecționează rezultatul execuției scriptului într-o sesiune nc a 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
Descrierea variabilelor din fișierul de configurare vm_manager.conf
srv_scripts_dir — folderul în care se află scripturile vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — dimensiunea pool-ului Vm
srv_start_port_pool — portul inițial, după care va începe amplasarea porturilor consolelor spice ale mașinilor virtuale
srv_tmp_dir — directorul pentru plasarea fișierelor temporare
base_host — Vm de bază (imagine de aur) din care vor fi create clone Vm în pool
input_iface — interfața de rețea a serverului, care se află în direcția Client Stations
vdi_spice_port — portul de rețea al serverului, de la care va avea loc redirecționarea cererii de conexiune de la clientul remote-viewer la portul spice al Vm-ului dedicat
count_conn_tryes — temporizatorul de așteptare, după expirarea căruia se consideră că nu s-a realizat conexiunea la VM (detalii despre funcționare se găsesc în vm_connect.sh)
Scriptul vm_manager.sh citește fișierul de configurare din vm_manager.conf, evaluează starea mașinilor virtuale din pool pe baza mai multor parametri, și anume: câte VM-uri sunt desfășurate, dacă există VM-uri libere. Pentru aceasta, citește fișierul clear.list care conține numerele porturilor „spice_console” ale „mașinilor virtuale recent create” (vezi ciclul de creare a VM-urilor mai jos) și verifică existența unei conexiuni stabilite cu acestea. În cazul în care se descoperă un port cu o conexiune de rețea stabilită (ceea ce nu ar trebui să existe categoric), se emite o avertizare, iar portul este mutat în waste.list. La descoperirea primului port din clear.list cu care nu există o conexiune în acest moment, vm_manager.sh apelează scriptul vm_connect.sh și îi transmite ca parametru numărul acestui port.
/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
Scriptul vm_connect.sh adaugă reguli de firewall care creează redirecționarea „vdi_spice_port” de pe serverul interfetei enp5s0 către „spice console port” al VM-ului, situat pe interfata lo0 a serverului, transmis ca parametru de pornire. Portul este mutat în conn_wait.list, iar VM-ul este considerat în așteptarea conexiunii. În sesiunea Client Station, pe portul „signal” al serverului, se transmite șirul „RULE ADDED, CONNECT NOW”, pe care scriptul remote.sh aflat în execuție îl așteaptă. Se începe un ciclu de așteptare a conexiunii, cu un număr de încercări definit de valoarea variabilei „count_conn_tryes” din fișierul de configurare. În fiecare secundă, se va transmite în sesiunea nc șirul „RULE ADDED, CONNECT NOW” și va fi verificată existența unei conexiuni stabilite către portul „spice_console”.
Dacă, în numărul stabilit de încercări, conexiunea nu are loc, portul „spice_console” este mutat înapoi în clear.list. Execuția vm_connect.sh se finalizează, iar execuția vm_manager.sh este reluată, care inițiază ciclul de curățare.
Dacă se înregistrează conexiunea Client Station la portul «spice_console» pe interfața lo0, regulile firewall-ului care creează redirecționarea între portul «spice» al serverului și portul «spice_console» sunt eliminate și menținerea ulterioară a conexiunii se face prin mecanismul de determinare a stării firewall-ului. În cazul întreruperii conexiunii, restabilirea legăturii cu portul «spice_console» nu va fi posibilă. Portul «spice_console» este mutat în waste.list, VM-ul este considerat «murdar» și nu se va putea întoarce în pool-ul de mașini virtuale «curate» fără a trece prin procesul de curățare. Execuția vm_connect.sh se încheie, iar execuția vm_manager.sh este reluată, care începe ciclul de curățare.
Ciclul de curățare începe cu examinarea fișierului waste.list în care sunt mutate numerele porturilor «spice_console» ale mașinilor virtuale cu care s-a stabilit conexiunea. Se determină existența unei conexiuni active pe fiecare port «spice_console» din listă. Dacă conexiunea lipsește, se consideră că mașina virtuală nu mai este utilizată și portul este mutat în recycle.list, iar procesul de ștergere a mașinii virtuale (vezi mai jos) este inițiat pentru cea care a aparținut acestui port. Dacă se află o conexiune de rețea activă pe port, se consideră că mașina virtuală este utilizată și nu se realizează nicio acțiune pentru aceasta. Dacă portul nu este ascultat, se consideră că VM-ul este oprit și nu mai este necesar. Portul este mutat în recycle.list și se inițiază procesul de ștergere a mașinii virtuale. Pentru aceasta se apelează scriptul vm_delete.sh, care primește ca parametru numărul portului «spice_console» al VM-ului care trebuie șters.
/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
Ștergerea mașinii virtuale este o operațiune destul de trivială, scriptul vm_delete.sh stabilește numele mașinii virtuale la care aparține portul transmis ca parametru de execuție. Se realizează oprirea forțată a VM-ului, ștergerea VM-ului din hypervizor și se șterge discul virtual al acestei VM. Portul «spice_console» este eliminat din recycle.list. Execuția vm_delete.sh se încheie, iar execuția vm_manager.sh este reluată.
Scriptul vm_manager.sh, la finalizarea operațiunilor de curățare a mașinilor virtuale suplimentare din lista waste.list, începe ciclul de creare a mașinilor virtuale în pool.
Procesul începe cu determinarea porturilor disponibile pentru plasarea «spice_console». În acest scop, pe baza parametrului din fișierul de configurare «srv_start_port_pool», care stabilește portul inițial pentru pool-ul «spice_console» al mașinilor virtuale, și al parametrului «srv_pool_size», care definește numărul maxim de mașini virtuale, se parcurg toate variantele posibile de porturi. Pentru fiecare port identificat, se caută în clear.list, waste.list, conn_wait.list și recycle.list. Dacă portul este găsit în oricare dintre aceste fișiere, este considerat ocupat și omis. Dacă portul nu este găsit în fișierele specificate, este adăugat în fișierul recycle.list și începe procesul de creare a unei noi mașini virtuale. Pentru aceasta, se apelează scriptul vm_create.sh, căruia îi este transmis ca parametru numărul portului «spice_console» pentru care trebuie creat 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
Procesul de creare a unei noi mașini virtuale
Scriptul vm_create.sh citește din fișierul de configurare valoarea variabilei «base_host», care definește modelul mașinii virtuale pe baza căruia va fi făcut un clon. Efectuează exportul configurației XML a VM din baza hipervizorului, efectuează o serie de verificări ale imaginii de disc qcow a VM și, în cazul în care acestea se finalizează cu succes, creează fișierul de configurare XML pentru noua VM și imaginea de disc de tip «linked clone» a noului VM. Ulterior, configurația XML a noii VM este încărcată în baza hipervizorului și VM-ul este pornit. Portul «spice_console» este mutat din recycle.list în clear.list. Executarea scriptului vm_create.sh se finalizează, iar execuția vm_manager.sh se încheie.
La următoarea conectare, totul începe din nou.
Pentru situații de urgență, există un script vm_clear.sh, care parcurge forțat toate VM-urile din pool și le elimină, resetând valorile listelor. Apelarea sa în timpul încărcării permite începutul activității (incomplet) VDI de la început.
/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="
Aici aș dori să închei prima parte a povestirii mele. Cele expuse ar trebui să fie suficiente pentru administratorii de sistem pentru a încerca (incomplet) VDI în acțiune. Dacă comunitatea consideră acest subiect interesant, în a doua parte voi vorbi despre modificarea livecd Fedora și transformarea acesteia într-un kiosk.
Sursa: habr.com
