KVM (unterbrochenes) VDI mit Einmal-VMs über bash

Für wen ist dieser Artikel gedacht

Dieser Artikel könnte für Systemadministratoren von Interesse sein, die vor der Aufgabe stehen, einen Service für "einmalige" Arbeitsplätze zu erstellen.

Prolog

Die IT-Abteilung eines jungen, dynamisch wachsenden Unternehmens mit einem kleinen regionalen Netzwerk hat die Anfrage erhalten, "Selbstbedienungsstationen" für die Nutzung durch externe Kunden zu organisieren. Diese Stationen sollten zur Registrierung auf externen Unternehmensportalen, zum Hochladen von Daten von externen Geräten und zur Nutzung von staatlichen Portalen verwendet werden.

Ein wichtiges Merkmal war die Tatsache, dass die meisten Softwareprodukte auf MS Windows ausgelegt sind (zum Beispiel "Deklaration"), und trotz der Bewegung in Richtung offener Formate bleibt MS Office der dominierende Standard im Austausch elektronischer Dokumente. Daher konnten wir bei der Lösung dieser Aufgabe nicht auf MS Windows verzichten.

Ein zentrales Problem war die Möglichkeit der Ansammlung verschiedener Daten von Benutzersitzungen, die zu deren Leckage an Dritte führen könnten. Eine solche Situation hat bereits das MFC in Schwierigkeiten gebracht.Im Gegensatz zu quasi-staatlichen (staatlichen autonomen Einrichtungen) MFC werden nichtstaatliche Organisationen für derartige Fehler viel härter bestraft. Ein weiteres kritisches Problem war die Anforderung, mit externen Datenträgern zu arbeiten, auf denen eindeutig eine Menge schädlicher Malware vorhanden sein würde. Die Wahrscheinlichkeit, Malware aus dem Internet einzuschleusen, wurde als weniger wahrscheinlich angesehen, da der Internetzugang durch eine Whitelist von Adressen eingeschränkt ist. Bei der Ausarbeitung der Anforderungen beteiligten sich Mitarbeiter anderer Abteilungen und brachten ihre Anforderungen und Wünsche ein, die endgültigen Anforderungen sahen folgendermaßen aus:

Anforderungen an die Informationssicherheit

  • Nach der Benutzung müssen alle Benutzerdaten (einschließlich temporärer Dateien und Registrierungskeys) gelöscht werden.
  • Alle Prozesse, die vom Benutzer gestartet wurden, müssen nach Beendigung der Arbeit abgeschlossen werden.
  • Internet-Zugang nach einer Whitelist von Adressen.
  • Einschränkungen für die Ausführung von Drittanbieter-Code.
  • Wenn eine Sitzung länger als 5 Minuten stillsteht, sollte die Sitzung automatisch beendet werden, die Station muss eine Bereinigung durchführen.

Kundenanforderungen

  • Die Anzahl der Clientstationen pro Filiale – nicht mehr als 4.
  • Die minimale Wartezeit bis zur Betriebsbereitschaft des Systems, von dem Moment an, in dem man sich „an den Stuhl setzt“, bis zum Beginn der Arbeit mit der Client-Software.
  • Die Möglichkeit, Peripheriegeräte (Scanner, USB-Sticks) direkt am Standort der „Selbstbedienungsstation“ anzuschließen.
  • Kundenwünsche
  • Demonstration von Werbematerialien (Bildern) während der Leerlaufzeit des Systems.

Die Qualen der Kreativität

Nachdem wir uns ausgiebig mit Windows Live-CDs beschäftigt hatten, kamen wir einstimmig zu dem Schluss, dass die resultierende Lösung mindestens 3 kritische Punkte nicht erfüllt. Sie laden entweder lange, sind nicht wirklich live oder die Anpassung war mit großen Schwierigkeiten verbunden. Möglicherweise haben wir schlecht gesucht, und Sie können ein Set von Werkzeugen empfehlen. Ich wäre dankbar dafür.

Anschließend schauten wir uns VDI-Lösungen an, aber für diese Aufgabe sind die meisten Lösungen entweder zu teuer oder erfordern besondere Aufmerksamkeit. Wir wünschten uns ein einfaches Werkzeug mit minimaler Magie, sodass die meisten Probleme durch einfaches Neustarten gelöst werden konnten. Glücklicherweise hatten wir Serverhardware der unteren Preisklasse in den Niederlassungen, die wir von einem stillgelegten Dienst verwenden konnten.

