KVM (nedo)VDI s jednorázovými virtuálními stroji pomocí bash

Komu je tento článek určen

Tento článek může být zajímavý pro systémové administrátory, kteří čelili úkolu vytvořit službu „jednorázových“ pracovních míst.

Prolog

Do oddělení IT podpory mladé dynamicky se rozvíjející společnosti s malou regionální sítí se obrátili s žádostí o organizaci „stanic pro samoobsluhu“ pro využití jejich externími klienty. Tyto stanice se měly používat k registraci na externích portálech společnosti, ke stahování dat z externích zařízení a k práci s vládními portály.

Důležitým aspektem byla skutečnost, že většina softwaru je „zaměřena“ na MS Windows (například „Deklarace“), a přestože se směřuje k otevřeným formátům, MS Office zůstává dominujícím standardem při výměně elektronických dokumentů. Takže jsme se nemohli vzdát MS Windows při řešení tohoto úkolu.

Hlavním problémem se zdála být možnost akumulace různých dat uživatelských sezení, která by mohla vést k úniku třetím stranám. Taková situace již přivedla MFC. Ale na rozdíl od quasi státního (státní autonomní zařízení) MFC, za podobné nedostatky nevládní organizace budou potrestány mnohem přísněji. Dalším kritickým problémem bylo požadavek na práci s externími datovými nosiči, na kterých bude rozhodně spousta škodlivého softwaru. Pravděpodobnost zavlečení škodlivého softwaru z internetu byla považována za méně pravděpodobnou kvůli omezení přístupu na internet pouze na bílý seznam adres. K zpracování požadavků se připojili zaměstnanci jiných oddělení, kteří přinesli své požadavky a přání, konečné požadavky vypadaly následovně:

Požadavky na informační bezpečnost

  • Po použití musí být všechna uživatelská data (včetně dočasných souborů a klíčů registru) odstraněna.
  • Všechny procesy spuštěné uživateli by měly po dokončení práce skončit.
  • Přístup na internet podle bílého seznamu adres.
  • Omezení možnosti spuštění cizího kódu.
  • V případě nečinnosti relace delší než 5 minut by se relace měla automaticky ukončit a stanice by měla provést vyčištění.

Požadavky zákazníka

  • Počet klientských stanic na pobočku – maximálně 4.
  • Minimální doba čekání na připravenost systému, od okamžiku „usednutí na židli“ do zahájení práce s klientským softwarem.
  • Možnost připojení periferních zařízení (skenery, USB disky) přímo z místa instalace „stanice samoobsluhy“.
  • Požadavky zákazníka
  • Prezentace reklamních materiálů (obrázky) v okamžiku nečinnosti komplexu.

Muky tvůrce

Po dostatečném hraní si s Windows livecd jsme dospěli k jednomyslnému závěru, že vzniklé řešení nevyhovuje minimálně 3 kritickým bodům. Buď se pomalu načítají, nebo nejsou zcela „live“, nebo jejich přizpůsobení bylo spojeno s obrovskými obtížemi. Možná jsme špatně hledali, a vy můžete doporučit nějakou sadu nástrojů, budu vděčný.

Poté jsme se začali dívat směrem k VDI, ale pro tento úkol většina řešení buď stojí příliš, nebo vyžaduje velkou pozornost. Chtělo by to jednoduchý nástroj s minimem magie, kde většinu problémů by šlo vyřešit prostým restartem/služby. Naštěstí jsme měli serverové vybavení, low-end třídy v pobočkách, z vyřazované služby, které jsme mohli použít jako technologickou základnu.

Co nakonec vzniklo? O tom, co nakonec vzniklo, vám nemohu říct, protože NDA, ale během hledání jsme vyvinuli zajímavou schému, která se dobře osvědčila při laboratorních testech, i když nebyla uvedena do sériové výroby.

Trochu disclaimerů: autor netvrdí, že navrhované řešení zcela řeší všechny stanovené úkoly a dělá to dobrovolně a s písní. Autor předem souhlasí s tvrzením, že Sein Englishe sprache is zehr schlecht. Protože řešení se již dále nerozvíjí, na opravy chyb nebo změny funkčnosti se spoléhat nedá, vše je ve vašich rukou. Autor předpokládá, že jste alespoň trochu obeznámeni s KVM a četli jste přehledový článek o protokolu Spice a také trochu pracovali s CentOS nebo jinou distribucí GNU Linux.

V tomto článku bych chtěl rozebrat jádro navrženého řešení, konkrétně interakci mezi klientem a serverem a podstatu procesů životního cyklu virtuálních strojů v rámci zkoumaného řešení. Pokud bude článek publiku zajímavý, popíšu detaily implementace live obrazů pro vytváření tenkých klientů na základě Fedora a povím o detailech nastavení virtuálních strojů a servery KVM pro optimalizaci výkonu a bezpečnosti.

Pokud vezmeme barevný papír,
Barvy, štětce a lepidlo,
A ještě trochu šikovnosti…
Můžete udělat sto korun!

Schéma a popis testovacího standu

KVM (nedo)VDI s jednorázovými virtuálními stroji pomocí bash

Všechno vybavení je umístěno uvnitř sítě pobočky, ven jde pouze internetový kanál. Proxy server již historicky existoval, nic mimořádného to není. Právě na něm se mimo jiné bude provádět filtraci provozu z virtuálních strojů (dále zkráceně VMs). Nic nebrání tomu umístit tuto službu na server KVM, jediné, co je třeba sledovat, je, jak se změní zatížení disku.

Client Station – vlastně ‚stanice samoobsluhy‘, ‚frontend‘ naší služby. Jsou to nettopové počítače Lenovo IdeaCentre. Čím je tento přístroj dobrý? Téměř vším, zejména potěší velké množství USB portů a čtečka karet na předním panelu. V naší schémě je v čtečce karet vložena SD karta s aktivovanou hardwarovou ochranou proti zápisu, na které je zapsán modifikovaný live obraz Fedora 28. Samozřejmě je k nettopu připojen monitor, klávesnice a myš.

Switch – nijak nezajímavý hardwarový switch druhé úrovně, stojí v serverovně a bliká diodami. Nechal se připojit pouze do sítě ‚stanic samoobsluhy‘.

KVM_Server – jádro schématu, ve standových testech Core 2 Quad Q9650 s 8 GB RAM si bez problémů poradil se 3 virtuálními stroji s Windows10. Diskový subsystém – adaptec 3405 2 disky Raid 1 + SSD. V terénních testech Xeon 1220 s vážným LSI 9260 + SSD snadno zvládal 5-6 VMs. Servery jsme získali od zaniklé služby, kapitálové náklady by nebyly velké. Na tomto serveru (serverech) je nasazena virtualizační systém KVM s pool systémem virtuálních strojů pool_Vm.

Vm – virtuální stroj, backend naší služby. V něm probíhá práce uživatele.

Enp5s0 – síťový interface směřující k síti "stanic samoobsluhy", na němž běží dhcpd, ntpd, httpd a xinetd naslouchá na portu "signal".

Lo0 – pseudointerface smyčky. Standardní.

Spice_console – velmi zajímavá věc, protože na rozdíl od klasického RDP, při nasazení spojení KVM + Spice protocol se objevuje další entita – konzolový port virtuálního stroje. V podstatě, připojením na tento TCP port získáváme konzoli VM, aniž bychom se museli připojovat k VM přes její síťový interface. Všechny interakce s VM prostřednictvím přenosu signálu bere server na sebe. Nejbližší analog podle funkce – IPKVM. Tedy na tento port se přenáší obraz monitoru VM, na nějž se také přenášejí údaje o pohybu myši, a (co je nejdůležitější) interakce prostřednictvím Spice protokolu umožňuje bezproblémové přesměrování USB zařízení do virtuálního stroje, jako by toto zařízení bylo připojeno přímo k VM. Otestováno pro flash disky, skenery, webkamery.

Vnet0, virbr0 a virtuální síťové karty VM vytvářejí síť virtuálních strojů.

Jak TO funguje

Z pohledu Klientské stanice

Klientská stanice se spouští v grafickém režimu z modifikovaného live obrazu Fedora 28, získává IP adresu přes DHCP z adresního prostoru sítě 169.254.24.0/24. Během spouštění se vytvářejí pravidla firewallu, která umožňují spojení na porty "signal" a "spice" serveru. Po dokončení spouštění stanice čeká na autorizaci uživatele "Client". Po autorizaci uživatele se spouští správce pracovních ploch "openbox" a vykonává se spouštěcí skript autostart jménem autorizovaného uživatele. Mezi jiným, spouštěcí skript spouští skript 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

Popis proměnných souboru client.conf
server_ip – adresa KVM_Server
vdi_signal_port – port KVM_Server, na kterém běží xinetd
vdi_spice_port – síťový port KVM_Server, ze kterého dojde k přesměrování požadavku na připojení od klienta remote-viewer na spice port vyhrazené VM (podrobnosti níže)
animation_folder – složka, odkud se berou obrázky pro předvádění bullshit animace
background_folder – složka, odkud se berou obrázky pro předvádění prezentací v režimu čekání. Více o animaci v další části článku.

Skript remote.sh načítá nastavení z konfiguračního souboru /etc/client.conf a pomocí nc se připojí na port "vdi_signal_port" KVM serveru a získává datový tok ze serveru, přičemž očekává řetězce "RULE ADDED, CONNECT NOW". Po obdržení požadovaného řetězce se spustí proces remote-viewer v kioskovém režimu, který navazuje spojení na port "vdi_spice_port" serveru. Provádění skriptu se pozastaví do okamžiku skončení procesu remote-viewer.

Remote-viewer, když se připojí na port "vdi_spice_port", díky přesměrování na serverové straně, se dostává na port "spice_console" rozhraní lo0, tj. na konzoli virtuálního stroje, a dochází k práci uživatele. Během očekávání připojení se uživateli zobrazuje bullshit animace ve formě slideshow z JPEG souborů, přičemž cesta k adresáři s obrázky je určena hodnotou proměnné animation_folder z konfiguračního souboru.

Při ztrátě spojení s portem "spice_console" virtuálního stroje, signalizujícího vypnutí/nebo restart virtuálního stroje (tj. skutečné ukončení uživatelské relace), dochází k ukončení všech procesů spuštěných jménem autorizovaného uživatele, což vede k restartu lightdm a návratu na přihlašovací obrazovku.

Ze strany KVM Serveru

Na portu "signal" síťové karty enp5s0 čeká na připojení xinetd. Po připojení na "signal" port xinetd spouští skript vm_manager.sh bez předání jakýchkoli vstupních parametrů a přesměrovává výstup provádění skriptu do nc relace 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

Popis proměnných konfiguračního souboru vm_manager.conf
srv_scripts_dir – adresář, kde se nacházejí skripty vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size – velikost poolu Vm
srv_start_port_pool – počáteční port, od kterého začne umístění portů spice konzolí virtuálních strojů
srv_tmp_dir – adresář pro umístění dočasných souborů
base_host – základní Vm (zlatý obraz), ze které budou vytvářeny klony Vm v poolu
input_iface – síťové rozhraní serveru, směřující směrem k Client Stations
vdi_spice_port – síťový port serveru, ze kterého dojde k přesměrování žádosti o připojení od klienta remote-viewer na spice port vyhrazeného Vm
count_conn_tryes — časovač čekání, po jehož uplynutí se považuje, že k Vm nedošlo k připojení (podrobnosti v práci viz vm_connect.sh)

Skript vm_manager.sh čte konfigurační soubor z vm_manager.conf, vyhodnocuje stav virtuálních strojů v poolu podle několika parametrů, a to: kolik VM je nasazeno, zda jsou dostupné volné čisté VM. Za tímto účelem čte soubor clear.list, ve kterém jsou obsažena čísla portů "spice_console" "čerstvě vytvořených" (viz níže cyklus vytváření V.M.) virtuálních strojů a kontroluje přítomnost aktivního připojení k nim. Při detekci portu s aktivním síťovým připojením, což by nemělo být, se zobrazí varování a port se přesune do waste.list. Při detekci prvního portu ze souboru clear.list, se kterým v současnosti není připojení, skript vm_manager.sh vyvolá skript vm_connect.sh a předá mu jako parametr číslo tohoto portu.

/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 přidává pravidla do firewallu, které vytváří přesměrování portu "vdi_spice_port" serverového rozhraní enp5s0 na "spice console port" VM, umístěném na rozhraní lo0 serveru, předaném jako parametr spuštění. Port se přesune do conn_wait.list, VM je považována za čekající na připojení. Do sezení Client Station na "signal" portu serveru se předává řetězec "RULE ADDED, CONNECT NOW", který očekává spuštěný skript remote.sh. Začíná cyklus čekání na připojení s počtem pokusů určeným hodnotou proměnné "count_conn_tryes" z konfiguračního souboru. Každou sekundu bude do сеsегncе nc předáván řetězec "RULE ADDED, CONNECT NOW" a kontrolována přítomnost aktivního připojení k portu "spice_console".

Pokud se v ustanoveném počtu pokusů nepodaří připojení, port "spice_console" se zpět přesune do clear.list. Provádění vm_connect.sh končí, pokračuje se ve vykonávání vm_manager.sh, který spouští čistící cyklus.

Pokud je připojení Client Station k portu „spice_console“ na rozhraní lo0 zaregistrováno, pravidla firewallu, která vytvářejí přesměrování mezi portem „spice“ serveru a portem „spice_console“, jsou odstraněna a další udržování spojení probíhá díky mechanismu určování stavu firewallu. V případě přerušení spojení nebude možné znovu navázat spojení s portem „spice_console“. Port „spice_console“ je přesunut do waste.list, VM je považována za „špinavou“ a vrátit se do fondu „čistých“ virtuálních strojů bez provedení vyčištění nebude možné. Spuštění skriptu vm_connect.sh končí, pokračuje se ve spuštění vm_manager.sh, který spouští cyklus vyčištění.

Cyklus vyčištění začíná prohlídkou souboru waste.list, do kterého jsou přesouvány čísla portů „spice_console“ virtuálních strojů, se kterými bylo navázáno spojení. Určuje se přítomnost aktivního spojení na každém portu „spice_console“ ze seznamu. Pokud spojení chybí, považuje se, že virtuální stroj již není používán, a port je přesunut do recycle.list a spouští se proces odstranění virtuálního stroje (viz níže), kterému tento port patřil. Pokud je na portu zjištěno aktivní síťové spojení, považuje se, že virtuální stroj je používán a pro něj se nepodnikají žádné akce. Pokud port není sledován, považuje se, že VM je vypnuta a již není potřeba. Port je přesunut do recycle.list a spouští se proces odstranění virtuálního stroje. K tomu se volá skript vm_delete.sh, kterému je jako parametr předáno číslo portu „spice_console“ virtuálního stroje, který je třeba odstranit.

/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

Odstranění virtuálního stroje je poměrně triviální operace, skript vm_delete.sh určuje název virtuálního stroje, kterému port náležící jako parametr spuštění. Provádí se nucené zastavení VM, odstranění VM z hypervizoru, by se měl odstranit virtuální pevný disk této VM. Port „spice_console“ je odstraněn z recycle.list. Spuštění vm_delete.sh končí, pokračuje se ve spuštění vm_manager.sh.

Skript vm_manager.sh, po dokončení operací čištění zbytečných virtuálních strojů z seznamu waste.list, začíná cyklus vytváření virtuálních strojů v fondu.

Proces začíná určením dostupných portů „spice_console“ pro nasazení. To se provádí na základě parametru konfiguračního souboru „srv_start_port_pool“, který určuje počáteční port pro pool „spice_console“ virtuálních strojů, a parametru „srv_pool_size“, který definuje maximální počet virtuálních strojů. Následně dojde k postupnému procházení všech možných variant portů. Pro každý určený port se vyhledává v souborech clear.list, waste.list, conn_wait.list, recycle.list. Pokud se port nalézá v jakémkoli z těchto souborů, považuje se za obsazený a přeskočí se. Pokud port ve zmíněných souborech nebyl nalezen, je vložen do souboru recycle.list a začíná proces vytváření nového virtuálního stroje. K tomu se spustí skript vm_create.sh, kterému se jako parametr předává číslo portu „spice_console“, pro který je třeba vytvořit 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

Proces vytváření nového virtuálního stroje

Skript vm_create.sh čte z konfiguračního souboru hodnotu proměnné „base_host“, která určuje šablonu virtuálního stroje, na jejímž základě bude vytvořen klon. Provádí export xml konfigurace VM z databáze hypervizoru, realizuje řadu kontrol qcow obrazu disku VM a při jejich úspěšném dokončení vytváří xml konfigurační soubor pro novou VM a „linked clone“ obraz disku nové VM. Poté je xml konfigurace nové VM nahrána do databáze hypervizoru a VM se spustí. Port „spice_console“ se přesouvá z recycle.list do clear.list. Provádění vm_create.sh končí a provádění vm_manager.sh se uzavírá.
Při dalším připojení vše začíná znovu.

Pro havarijní případy je k dispozici skript vm_clear.sh, který násilně prochází všechny VM z poolu a odstraňuje je se smazáním hodnot listů. Jeho volání v průběhu načítání umožňuje začít práci (nedo)VDI s čistým štítem.

/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="

Tímto bych chtěl zakončit první část svého vyprávění. To, co bylo uvedeno, by mělo být dostatečné pro systémové administrátory, aby mohli vyzkoušet nedoVDI v praxi. Pokud se komunitě tato téma bude zdát zajímavá, v druhé části se podělím o modifikaci livecd Fedora a o tom, jak ji proměnit v kiosk.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster