A chi è destinato questo articolo
Questo articolo potrebbe interessare gli amministratori di sistema, che si sono trovati di fronte alla necessità di creare un servizio di "postazioni di lavoro usa e getta".
Prologo
Nel dipartimento IT di una giovane azienda dinamica in rapida crescita con una piccola rete regionale, è stata fatta richiesta di organizzare "stazioni di self-service" per l'uso da parte dei loro clienti esterni. Queste stazioni erano destinate a essere utilizzate per la registrazione ai portali esterni dell'azienda, per il caricamento di dati da dispositivi esterni e per l'interazione con i portali governativi.
Un aspetto importante era il fatto che gran parte del software fosse "ottimizzato" per MS Windows (ad esempio, "Dichiarazione"), e nonostante la transizione verso formati aperti, MS Office rimane lo standard dominante per lo scambio di documenti elettronici. Pertanto, non potevamo rinunciare a MS Windows nella risoluzione di questo problema.
Il problema principale risiedeva nella possibilità di accumulare vari dati delle sessioni utente, che potrebbero portare a fughe di informazioni a terzi. . A differenza dell'ente quasi statale (istituzione autonoma statale) MFC, le organizzazioni non statali saranno sanzionate in modo significativamente più severo per tali inadempienze. Il successivo problema critico era la richiesta di lavorare con supporti esterni di dati, che senza dubbio conterranno molti malware. La probabilità di contagio da malware attraverso la rete è stata considerata meno probabile, a causa della limitazione dell'accesso a Internet tramite una lista bianca di indirizzi. Il trattamento dei requisiti ha coinvolto i dipendenti di altri reparti, che hanno apportato le loro esigenze e desideri; i requisiti finali sono apparsi come segue:
Requisiti di sicurezza informatica
- Dopo l'uso, tutti i dati degli utenti (inclusi i file temporanei e le chiavi di registro) devono essere eliminati.
- Tutti i processi avviati dall'utente devono terminare al termine del lavoro.
- Accesso a Internet tramite una lista bianca di indirizzi.
- Restrizioni sulla possibilità di eseguire codice di terze parti.
- Se la sessione rimane inattiva per più di 5 minuti, deve terminare automaticamente e la stazione deve effettuare una pulizia.
Requisiti del cliente
- Numero massimo di stazioni client per filiale – non più di 4.
- Tempo minimo di attesa per la preparazione del sistema, dal momento in cui ci si siede fino all'inizio dell'utilizzo del software client.
- Possibilità di collegare dispositivi periferici (scanner, chiavette USB) direttamente dal luogo di installazione della 'stazione di self-service'.
- Richieste del cliente
- Dimostrazione di materiali pubblicitari (immagini) durante il periodo di inattività del complesso.
Difficoltà creative
Dopo aver sperimentato a lungo con livecd di Windows, siamo giunti alla conclusione unanime che la soluzione risultante non soddisfa almeno 3 punti critici. Si avviano lentamente, non sono del tutto ‘live’ o la loro personalizzazione è stata accompagnata da notevoli difficoltà. Forse non abbiamo cercato bene, e se potete consigliare un certo insieme di strumenti, ve ne sarei grato.
In seguito abbiamo iniziato a considerare il VDI, ma per questo compito la maggior parte delle soluzioni è o troppo costosa o richiede un'attenzione costante. Volevamo uno strumento semplice, con il minor numero possibile di complicazioni, la cui maggior parte dei problemi potesse essere risolta con un semplice riavvio del servizio. Fortunatamente, avevamo hardware server di fascia bassa nelle filiali, proveniente da un servizio in fase di dismissione, che potevamo utilizzare come base tecnologica.
Cosa abbiamo ottenuto alla fine? Ebbene, non posso raccontarvi cosa abbiamo ottenuto, poiché siamo vincolati da un NDA, ma durante la ricerca abbiamo sviluppato uno schema interessante che ha funzionato bene nei test di laboratorio, anche se non è progredito nella produzione.
Un po' di disclaimer: l'autore non pretende che la soluzione proposta risolva completamente tutti i problemi e lo fa volontariamente e cantando. L'autore conviene che il suo inglese è molto scarso. Poiché la soluzione non è più in fase di sviluppo, non ci si può aspettare correzioni di bug o modifiche funzionali, tutto è nelle vostre mani. L'autore presume che abbiate almeno un minimo di familiarità con KVM e abbiate letto un articolo di panoramica sul protocollo Spice, nonché lavorato un po' con Centos o un altro sistema GNU/Linux.
In questo articolo vorrei esaminare la struttura della soluzione ottenuta, in particolare l'interazione tra client e server e la natura dei processi nel ciclo di vita delle macchine virtuali all'interno della soluzione considerata. Se l'articolo susciterà interesse, descriverò i dettagli dell'implementazione delle immagini live per creare thin client basati su Fedora e parlerò dei dettagli della configurazione delle macchine virtuali e del server KVM per ottimizzare le prestazioni e la sicurezza.
Se prendi della carta colorata,
Colori, pennelli e colla,
E un po' di abilità in più…
Si possono fare cento rubli!
Schema e descrizione del banco di prova

Tutta l'attrezzatura si trova all'interno della rete della filiale; solo il canale Internet esce all'esterno. Il server proxy era già esistente storicamente, non ha nulla di straordinario. Tuttavia, su di esso avverrà, tra l'altro, la filtrazione del traffico dalle macchine virtuali (abbrev. VM nel testo). Non c'è nulla che impedisca di collocare questo servizio su un server KVM, l'unica cosa da considerare è come cambierà il carico sul sistema di archiviazione.
Client Station – le effettive 'stazioni di self-service', il 'frontend' del nostro servizio. Sono dei nettop Lenovo IdeaCentre. Cosa ha di buono questo dispositivo? Praticamente tutto, soprattutto il grande numero di porte USB e il lettore di schede sul pannello frontale. Nella nostra configurazione, nel lettore di schede è inserita una scheda SD con protezione hardware da scrittura attivata, su cui è registrata un'immagine live modificata di Fedora 28. Naturalmente, al nettop sono collegati un monitor, una tastiera e un mouse.
Switch – un dispositivo switch di secondo livello del tutto ordinario, si trova nella sala server e lampeggia con le spie. Non è collegato a nessuna rete, tranne che alla rete delle 'stazioni di self-service'.
KVM_Server – il core dello schema, nei test di laboratorio Core 2 Quad Q9650 con 8 GB di RAM riusciva a gestire 3 macchine virtuali con Windows 10. Il subsistema di archiviazione – adaptec 3405 con 2 dischi in Raid 1 + SSD. Nei test sul campo, Xeon 1220 con un LSI 9260 più potente + SSD gestivano facilmente 5-6 VM. I server provenivano da un servizio in dismissione, quindi i costi di capitale sarebbero stati contenuti. Su questo server(i) è stata implementata la soluzione di virtualizzazione KVM con il pool di macchine virtuali pool_Vm.
Vm – macchina virtuale, il back-end del nostro servizio. Qui avviene il lavoro dell'utente.
Enp5s0 – interfaccia di rete che guarda verso la rete delle "stazioni di servizio", su di essa si trovano dhcpd, ntpd, httpd, e xinetd che ascolta la porta "signal".
Lo0 – pseudointerfaccia di loopback. Standard.
Spice_console – Una cosa molto interessante, infatti, a differenza del classico RDP, quando si utilizza la combinazione KVM+Spice protocol, compare un'entità aggiuntiva: la porta della console della macchina virtuale. In pratica, connettendosi a questa porta TCP, otteniamo la console della VM, senza la necessità di collegarci alla VM attraverso la sua interfaccia di rete. Tutta l'interazione con la VM per la trasmissione del segnale viene gestita dal server. L'analogo più vicino per funzionalità è l'IPKVM. Cioè, su questa porta viene trasmesso l'immagine del monitor della VM, vengono anche inviati i dati sul movimento del mouse e (cosa più importante) l'interazione tramite il protocollo Spice consente di reindirizzare senza soluzione di continuità i dispositivi USB nella macchina virtuale, proprio come se quel dispositivo fosse collegato alla VM stessa. Verificato per chiavette USB, scanner, webcam.
Vnet0, virbr0 e le schede di rete virtuali della VM formano una rete di macchine virtuali.
Come funziona QUESTO
Dal lato della Client Station
La stazione client si avvia in modalità grafica da un'immagine live modificata di Fedora 28, ricevendo un indirizzo IP tramite DHCP dallo spazio di indirizzi della rete 169.254.24.0/24. Durante il processo di avvio vengono create regole del firewall che consentono di stabilire connessioni alle porte «signal» e «spice» del server. Al termine del caricamento, la stazione attende l'autenticazione dell'utente «Client». Dopo l'autenticazione dell'utente, viene avviato il gestore dei desktop «openbox» e viene eseguito lo script di avvio automatico autostart a nome dell'utente autenticato. Tra le altre cose, lo script di avvio automatico avvia lo script 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
Descrizione delle variabili nel file client.conf
server_ip — l'indirizzo del KVM_Server
vdi_signal_port — la porta del KVM_Server su cui è in ascolto xinetd
vdi_spice_port — la porta di rete del KVM_Server dalla quale avverrà il reindirizzamento della richiesta di connessione dal client remote-viewer alla porta spice della VM dedicata (dettagli di seguito)
animation_folder — la cartella da cui vengono prelevate le immagini per la dimostrazione dell'animazione bullshit
background_folder — la cartella da cui vengono prelevate le immagini per la presentazione delle demo in modalità di attesa. Maggiori informazioni sull'animazione nella prossima parte dell'articolo.
Lo script remote.sh estrae le impostazioni dal file di configurazione /etc/client.conf e stabilisce una connessione tramite nc alla porta «vdi_signal_port» del server KVM, ricevendo un flusso di dati dal server, tra cui attende la stringa «RULE ADDED, CONNECT NOW». All'arrivo della stringa richiesta, viene avviato il processo remote-viewer in modalità chiosco, stabilendo una connessione alla porta «vdi_spice_port» del server. L'esecuzione dello script è sospesa fino al termine del processo di remote-viewer.
Collegandosi alla porta «vdi_spice_port», remote-viewer, grazie al redirect lato server, accede alla porta «spice_console» dell'interfaccia lo0, ovvero alla console della macchina virtuale, dove l'utente può interagire. Durante l'attesa della connessione, viene mostrata all'utente un'animazione fittizia, sotto forma di slideshow di file jpeg, il cui percorso è determinato dal valore della variabile animation_folder nel file di configurazione.
Quando si perde la connessione con il port «spice_console» della macchina virtuale, il segnale che indica lo spegnimento/restart della macchina virtuale (ossia la fine effettiva della sessione utente), tutti i processi avviati a nome dell'utente autorizzato vengono terminati, portando al riavvio di lightdm e al ritorno alla schermata di accesso.
Dal lato del Server KVM
Sul port «signal» della scheda di rete enp5s0, xinetd attende una connessione. Dopo la connessione al port «signal», xinetd esegue lo script vm_manager.sh senza passare alcun parametro di input e reindirizza il risultato dell'esecuzione dello script nella sessione 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
Descrizione delle variabili nel file di configurazione vm_manager.conf
srv_scripts_dir — cartella dove si trovano gli script vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — dimensione del pool Vm
srv_start_port_pool — porta iniziale da cui inizierà la disposizione delle porte spice per le console delle macchine virtuali
srv_tmp_dir — cartella per la memorizzazione dei file temporanei
base_host — immagine di Vm di base (immagine dorata) da cui verranno creati i cloni di Vm nel pool
input_iface — interfaccia di rete del server che si affaccia sulle Client Stations
vdi_spice_port — porta di rete del server da cui verrà effettuato il reindirizzamento della richiesta di connessione dal client remote-viewer alla porta spice della Vm dedicata
count_conn_tryes — timer di attesa dopo il quale si considera che non si sia verificata alcuna connessione alla Vm (per dettagli sul funzionamento, vedere vm_connect.sh)
Lo script vm_manager.sh legge il file di configurazione vm_manager.conf, valuta lo stato delle macchine virtuali nel pool in base a diversi parametri, ovvero: quante VM sono attive e se ci sono VM libere. A tal fine, legge il file clear.list, che contiene i numeri delle porte "spice_console" delle macchine virtuali "fresche" (vedi sotto il ciclo di creazione delle VM) e verifica la presenza di una connessione attiva con esse. Se viene trovata una porta con una connessione di rete stabilita (cosa che non dovrebbe accadere categoricamente), viene restituito un avviso e la porta viene spostata in waste.list. Quando viene trovato il primo porto nel file clear.list con cui attualmente non c'è connessione, vm_manager.sh chiama lo script vm_connect.sh e gli passa come parametro il numero di quella porta.
/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
Lo script vm_connect.sh inserisce le regole del firewall che creano un reindirizzamento della porta «vdi_spice_port» dell'interfaccia server enp5s0 verso la «spice console port» della VM, situata sull'interfaccia lo0 del server, passata come parametro di avvio. La porta viene trasferita in conn_wait.list, la VM è considerata in attesa di connessione. Durante la sessione Client Station, la stringa «RULE ADDED, CONNECT NOW» viene trasmessa attraverso la porta «signal» del server, attesa dallo script remote.sh in esecuzione. Inizia un ciclo di attesa della connessione con un numero di tentativi definito dal valore della variabile «count_conn_tryes» presente nel file di configurazione. Ogni secondo, nella sessione nc, verrà fornita la stringa «RULE ADDED, CONNECT NOW» e verrà verificata la presenza di una connessione stabilita verso la porta «spice_console».
Se, dopo il numero di tentativi stabilito, non si è verificata alcuna connessione, la porta «spice_console» viene trasferita nuovamente in clear.list. L'esecuzione di vm_connect.sh termina, e viene ripreso l'esecuzione di vm_manager.sh, che avvia un ciclo di pulizia.
Se viene registrata una connessione tra la Client Station e la porta «spice_console» sull'interfaccia lo0, le regole del firewall che creano un redirect tra la porta «spice» del server e la porta «spice_console» vengono rimosse e il mantenimento della connessione avviene tramite il meccanismo di rilevamento dello stato del firewall. In caso di disconnessione, non sarà possibile ristabilire una connessione con la porta «spice_console». La porta «spice_console» viene spostata in waste.list, la VM viene considerata "sporca" e non potrà tornare nel pool delle macchine virtuali "pulite" senza passare attraverso il processo di pulizia. L'esecuzione di vm_connect.sh termina, e viene ripresa l'esecuzione di vm_manager.sh, che avvia un ciclo di pulizia.
Il ciclo di pulizia inizia con la revisione del file waste.list, dove vengono trasferiti i numeri delle porte "spice_console" delle macchine virtuali a cui è stata stabilita una connessione. Viene verificata la presenza di una connessione attiva su ogni porta "spice_console" nell'elenco. Se non è presente alcuna connessione, la macchina virtuale viene considerata non più utilizzata e la porta viene trasferita nel file recycle.list, avviando il processo di eliminazione della macchina virtuale (vedi sotto) a cui apparteneva questa porta. Se viene rilevata una connessione di rete attiva sulla porta, la macchina virtuale viene considerata in uso e non si prendono ulteriori provvedimenti. Se la porta non è in ascolto, si considera che la VM sia spenta e non necessaria. La porta viene trasferita nel file recycle.list e avviato il processo di eliminazione della macchina virtuale. A tal fine, viene chiamato lo script vm_delete.sh, al quale viene passato come parametro il numero della porta "spice_console" della VM che deve essere eliminata.
/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
La rimozione di una macchina virtuale è un'operazione piuttosto semplice; lo script vm_delete.sh identifica il nome della macchina virtuale a cui appartiene la porta fornita come parametro di avvio. Viene forzatamente arrestata la VM, rimossa dal hypervisor e viene eliminato il disco rigido virtuale associato. La porta "spice_console" viene rimossa da recycle.list. L'esecuzione di vm_delete.sh si conclude, e si riprende l'esecuzione di vm_manager.sh.
Lo script vm_manager.sh, al termine delle operazioni di pulizia delle macchine virtuali superflue dalla lista waste.list, avvia un ciclo di creazione delle macchine virtuali nel pool.
Il processo inizia con la definizione delle porte "spice_console" disponibili per l'allocazione. A tal fine, si basa sul parametro del file di configurazione "srv_start_port_pool", che determina la porta iniziale per il pool di porte "spice_console" delle macchine virtuali, e sul parametro "srv_pool_size", che definisce il numero massimo di macchine virtuali. Viene eseguita una ricerca sequenziale di tutte le possibili porte. Per ogni porta determinata si controlla la sua presenza in clear.list, waste.list, conn_wait.list e recycle.list. Se la porta viene trovata in uno di questi file, è considerata occupata e viene saltata. Se la porta non è presente nei file indicati, viene aggiunta al file recycle.list e inizia il processo di creazione di una nuova macchina virtuale. A tal fine, viene chiamato lo script vm_create.sh passando come parametro il numero di porta "spice_console" per il quale è necessario creare la 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
Il processo di creazione di una nuova macchina virtuale
Lo script vm_create.sh legge dal file di configurazione il valore della variabile «base_host», che definisce il modello della macchina virtuale da cui verrà creato il clone. Esporta la configurazione XML della VM dal database del hypervisor, esegue una serie di controlli sull'immagine disco qcow della VM e, al termine con successo di questi controlli, crea un file di configurazione XML per la nuova VM e un'immagine disco «linked clone» per la nuova VM. Successivamente, la configurazione XML della nuova VM viene caricata nel database del hypervisor e la VM viene avviata. La porta «spice_console» viene spostata da recycle.list a clear.list. L'esecuzione di vm_create.sh si conclude e termina l'esecuzione di vm_manager.sh.
Alla prossima connessione, tutto ricomincia da capo.
Per casi di emergenza, è incluso uno script vm_clear.sh che attraversa forzatamente tutte le VM del pool e le rimuove azzerando i valori delle liste. Invocarlo durante il caricamento consente di avviare il (non completo) VDI da un foglio bianco.
/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="
Con questo, vorrei concludere la prima parte del mio racconto. Le informazioni fornite dovrebbero essere sufficienti per gli amministratori di sistema per provare il VDI non completo in azione. Se la comunità trova questo argomento interessante, nella seconda parte parlerò della modifica di livecd Fedora e della sua trasformazione in chiosco.
Fonte: habr.com