Was ist am Ende herausgekommen? Nun, was am Ende herausgekommen ist, kann ich Ihnen nicht erzählen, da NDA, aber im Verlauf unserer Suche entwickelten wir ein interessantes Schema, das sich in Labortests gut bewährt hat, auch wenn es nicht in die Serie ging.

Einige Hinweise: Der Autor erhebt keinen Anspruch darauf, dass die vorgeschlagene Lösung alle gestellten Aufgaben vollständig löst und dies freiwillig und mit einem Lied tut. Der Autor stimmt dem Argument zu, dass sein Englisch sehr schlecht ist. Da die Lösung nicht weiterentwickelt wird, können Sie nicht auf Bugfixes oder funktionale Änderungen hoffen, alles liegt in Ihren Händen. Der Autor geht davon aus, dass Sie ein wenig mit KVM vertraut sind, einen Überblicksartikel zum Spice-Protokoll gelesen haben und ein wenig mit CentOS oder einem anderen GNU/Linux-Distribution gearbeitet haben.

In diesem Artikel möchte ich das Gerüst der entstandenen Lösung untersuchen, nämlich die Interaktion zwischen Client und Server sowie die essenziellen Prozesse im Lebenszyklus von virtuellen Maschinen im Rahmen der betrachteten Lösung. Sollte der Artikel für das Publikum von Interesse sein, werde ich die Einzelheiten der Implementierung von Live-Images zur Erstellung von Thin-Clients auf Basis von Fedora beschreiben und die Details zur Konfiguration von virtuellen Maschinen erläutern. KVM-Server zur Optimierung der Leistung und Sicherheit.

Wenn man farbiges Papier nimmt,
Farbe, Pinsel und Kleber,
Und noch ein wenig Geschick…
Kann man hundert Rubel machen!

Schema und Beschreibung des Teststandes

KVM (unterbrochenes) VDI mit Einmal-VMs über bash

Die gesamte Hardware befindet sich innerhalb des Filialnetzwerks, nach außen geht nur der Internetkanal. Der Proxy-Server existiert historisch bereits, er ist nichts Außergewöhnliches. Aber gerade auf ihm wird unter anderem die Filterung des Traffics von virtuellen Maschinen (im Folgenden als VMs abgekürzt) stattfinden. Es steht nichts im Weg, diesen Dienst auf dem KVM-Server unterzubringen, einzig ist darauf zu achten, wie sich die Last auf das Speichersystem ändert.

Client-Station – tatsächlich die "Selbstbedienungsstationen", das "Frontend" unseres Dienstes. Sie sind Lenovo IdeaCentre-Nettops. Was macht dieses Gerät gut? Eigentlich so gut wie alles, besonders erfreuen die große Anzahl an USB-Anschlüssen und der Kartenleser auf der Vorderseite. In unserem Schema ist im Kartenleser eine SD-Karte mit aktiviertem Schreibschutz eingelegt, auf der ein modifiziertes Live-Image von Fedora 28 gespeichert ist. Selbstverständlich sind ein Monitor, eine Tastatur und eine Maus an den Nettop angeschlossen.

Switch – ein unauffälliger Hardware-Switch der zweiten Ebene, der im Serverraum steht und mit Lichtern blinkt. Er ist mit nichts verbunden außer dem Netzwerk der "Selbstbedienungsstationen".

KVM_Server – das Kernstück des Schemas, bei den Standversuchen hat der Core 2 Quad Q9650 mit 8 GB RAM zuverlässig 3 virtuelle Maschinen mit Windows 10 bewältigt. Das Speichersystem – Adaptec 3405 mit 2 Festplatten im RAID 1 + SSD. In den Feldversuchen haben Xeon 1220 und der wesentlich leistungsfähigere LSI 9260 + SSD problemlos 5-6 VMs betrieben. Die Server waren von einem ausgeschiedenen Dienst übrig geblieben, die Kapitalaufwendungen wären nicht hoch. Auf diesem/ diesen Server(n) ist das KVM-Virtualisierungssystem mit dem Pool von virtuellen Maschinen pool_Vm implementiert.

VM – virtuelle Maschine, das Backend unseres Dienstes. Hier erfolgt die Nutzerarbeit.

Enp5s0 – ein Netzwerkinterface, das auf das Netzwerk der "Selbstbedienungsstationen" zeigt. Auf ihm leben dhcpd, ntpd, httpd und xinetd hört auf den "signal" Port.

Lo0 – ein Pseudo-Interface der Loopback-Schnittstelle. Standardmäßig.

Spice_console – Eine sehr interessante Sache. Anders als beim klassischen RDP gibt es bei der Bereitstellung des KVM + Spice Protokolls eine zusätzliche Entität – den Konsolenport der virtuellen Maschine. Tatsächlich erhalten wir, indem wir uns mit diesem TCP-Port verbinden, die Konsole der VM, ohne uns über das Netzwerkinterface der VM verbinden zu müssen. Alle Interaktionen mit der VM hinsichtlich der Signalübertragung übernimmt der Server. Der nächstliegende funktionale Vergleich ist IPKVM. Das heißt, an diesen Port wird das Bild des Monitors der VM übertragen, ebenso wie die Daten über die Bewegungen der Maus und (was am wichtigsten ist) das Interagieren über das Spice-Protokoll ermöglicht es, USB-Geräte nahtlos in die virtuelle Maschine umzuleiten, als wären diese Geräte direkt mit der VM verbunden. Dies wurde für USB-Sticks, Scanner, Webcams geprüft.

Vnet0, virbr0 und die virtuellen Netzwerkkarten der VM bilden ein Netzwerk von virtuellen Maschinen.

Wie DAS funktioniert

Von der Clientstation

Die Clientstation bootet im grafischen Modus mit einem modifizierten Live-Image von Fedora 28 und erhält eine IP-Adresse über DHCP aus dem Adressbereich des Netzwerks 169.254.24.0/24. Während des Bootvorgangs werden Firewall-Regeln erstellt, die Verbindungen zu den "signal" und "spice" Ports des Servers ermöglichen. Nach Abschluss des Bootvorgangs wartet die Station auf die Authentifizierung des Benutzers "Client". Nach der Authentifizierung des Benutzers wird der Desktop-Manager "openbox" gestartet und das Autostart-Skript autostart im Namen des authentifizierten Benutzers ausgeführt. Unter anderem startet das Autostart-Skript das 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

Beschreibung der Variablen in der Datei client.conf
server_ip — die Adresse des KVM_Server
vdi_signal_port — der Port des KVM_Server, an dem xinetd "sitzt"
vdi_spice_port — der Netzwerkport des KVM_Server, über den die Anfrage von dem remote-viewer-Client an den Spice-Port der zugewiesenen VM umgeleitet wird (Details siehe unten)
animation_folder — der Ordner, aus dem die Bilder für die Demonstration der Bullshit-Animation stammen
background_folder — der Ordner, aus dem die Bilder für die Präsentationen im Wartemodus stammen. Weitere Informationen zur Animation im nächsten Teil des Artikels.

Das Skript remote.sh nimmt die Einstellungen aus der Konfigurationsdatei /etc/client.conf und stellt eine Verbindung mit dem KVM-Server über den «vdi_signal_port»-Port mithilfe von nc her. Es empfängt einen Datenstrom vom Server, in dem es die Zeile «RULE ADDED, CONNECT NOW» erwartet. Nach Erhalt der gesuchten Zeile wird der Prozess remote-viewer im Kiosk-Modus gestartet, wobei eine Verbindung zum «vdi_spice_port»-Port des Servers hergestellt wird. Die Ausführung des Skripts wird bis zum Abschluss von remote-viewer pausiert.

Der remote-viewer, der sich mit dem «vdi_spice_port»-Port verbindet, gelangt durch die Weiterleitung auf der Serverseite zum «spice_console»-Port des lo0-Interfaces, d.h. zur Konsole der virtuellen Maschine, und der Benutzer kann direkt arbeiten. Während des Verbindungsaufbaus wird dem Benutzer eine Bullshit-Animation in Form einer Diashow aus jpeg-Dateien angezeigt, wobei der Pfad zum Verzeichnis mit Bildern durch den Wert der Variablen animation_folder aus der Konfigurationsdatei definiert wird.

Bei einem Verbindungsabbruch zum «spice_console»-Port der virtuellen Maschine, der signalisiert, dass die virtuelle Maschine abgeschaltet oder neu gestartet wird (d.h. die Sitzung des Benutzers tatsächlich endet), werden alle Prozesse, die im Namen des autorisierten Benutzers gestartet wurden, beendet, was zu einem Neustart von lightdm und der Rückkehr zum Anmeldebildschirm führt.

Vom KVM-Server aus

Am «signal»-Port der Netzwerkkarte enp5s0 wartet xinetd auf Verbindungen. Nach dem Anschluss an den «signal»-Port startet xinetd das Skript vm_manager.sh, ohne ihm Eingabeparameter zu übergeben, und leitet das Ergebnis der Skriptausführung an die nc-Session der Client Station weiter.

/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

Beschreibung der Variablen in der Konfigurationsdatei vm_manager.conf
srv_scripts_dir — Verzeichnis, in dem sich die Skripte vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh befinden.
srv_pool_size — Größe des Vm-Pools.
srv_start_port_pool — Anfangsport, ab dem die Ports der Spice-Konsolen der virtuellen Maschinen zugewiesen werden.
srv_tmp_dir — Verzeichnis für temporäre Dateien.
base_host — Basis-Vm (Golden Image), von der Klone für den Pool erstellt werden.
input_iface — Netzwerkinterface des Servers, das auf die Client Stations zeigt.
vdi_spice_port — Netzwerkport des Servers, über den die Anfrage zur Verbindung vom remote-viewer-Client zum Spice-Port der zugewiesenen Vm umgeleitet wird.
count_conn_tryes – der Timer, nach dessen Ablauf angenommen wird, dass keine Verbindung zu Vm hergestellt werden konnte (Details zur Funktionsweise siehe vm_connect.sh)

Das Skript vm_manager.sh liest die Konfigurationsdatei vm_manager.conf aus, bewertet den Status der virtuellen Maschinen im Pool anhand mehrerer Parameter, nämlich: wie viele VMs bereitgestellt sind, ob es freie (unbenutzte) VMs gibt. Dazu liest es die Datei clear.list, in der die Nummern der „spice_console“-Ports der „neu geschaffenen“ (siehe unten im Erstellungszyklus der VMs) virtuellen Maschinen enthalten sind, und überprüft die Existenz einer Verbindung zu ihnen. Bei der Entdeckung eines Ports mit einer aktiven Netzwerkverbindung (was kategorisch nicht der Fall sein sollte) wird eine Warnung ausgegeben und der Port wird in waste.list verschoben. Bei der Entdeckung des ersten Ports aus der Datei clear.list, zu dem derzeit keine Verbindung besteht, ruft vm_manager.sh das Skript vm_connect.sh auf und übergibt die Nummer dieses Ports als Parameter.

/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

Das Skript vm_connect.sh fügt Firewall-Regeln hinzu, die einen Redirect vom „vdi_spice_port“ der Server-Schnittstelle enp5s0 zum „spice console port“ VM, der sich auf der lo0-Schnittstelle des Servers befindet und als Startparameter übergeben wird, erstellen. Der Port wird in conn_wait.list verschoben, die VM gilt als wartend auf eine Verbindung. In der Client Station-Sitzung wird über den „signal“-Port des Servers die Zeile „RULE ADDED, CONNECT NOW“ gesendet, die vom darauf laufenden Skript remote.sh erwartet wird. Ein Zyklus des Wartens auf Verbindung beginnt, dessen Anzahl der Versuche durch den Wert der Variable „count_conn_tryes“ aus der Konfigurationsdatei bestimmt wird. Jede Sekunde wird der nc-Sitzung die Zeile „RULE ADDED, CONNECT NOW“ übergeben, und es wird geprüft, ob eine Verbindung zum „spice_console“-Port besteht.

Wenn in der festgelegten Anzahl von Versuchen keine Verbindung hergestellt werden konnte, wird der „spice_console“-Port zurück in clear.list verschoben. Die Ausführung von vm_connect.sh wird beendet, und die Ausführung von vm_manager.sh wird fortgesetzt, das den Reinigungszyklus startet.

Wenn sich die Client Station mit dem „spice_console“ Port am lo0 Interface verbindet, werden die Firewall-Regeln, die eine Umleitung zwischen dem „spice“ Serverport und dem „spice_console“ Port erzeugen, entfernt und die weitere Aufrechterhaltung der Verbindung erfolgt durch den Statusüberwachungsmechanismus der Firewall. Im Falle eines Verbindungsabbruchs kann die Wiederherstellung der Verbindung zum „spice_console“ Port nicht mehr erfolgen. Der „spice_console“ Port wird in die waste.list verschoben, die VM gilt als „schmutzig“ und kann nicht ohne Bereinigung in den Pool der „sauberen“ virtuellen Maschinen zurückkehren. Die Ausführung von vm_connect.sh wird beendet, die Ausführung von vm_manager.sh wird wieder aufgenommen, welches den Bereinigungszyklus startet.

Der Bereinigungszyklus beginnt mit dem Durchsehen der Datei waste.list, in die die Nummern der „spice_console“ Ports der virtuellen Maschinen, mit denen eine Verbindung hergestellt wurde, verschoben werden. Es wird überprüft, ob eine aktive Verbindung an jedem „spice_console“ Port aus der Liste vorhanden ist. Wenn keine Verbindung besteht, wird angenommen, dass die virtuelle Maschine nicht mehr verwendet wird und der Port wird in die recycle.list verschoben, wonach der Prozess zur Löschung der zugehörigen virtuellen Maschine gestartet wird (siehe unten). Wenn eine aktive Netzwerkverbindung am Port erkannt wird, wird angenommen, dass die virtuelle Maschine in Gebrauch ist, und es werden keine Maßnahmen ergriffen. Wenn der Port nicht überwacht wird, wird angenommen, dass die VM ausgeschaltet und nicht mehr benötigt wird. Der Port wird in die recycle.list verschoben und der Prozess zur Löschung der virtuellen Maschine wird gestartet. Hierfür wird das Skript vm_delete.sh aufgerufen, dem als Parameter die Nummer des „spice_console“ Ports der zu löschenden VM übergeben wird.

/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

Die Löschung einer virtuellen Maschine ist eine relativ triviale Operation, das Skript vm_delete.sh ermittelt den Namen der virtuellen Maschine, zu der der als Startparameter übergebene Port gehört. Es erfolgt ein erzwungener Stopp der VM, die VM wird aus dem Hypervisor gelöscht und die virtuelle Festplatte dieser VM wird entfernt. Der „spice_console“ Port wird aus der recycle.list gelöscht. Die Ausführung von vm_delete.sh wird beendet, die Ausführung von vm_manager.sh wird wieder aufgenommen.

Das Skript vm_manager.sh beginnt nach Abschluss der Bereinigungsoperationen überflüssiger virtueller Maschinen aus der waste.list einen Zyklus zur Erstellung virtueller Maschinen im Pool.

Der Prozess beginnt mit der Feststellung der verfügbaren Ports für das Hosting von «spice_console». Hierbei erfolgt basierend auf dem Parameter der Konfigurationsdatei «srv_start_port_pool», der den Startport für den Pool «spice_console» virtueller Maschinen festlegt, sowie dem Parameter «srv_pool_size», der die maximale Anzahl virtueller Maschinen definiert, eine sequenzielle Durchsuchung aller möglichen Portvarianten. Für jeden festgelegten Port wird in clear.list, waste.list, conn_wait.list und recycle.list nach seinem Vorkommen gesucht. Wenn der Port in einer dieser Dateien gefunden wird, gilt er als belegt und wird übersprungen. Wird der Port in den angegebenen Dateien nicht gefunden, wird er in die Datei recycle.list aufgenommen und der Prozess zur Erstellung einer neuen virtuellen Maschine beginnt. Hierfür wird das Skript vm_create.sh aufgerufen, dem die Portnummer «spice_console» als Parameter übergeben wird, für den eine VM erstellt werden soll.

/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

Der Prozess zur Erstellung einer neuen virtuellen Maschine

Das Skript vm_create.sh liest aus der Konfigurationsdatei den Wert der Variablen «base_host», der das Vorbild der virtuellen Maschine definiert, auf Basis dessen ein Klon erstellt werden soll. Es lädt die XML-Konfiguration der VM aus der Hypervisor-Datenbank und führt eine Reihe von Prüfen des qcow-Images der VM durch. Nach erfolgreichen Prüfungen wird eine XML-Konfigurationsdatei für die neue VM und ein «linked clone» des Disk-Images der neuen VM erstellt. Danach wird die XML-Konfiguration der neuen VM in die Hypervisor-Datenbank geladen und die VM wird gestartet. Der Port «spice_console» wird von recycle.list in clear.list übertragen. Die Ausführung von vm_create.sh endet und die Ausführung von vm_manager.sh wird abgeschlossen.
Bei der nächsten Verbindung beginnt alles wieder von vorne.

Für Notfälle gibt es im Paket das Skript vm_clear.sh, das gezwungenermaßen alle VMs im Pool durchläuft und sie mit Zurücksetzen der Werte in den Listen entfernt. Sein Aufruf während des Startvorgangs ermöglicht es, (unfertige) VDI mit einem sauberen Blatt zu starten.

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

An dieser Stelle möchte ich den ersten Teil meiner Erzählung beenden. Das Gehörte sollte für Systemadministratoren ausreichend sein, um (unfertige) VDI in der Praxis auszuprobieren. Wenn die Community dieses Thema interessant findet, werde ich im zweiten Teil von der Modifikation des LiveCD Fedora und der Umwandlung in einen Kiosk berichten.

Quelle: habr.com

60GB SSD 8Gb DDR4