Za koga je ovaj članak namijenjen
Ovaj članak može biti zanimljiv sistemskim administratorima koji su se suočili s izazovom stvaranja usluge "jednokratnih" radnih mjesta.
Prolog
Odjel IT podrške mlade, dinamične tvrtke s malom regionalnom mrežom obratio se s molbom da organizira "stanice samoopservisanja" za korištenje njihovim vanjskim klijentima. Ove stanice planirale su se koristiti za registraciju na vanjskim portalima tvrtke, preuzimanje podataka s vanjskih uređaja i rad s državnim portalama.
Važan aspekt bila je činjenica da je velika većina softvera "specijalizirana" za MS Windows (na primjer, "Deklaracija"), a unatoč pomaku prema otvorenim formatima, MS Office ostaje dominantni standard za razmjenu elektroničkih dokumenata. Dakle, nismo mogli odustati od MS Windows prilikom rješavanja ovog zadatka.
Glavni problem vidio se u mogućnosti nakupljanja raznih podataka korisničkih sesija, koji bi mogli dovesti do njihovog curenja trećim stranama. . No, za razliku od kvazidržavnog (državnog autonomnog tijela) MFC-a, ne vladine organizacije bit će kažnjene značajno jače za takve nedostatke. Sljedeći kritični problem bilo je zahtijevano rad s vanjskim medijima podataka, na kojima će neizbježno biti puno zlonamjernih softvera. Vjerojatnost unošenja zlonamjernih softvera putem interneta smatrana je manje mogućom zbog ograničenja izlaska na internet prema bijelom popisu adresa. O zahtjevima su se uključili zaposlenici drugih odjela, unoseći svoje zahtjeve i želje, a konačni zahtjevi izgledali su ovako:
Zahtjevi za IB
- Nakon korištenja, svi korisnički podaci (uključujući privremene datoteke i ključeve registra) moraju se ukloniti.
- Svi procesi koje je pokrenuo korisnik moraju se završiti nakon završetka rada.
- Izlaz na internet putem bijelog popisa adresa.
- Ograničenja na mogućnost pokretanja vanjskog koda.
- U slučaju neaktivnosti sesije duže od 5 minuta, sesija se mora automatski završiti, stanica mora izvršiti čišćenje.
Zahtjevi klijenta
- Broj klijenata stanica po podružnici – ne više od 4.
- Minimalno vrijeme čekanja na spremnost sustava, od trenutka «sjedenja na stolicu» do početka rada s klijentkim softverom.
- Mogućnost povezivanja perifernih uređaja (skeneri, USB uređaji) izravno s mjesta instalacije «stanice za samoposluživanje».
- Želje naručitelja
- Prikazivanje reklamnih materijala (slike) tijekom zastoja sustava.
Muke kreativnosti
Nakon što smo se dovoljno igrali s Windows livecd-ima, došli smo do jednoglasnog zaključka da rješenje koje smo dobili ne zadovoljava najmanje 3 kritične točke. Ili se dugo pokreće, ili nije potpuno live, ili je njihova prilagodba bila povezana s velikim teškoćama. Moguće je da smo loše tražili i možda biste mogli savjetovati neki set alata, bio bih zahvalan.
Zatim smo počeli razmatrati VDI, ali za ovaj zadatak većina rješenja ili je preskupa ili zahtijeva pažnju. A htjeli smo jednostavan alat, s minimalnom količinom magije, većinu problema koje bismo mogli riješiti jednostavnim ponovnim pokretanjem/ponovnim pokretanjem usluge. Na sreću, imali smo poslužiteljski hardver, niskog ranga u podružnicama, iz povučenog servisa, koji smo mogli koristiti kao tehnološku osnovu.
Što smo na kraju dobili? A evo, što smo na kraju dobili, ne mogu vam reći, jer postoji NDA, ali tijekom naših pretraživanja razvili smo zanimljivu shemu koja se dobro pokazala u laboratorijskim ispitivanjima, iako nije otišla u serijsku proizvodnju.
Mali disclaimer: autor ne tvrdi da predloženo rješenje potpuno rješava sve postavljene zadatke i da to radi s veseljem i voljom. Autor unaprijed pristaje na tvrđenje da njegov engleski jezik nije dobar. Budući da se rješenje više ne razvija, ne očekujte popravke grešaka ili promjene funkcionalnosti, sve je u vašim rukama. Autor pretpostavlja da ste barem malo upoznati s KVM-om i čitali ste preglednu temu o Spice protokolu, te radili s Centosa ili nekom drugom GNU Linux distribucijom.
U ovom članku bih želio razjasniti srž rješenja, a to su interakcija klijenta i poslužitelja, te suština procesa životnog ciklusa virtualnih strojeva unutar razmatranog rješenja. Ako se članak pokaže zanimljivim publici, opisat ću detalje implementacije live slika za izradu tankih klijenata na bazi Fedore i govorit ću o detaljima postavljanja virtualnih strojeva i KVM poslužitelji za optimizaciju performansi i sigurnosti.
Ako uzmemo obojeni papir,
Boje, četkice i ljepilo,
I još malo spretnosti…
Može se napraviti sto kuna!
Shema i opis testnog stuba

Sva oprema se nalazi unutar mreže podružnice, prema van izlazi samo internet kanal. Proxies poslužitelj povijesno je već postojao, ne predstavlja ništa izvanredno. No, upravo na njemu će se, između ostalog, provoditi filtracija prometa s virtualnih strojeva (skraćeno VM u tekstu). Ništa ne sprječava postavljanje ove usluge na KVM poslužitelju, jedino što treba paziti je kako će se opterećenje od njega promijeniti na diskovnom sustavu.
Klijentska stanica – zapravo, "stanice samoposluživanja", "frontend" naše usluge. Predstavljaju mini računala Lenovo IdeaCentre. Što je dobro u ovom uređaju? Pa gotovo sve, posebno veseli veliki broj USB priključaka i čitač kartica na prednjoj strani. U našoj shemi u čitaču kartica umetnuta je SD kartica s uključenom hardverskom zaštitom od pisanja, na kojoj je snimljena modificirana live slika Fedore 28. Naravno, mini računalu je priključen monitor, tipkovnica i miš.
Switch – neprimjetan hardverski switch drugog nivoa, smješten u poslužiteljskoj sobi i svijetli svjetlima. Nije povezan s mrežama, osim s mrežom "stanica samoposluživanja".
KVM_Server – jezgra sheme, u testnim ispitivanjima Core 2 Quad Q9650 s 8 GB RAM-a sigurno je podnosio 3 virtualna stroja s Windows 10. Diskovni sustav – adaptec 3405 2 diska Raid 1 + SSD. U terenskim ispitivanjima Xeon 1220 ozbiljniji LSI 9260 + SSD lako je podnosio 5-6 VM. Poslužitelji su nam dodeljeni od otkazujuće usluge, kapitalni troškovi ne bi bili veliki. Na ovom poslužitelju(ima) je raspoređena KVM virtualizacijska sustav s grupom virtualnih strojeva pool_Vm.
VM — virtualni stroj, backend naše usluge. U njemu se odvija rad korisnika.
Enp5s0 – mrežni sučelje usmjeren prema mreži „stanica za samoposluživanje“, na njemu se nalaze dhcpd, ntpd, httpd, a xinetd sluša „signal“ port.
Lo0 – pseudo sučelje povratne petlje. Standardno.
Spice_console – Vrlo zanimljiva stvar, razlika je u tome što, za razliku od klasičnog RDP-a, prilikom korištenja KVM+Spice protokola nastaje dodatna entiteta – port konzole virtualne mašine. U suštini, povezivanjem na ovaj TCP port, dobijamo konzolu Vm bez potrebe za povezivanjem na Vm kroz njeno mrežno sučelje. Sve interakcije s Vm-om preuzima server. Najbliži analog funkciji je IPKVM. Dakle, na ovaj port se prenosi slika monitora VM-a, a također se prenose podaci o pomicanju miša, i (što je najvažnije) interakcija putem Spice protokola omogućava besprijekorno preusmjeravanje USB uređaja u virtualnu mašinu, kao da je taj uređaj priključen na samu Vm. Testirano za flash drive-e, skeneri, web kamere.
Vnet0, virbr0 i virtualne mrežne kartice Vm čine mrežu virtualnih mašina.
Kako OVO radi
Sa strane Klijent Stanice
Klijent stanica se učitava u grafičkom načinu s modificirane live slike Fedora 28, dobiva ip adresu putem dhcp-a iz adresnog prostora mreže 169.254.24.0/24. Tijekom učitavanja se stvaraju pravila vatrozida koja omogućuju povezivanje na „signal“ i „spice“ portove servera. Nakon završetka učitavanja stanica očekuje autorizaciju korisnika „Klijent“. Nakon autorizacije korisnika pokreće se menadžer radnih površina „openbox“ i izvršava se autostart skripta autostart u ime autoriziranog korisnika. Među ostalima, autostart skripta pokreće skriptu 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
Opis varijabli datoteke client.conf
server_ip — adresa KVM_Server
vdi_signal_port — port KVM_Server na kojem „sjedi“ xinetd
vdi_spice_port — mrežni port KVM_Server s kojeg će se preusmjeriti zahtjev za povezivanjem od remote-viewer klijenta na spice port dodijeljene Vm (detalji u nastavku)
animation_folder — mapa iz koje se uzimaju slike za demonstraciju animacije
background_folder — mapa iz koje se uzimaju slike za demonstraciju prezentacija u stanju čekanja. Više o animaciji u sljedećem dijelu članka.
Skript remote.sh uzima postavke iz konfiguracijske datoteke /etc/client.conf i uspostavlja vezu na "vdi_signal_port" port KVM poslužitelja koristeći nc te prima tok podataka s poslužitelja, među kojima očekuje redke "RULE ADDED, CONNECT NOW". Kada primi traženi redak, pokreće se proces remote-viewer u kiosku, uspostavljajući vezu na "vdi_spice_port" port poslužitelja. Izvršavanje skripte se pauzira dok remote-viewer ne završi.
Remote-viewer koji se povezuje na "vdi_spice_port" port, putem preusmjeravanja na strani poslužitelja, dolazi na port "spice_console" sučelja lo0, tj. na konzolu virtualnog stroja i korisnik aktivno radi. Tijekom čekanja na vezu, korisniku se prikazuje animacija, u obliku slajdova od jpeg datoteka, put do direktorija s slikama definira se vrijednošću varijable animation_folder iz konfiguracijske datoteke.
U slučaju gubitka veze s "spice_console" portom virtualnog stroja, što signalizira isključivanje/restart virtualnog stroja (tj. stvarno završavanje korisničke sesije), završavaju se svi procesi pokrenuti u ime autoriziranog korisnika, što uzrokuje ponovni pokret lightdm-a i povratak na ekran autorizacije.
S strane KVM poslužitelja
Na "signal" portu mrežne kartice enp5s0 čeka na vezu xinetd. Nakon povezivanja na "signal" port, xinetd pokreće skriptu vm_manager.sh bez proslijeđivanja bilo kakvih ulaznih parametara i preusmjerava rezultat izvršavanja skripte u nc sesiju Klijentske stanice.
/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
Opis varijabli konfiguracijske datoteke vm_manager.conf
srv_scripts_dir — direktorij u kojem se nalaze skripte vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — veličina Vm skupa
srv_start_port_pool — početni port, od kojeg će se započeti raspoređivanje portova spice konzola virtualnih strojeva
srv_tmp_dir — direktorij za pohranu privremenih datoteka
base_host — osnovni Vm (zlati obraz) iz kojeg će se stvarati klonovi Vm u skupu
input_iface — mrežni sučelje poslužitelja koje gleda prema Klijentskim stanicama
vdi_spice_port — mrežni port poslužitelja s kojeg će se preusmjeriti zahtjev za povezivanje klijenta remote-viewer na spice port dodijeljenom Vm
count_conn_tryes — timer čekanja, nakon kojeg se smatra da spoj na Vm nije uspostavljen (detalje rada pogledajte u vm_connect.sh)
Skript vm_manager.sh čita konfiguracijsku datoteku iz vm_manager.conf, ocjenjuje stanje virtualnih strojeva u bazenu prema nekoliko parametara, a to su: koliko je VM-ova raspoređeno, postoje li slobodni čisti VM-ovi. Za to čita datoteku clear.list koja sadrži brojeve portova ‘spice_console’ novih virtualnih strojeva (vidi dolje ciklus stvaranja V.M.) i provjerava postoji li uspostavljena veza s njima. Kada se otkrije port s uspostavljenom mrežnom vezom (što ne bi smjelo biti), prikazuje se upozorenje i port se prebacuje u waste.list. Kada se otkrije prvi port iz datoteke clear.list s kojim trenutno nema veze, vm_manager.sh poziva skript vm_connect.sh i prosljeđuje mu broj tog porta kao parametar.
/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
Skript vm_connect.sh dodaje pravila vatrozida koja stvaraju preusmjeravanje ‘vdi_spice_port’ porta poslužiteljske sučelje enp5s0 na ‘spice console port’ VM-a, smještenog na lo0 sučelju poslužitelja, koji se prenosi kao parametar pri pokretanju. Port se prebacuje u conn_wait.list, a VM se smatra da čeka povezivanje. U sesiju Klijentske Stanice na ‘signal’ portu servera šalje se niz ‘RULE ADDED, CONNECT NOW’, koji očekuje skript remote.sh pokrenut na njemu. Počinje ciklus čekanja na vezu s brojem pokušaja određenim vrijednošću varijable ‘count_conn_tryes’ iz konfiguracijske datoteke. Svake sekunde u nc sesiju će se slati niz ‘RULE ADDED, CONNECT NOW’ i provjeravati postojanje uspostavljene veze do ‘spice_console’ porta.
Ako se tijekom određenog broja pokušaja veza ne uspostavi, ‘spice_console’ port se vraća u clear.list. Izvršavanje vm_connect.sh završava, a nastavlja se izvršavanje vm_manager.sh, koji pokreće ciklus čišćenja.
Ako je povezivanje Client Station s portom 'spice_console' na sučelju lo0 zabilježeno, firewall pravila koja stvaraju preusmjeravanje između 'spice' porta servera i 'spice_console' porta brišu se, a daljnje održavanje veze odvija se putem mehanizma za provjeru stanja firewalla. U slučaju prekida veze, ponovna uspostava veze s 'spice_console' portom neće biti moguća. Port 'spice_console' premješta se u waste.list, VM se smatra 'prljavom' i ne može se vratiti u bazen 'čistih' virtualnih mašina bez prolaska čišćenja. Izvršenje vm_connect.sh završava, nastavlja se izvršenje vm_manager.sh, koji pokreće ciklus čišćenja.
Ciklus čišćenja započinje pregledom datoteke waste.list u koju se premještaju brojevi 'spice_console' portova virtualnih mašina s kojima je uspostavljena veza. Provjerava se postoji li aktivna veza na svakom 'spice_console' portu s popisa. Ako veza ne postoji, smatra se da se virtualna mašina više ne koristi, a port se premješta u recycle.list i pokreće se proces brisanja virtualne mašine (vidi dolje) kojoj je ovaj port pripadao. Ako se otkrije aktivna mrežna veza na portu, smatra se da se virtualna mašina koristi i za nju se ne poduzimaju nikakve mjere. Ako port nije slušajuć, smatra se da je VM isključen i više nije potreban. Port se premješta u recycle.list i pokreće se proces brisanja virtualne mašine. U tu svrhu poziva se skripta vm_delete.sh, kojoj se kao parametar prosljeđuje broj 'spice_console' porta VM koju treba izbrisati.
/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
Brisanje virtualne mašine je prilično trivijalna operacija, skripta vm_delete.sh utvrđuje ime virtualne mašine kojoj pripada port koji je proslijeđen kao parametar pokretanja. Provodi se prisilno zaustavljanje VM, brisanje VM iz hipervizora, briše se virtualni tvrdi disk te VM. Port 'spice_console' briše se iz recycle.list. Izvršenje vm_delete.sh završava, a nastavak izvršenja vm_manager.sh se obnavlja.
Skripta vm_manager.sh, nakon završetka operacija čišćenja suvišnih virtualnih mašina iz popisa waste.list, započinje ciklus stvaranja virtualnih mašina u bazenu.
Proces započinje definiranjem dostupnih portova za 'spice_console'. Na temelju parametra iz konfiguracijske datoteke 'srv_start_port_pool', koji određuje početni port za 'spice_console' bazen virtualnih mašina, te parametra 'srv_pool_size', koji definiše maksimalan broj virtualnih mašina, izvršava se postupno pretraživanje svih mogućih portova. Za svaki određeni port traži se u clear.list, waste.list, conn_wait.list i recycle.list. Ako se port pronađe u bilo kojoj od ovih datoteka, smatra se zauzetim i preskoči se. Ako port nije pronađen u navedenim datotekama, dodaje se u recycle.list i započinje proces stvaranja nove virtualne mašine. Za to se poziva skripta vm_create.sh kojoj se prosljeđuje broj 'spice_console' porta za koji treba stvoriti VM kao parametar.
/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
Proces stvaranja nove virtualne mašine
Skripta vm_create.sh čita iz konfiguracijske datoteke vrijednost varijable 'base_host', koja određuje uzorak virtualne mašine na temelju koje će se napraviti klon. Izvodi se izvoz XML konfiguracije VM iz baze hipervizora, obavljaju se niz provjera qcow slike diska VM-a, a nakon uspješnog završetka kreira se XML konfiguracijska datoteka za novu VM i 'linked clone' slika diska nove VM. Nakon toga, XML konfiguracija nove VM učitava se u bazu hipervizora i VM se pokreće. Port 'spice_console' premješta se iz recycle.list u clear.list. Izvršavanje vm_create.sh završava, a izvršavanje vm_manager.sh se također završava.
Prilikom sljedećeg povezivanja, sve počinje iznova.
Za hitne slučajeve, u kompletu je skripta vm_clear.sh koja prisilno prolazi kroz sve VM-ove iz bazena i uklanja ih uz resetiranje vrijednosti listova. Njihovo pozivanje u fazi pokretanja omogućava započinjanje rada (ne)dovršenog VDI-a s čistog lista.
/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="
Time bih završio prvi dio svoje priče. Onoga što je izloženo trebalo bi biti dovoljno za sistemske administratore da isprobaju nekompletni VDI u praksi. Ako zajednica pronađe ovu temu zanimljivom, u drugom ću dijelu govoriti o modifikaciji livecd Fedora i njenom pretvaranju u kiosk.
Izvor: habr.com
