KVM (nedo)VDI con macchine virtuali monouso utilizzando bash

A chi è destinato questo articolo

Questo articolo può interessare gli amministratori di sistema che si trovano ad affrontare la sfida 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 richiesta l'organizzazione di 'stazioni di self-service' per l'uso da parte dei loro clienti esterni. Queste stazioni sarebbero state utilizzate per la registrazione sui portali esterni dell'azienda, per il caricamento di dati da dispositivi esterni e per interagire con i portali governativi.

Un aspetto importante era il fatto che la maggior parte del software era 'ottimizzato' per MS Windows (ad esempio, 'Dichiarazione'), e nonostante la tendenza verso formati aperti, MS Office rimane lo standard dominante per lo scambio di documenti elettronici. Pertanto, non potevamo escludere MS Windows nella risoluzione di questo problema.

La principale preoccupazione riguardava la possibilità di accumulare vari dati delle sessioni utente, che potrebbero portare a fughe di informazioni a terzi. Una situazione del genere ha già danneggiato il MFC.. Tuttavia, a differenza del quasi ente pubblico (ente autonomo statale), organizzazioni non statali saranno punite in modo significativamente più severo per tali carenze. La problematica successiva era la richiesta di lavorare con supporti dati esterni, sui quali ci sarebbero sicuramente stati molti malware. La probabilità di introdurre malware tramite Internet è stata considerata meno probabile, a causa delle restrizioni all'accesso a Internet impostate su una lista bianca di indirizzi. Per definire i requisiti hanno contribuito anche impiegati di altri reparti, apportando le proprie esigenze e desideri; i requisiti finali sono stati i seguenti:

Requisiti di sicurezza informatica

  • Dopo l'uso, tutti i dati utente (inclusi file temporanei e chiavi di registro) devono essere eliminati.
  • Tutti i processi avviati dall'utente devono essere terminati al termine del lavoro.
  • Accesso a Internet limitato a una lista bianca di indirizzi.
  • Restrizioni sulla possibilità di eseguire codice di terze parti.
  • In caso di inattività della sessione superiore a 5 minuti, la sessione deve chiudersi automaticamente e la stazione deve effettuare una pulizia.

Requisiti del cliente

  • Numero massimo di stazioni client per filiale - non più di 4.
  • Il tempo minimo di attesa per la prontezza del sistema, dal momento in cui «si è seduto sulla sedia» all'inizio del lavoro con il software cliente.
  • Possibilità di collegare dispositivi periferici (scanner, pen drive) direttamente dal luogo di installazione della «stazione di self-service».
  • Richieste del cliente
  • Dimostrazione di materiali pubblicitari (immagini) durante i momenti di inattività del complesso.

Le fatiche della creatività

Dopo aver giocato a lungo con livecd di Windows, siamo giunti all'unanimità alla conclusione che la soluzione risultante non soddisfa almeno 3 punti critici. Loro o si avviano lentamente, oppure non sono davvero live, o la loro personalizzazione era accompagnata da enormi dolori. Forse abbiamo cercato male, e potreste consigliarmi un insieme di strumenti, sarei grato.

Dopo abbiamo iniziato a guardare verso il VDI, ma per questo compito la maggior parte delle soluzioni è o troppo costosa, o richiede particolare attenzione. Volevamo uno strumento semplice, con il minor numero possibile di magie, la maggior parte dei problemi del quale potesse essere risolta con un semplice riavvio o riavvio del servizio. Fortunatamente, avevamo attrezzatura server, di classe low end, nelle filiali, dal servizio che stava per essere dismesso, che potevamo utilizzare come base tecnologica.

Cosa ne è risultato alla fine? Ecco, cosa ne è risultato, non posso raccontarvi, poiché è coperto da un NDA, ma durante la ricerca abbiamo sviluppato uno schema interessante, che ha mostrato buoni risultati nei test di laboratorio, anche se non è andato in produzione.

Un po' di disclaimer: l'autore non pretende che la soluzione proposta risolva completamente tutte le problematiche poste e lo fa volontariamente e con entusiasmo. L'autore riconosce in anticipo che il suo inglese è molto scarso. Poiché la soluzione non è più in fase di sviluppo, non si può contare su bug fix o modifiche funzionali, tutto è nelle vostre mani. L'autore presuppone che abbiate almeno una minima conoscenza di KVM e che abbiate letto un articolo introduttivo sul protocollo Spice e abbiate lavorato un po' con CentOS o con qualche altra distribuzione GNU/Linux.

In questo articolo vorrei esaminare la struttura della soluzione risultante, in particolare l'interazione tra client e server e la natura dei processi del ciclo di vita delle macchine virtuali all'interno della soluzione considerata. Se l'articolo risulterà interessante per il pubblico, descriverò i dettagli dell'implementazione delle immagini live per creare client leggeri basati su Fedora e parlerò dei dettagli della configurazione delle macchine virtuali e server KVM per ottimizzare le prestazioni e la sicurezza.

Se prendiamo 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

KVM (nedo)VDI con macchine virtuali monouso utilizzando bash

Tutta l'attrezzatura si trova all'interno della rete della filiale, l'unica connessione esterna è il canale internet. Il server proxy esiste storicamente, non rappresenta nulla di straordinario. Ma proprio su di esso, tra l'altro, avverrà la filtrazione del traffico dalle macchine virtuali (abbreviato in VM nel testo). Non c'è nulla che impedisca di posizionare questo servizio su un server KVM, l'unica cosa da monitorare è come cambierà il carico sul sistema di archiviazione.

Client Station – precisamente, le "stazioni self-service", il "frontend" del nostro servizio. Si tratta di nettops Lenovo IdeaCentre. Cosa ha di buono questo apparecchio? Praticamente tutto, in particolare la grande quantità 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 contro la scrittura, sulla quale è registrata un'immagine live modificata di Fedora 28. Naturalmente, al nettop sono collegati un monitor, una tastiera e un mouse.

Switch – un apparecchio switch di rete di secondo livello, non particolarmente distintivo, si trova nel server e lampeggia. Non è collegato a nessuna rete, a parte quella delle "stazioni self-service".

KVM_Server – il nucleo dello schema, nei test su banco Core 2 Quad Q9650 con 8 GB di RAM ha gestito senza problemi 3 macchine virtuali con Windows 10. Il sistema di archiviazione è Adaptec 3405 con 2 dischi in Raid 1 + SSD. Nei test sul campo, Xeon 1220 con LSI 9260 + SSD gestivano facilmente 5-6 VM. I server ci sono stati forniti da un servizio dismesso, gli investimenti iniziali sarebbero stati minimi. Su questo (questi) server è stata implementata la piattaforma di virtualizzazione KVM con un pool di macchine virtuali pool_Vm.

Vm – macchina virtuale, il backend 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 operano dhcpd, ntpd, httpd e xinetd ascolta sulla porta «signal».

Lo0 – interfaccia pseudo di loopback. Standard.

Spice_console – Una cosa molto interessante, in quanto, a differenza del classico RDP, quando si imposta il set KVM + Spice protocol, appare un'entità addizionale – la porta della console della macchina virtuale. Praticamente, collegandosi a questa porta TCP, otteniamo la console della Vm, senza la necessità di connettersi alla Vm attraverso la sua interfaccia di rete. Tutta l’interazione con la Vm avviene tramite la trasmissione del segnale, il server se ne occupa. L’analoga più vicina per funzionalità è l’IPKVM. Cioè, su questa porta viene trasmesso l’immagine dello schermo della VM, vengono inviati anche i dati sul movimento del mouse e (cosa più importante) l'interazione tramite il protocollo Spice permette di reindirizzare senza soluzione di continuità i dispositivi USB nella macchina virtuale, come se questo dispositivo fosse collegato alla stessa Vm. Verificato per unità flash, scanner e webcam.

Vnet0, virbr0 e le schede di rete virtuali Vm formano una rete di macchine virtuali.

Come FUNZIONA

Dal lato della Client Station

La stazione cliente si avvia in modalità grafica con un'immagine live modificata di Fedora 28, ricevendo un indirizzo IP tramite DHCP dallo spazio indirizzi della rete 169.254.24.0/24. Durante il processo di avvio vengono creati regole del firewall che consentono collegamenti alle porte «signal» e «spice» del server. Al termine dell'avvio, 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 del file client.conf
server_ip — indirizzo del KVM_Server
vdi_signal_port — porta del KVM_Server su cui è in esecuzione xinetd
vdi_spice_port — porta di rete del KVM_Server, da cui verrà effettuato il reindirizzamento della richiesta di connessione dal client remote-viewer alla porta spice della Vm dedicata (dettagli sotto)
animation_folder — cartella da cui vengono prelevate le immagini per la dimostrazione dell'animazione bullshit
background_folder — cartella da cui vengono prelevate le immagini per la presentazione in modalità attesa. Maggiori informazioni sull'animazione nella prossima parte dell'articolo.

Lo script remote.sh prende le impostazioni dal file di configurazione /etc/client.conf e stabilisce una connessione tramite nc sulla porta "vdi_signal_port" del server KVM, ricevendo un flusso di dati dal server, tra cui attende le righe "RULE ADDED, CONNECT NOW". Alla ricezione della riga cercata, si avvia il processo remote-viewer in modalità kiosk stabilendo una connessione sulla porta "vdi_spice_port" del server. L'esecuzione dello script è sospesa fino al completamento dell'esecuzione di remote-viewer.

Il remote-viewer, collegandosi alla porta "vdi_spice_port", grazie al redirect lato server, accede alla porta "spice_console" dell'interfaccia lo0, cioè alla console della macchina virtuale, e avviene, di fatto, il lavoro dell'utente. Durante l'attesa della connessione, all'utente viene mostrata un'animazione di intrattenimento, sotto forma di slideshow di file jpeg, il cui percorso è determinato dal valore della variabile animation_folder nel file di configurazione.

In caso di perdita della connessione con la porta "spice_console" della macchina virtuale, che segnala lo spegnimento/restart della macchina virtuale (cioè la fine effettiva della sessione utente), vengono terminati tutti i processi avviati a nome dell'utente autenticato, il che porta al riavvio di lightdm e al ritorno alla schermata di autorizzazione.

Dal lato del Server KVM

Sulla porta "signal" della scheda di rete enp5s0 attende una connessione xinetd. Dopo la connessione alla porta "signal", xinetd avvia lo script vm_manager.sh senza passarvi alcun parametro di input e reindirizza il risultato dell'esecuzione dello script nella sessione nc della 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 del file di configurazione vm_manager.conf
srv_scripts_dir — cartella contenente 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 inizieranno ad essere assegnate le porte delle console spice delle macchine virtuali
srv_tmp_dir — cartella per la memorizzazione dei file temporanei
base_host — Vm di base (immagine master) da cui verranno creati i cloni Vm nel pool
input_iface — interfaccia di rete del server, che guarda verso le Client Stations
vdi_spice_port — porta di rete del server, da cui avverrà il reindirizzamento della richiesta di connessione dal cliente remote-viewer alla porta spice della Vm dedicata
count_conn_tryes — un timer di attesa; al termine del quale si considera che non ci sia stata connessione alla VM (per maggiori dettagli, vedi vm_connect.sh)

Lo script vm_manager.sh legge il file di configurazione da vm_manager.conf, valuta lo stato delle macchine virtuali nel pool secondo diversi parametri, in particolare: quante VM sono state distribuite, se ci sono VM libere e pulite. A tal fine, legge il file clear.list che contiene i numeri delle porte 'spice_console' delle macchine virtuali 'recentemente create' (vedi sotto il ciclo di creazione VM) e verifica la presenza di una connessione stabilita con esse. Se viene trovato un porto con una connessione di rete attiva (cosa che non dovrebbe assolutamente accadere), viene emesso un avviso e la porta viene spostata in waste.list. Al primo porto trovato del file clear.list senza connessione, vm_manager.sh chiama lo script vm_connect.sh e gli passa come parametro il numero della 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' del server dell'interfaccia enp5s0 sulla 'spice console port' della VM, situata nell'interfaccia lo0 del server, fornita come parametro di avvio. La porta viene spostata in conn_wait.list e la VM è considerata in attesa di connessione. Al Client Station su 'signal' della porta del server viene inviata la stringa 'RULE ADDED, CONNECT NOW', attesa dallo script remote.sh in esecuzione. Inizia un ciclo di attesa per la connessione con un numero di tentativi definito dal valore della variabile 'count_conn_tryes' nel file di configurazione. Ogni secondo nella sessione nc verrà inviata la stringa 'RULE ADDED, CONNECT NOW' e verrà controllata l'esistenza di una connessione stabilita alla 'spice_console' port.

Se entro il numero di tentativi stabilito non si è stabilita alcuna connessione, la porta 'spice_console' viene spostata nuovamente in clear.list. L'esecuzione di vm_connect.sh termina e vm_manager.sh riprende l'esecuzione, avviando il ciclo di pulizia.

Se una connessione Client Station viene registrata sulla porta «spice_console» dell'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 un meccanismo di monitoraggio dello stato del firewall. In caso di interruzione della connessione, non sarà possibile ristabilire la connessione con la porta «spice_console». La porta «spice_console» viene spostata in waste.list, la VM è considerata «sporca» e non potrà tornare nel pool delle macchine virtuali «pulite» senza passare per una pulizia. L'esecuzione di vm_connect.sh termina, riprende l'esecuzione di vm_manager.sh, che avvia il ciclo di pulizia.

Il ciclo di pulizia inizia con la revisione del file waste.list in cui vengono trasferiti i numeri delle porte «spice_console» delle macchine virtuali con cui è stata stabilita una connessione. Viene determinata l'esistenza di una connessione attiva su ciascuna porta «spice_console» della lista. Se la connessione è assente, si considera che la macchina virtuale non sia più utilizzata e la porta viene spostata in recycle.list avviando il processo di eliminazione della macchina virtuale (vedi sotto) cui apparteneva questa porta. Se viene trovata una connessione di rete attiva sulla porta, si considera che la macchina virtuale sia utilizzata e non verranno intraprese azioni per essa. Se la porta non è in ascolto, si considera che la VM sia spenta e non più necessaria. La porta viene spostata in recycle.list e il processo di eliminazione della macchina virtuale viene avviato. A tal fine, viene chiamato lo script vm_delete.sh, al quale viene passato come parametro il numero della porta «spice_console» della VM da eliminare.

/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

L'eliminazione della macchina virtuale è un'operazione piuttosto triviale, lo script vm_delete.sh determina il nome della macchina virtuale a cui appartiene la porta passata come parametro di avvio. Viene eseguita un'interruzione forzata della VM, l'eliminazione della VM dall'ipervisore e la cancellazione del disco rigido virtuale di tale VM. La porta «spice_console» viene rimossa da recycle.list. L'esecuzione di vm_delete.sh termina, riprende l'esecuzione di vm_manager.sh.

Lo script vm_manager.sh, al termine delle operazioni di pulizia delle macchine virtuali in eccesso dalla lista waste.list, avvia il ciclo di creazione delle macchine virtuali nel pool.

Il processo inizia con la definizione delle porte "spice_console" disponibili per l'hosting. A tal fine, si parte dal parametro del file di configurazione "srv_start_port_pool" che stabilisce la porta iniziale per il pool "spice_console" delle macchine virtuali e dal parametro "srv_pool_size" che definisce il numero massimo di macchine virtuali. Viene quindi eseguita una scansione sequenziale di tutte le porte disponibili. Per ogni porta individuata, si cerca la sua presenza in clear.list, waste.list, conn_wait.list, recycle.list. Se la porta viene trovata in uno di questi file, viene considerata occupata e quindi 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 questo scopo viene richiamato lo script vm_create.sh al quale viene passato come parametro il numero della porta "spice_console" per la quale deve essere creata 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à effettuata la copia. Scarica la configurazione XML della VM dal database dell'hypervisor, esegue una serie di controlli sull'immagine qcow del disco della VM e, se questi vengono superati con successo, crea un file di configurazione XML per la nuova VM e un'immagine del disco "linked clone" della nuova VM. Successivamente, il file di configurazione XML della nuova VM viene caricato nel database dell'hypervisor e la VM viene avviata. La porta "spice_console" viene spostata da recycle.list a clear.list. L'esecuzione di vm_create.sh viene completata e termina l'esecuzione di vm_manager.sh.
Alla prossima connessione, tutto ricomincia da capo.

Per i casi di emergenza, è incluso uno script vm_clear.sh che passa in rassegna tutte le VM del pool e le rimuove, azzerando i valori delle liste. Richiamarlo all'avvio consente di iniziare il lavoro con un (semi)VDI completamente ripulito.

/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. Quanto esposto dovrebbe essere sufficiente per gli amministratori di sistema per provare il (semi)VDI sul campo. Se la comunità troverà interessante questo argomento, nella seconda parte parlerò della modifica del livecd di Fedora e della sua trasformazione in un chiosco.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster