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 "Einmal"-Arbeitsplätze zu erstellen.
Prolog
Die IT-Abteilung eines jungen, dynamisch wachsenden Unternehmens mit einem kleinen regionalen Netzwerk hat die Anfrage gestellt, "Selbstbedienungsstationen" für die Nutzung durch externe Kunden einzurichten. Diese Stationen sollten für die Registrierung auf externen Portalen des Unternehmens, das Hochladen von Daten von externen Geräten und die Arbeit mit staatlichen Portalen verwendet werden.
Ein wichtiger Aspekt war, dass die meisten Softwarelösungen auf MS Windows optimiert sind (zum Beispiel "Deklaration"), und trotz des Trends zu offenen Formaten bleibt MS Office der dominierende Standard im Austausch elektronischer Dokumente. Daher konnten wir bei der Lösung dieser Aufgabe nicht auf MS Windows verzichten.
Das Hauptproblem war die Möglichkeit der Ansammlung verschiedener Daten aus Benutzersitzungen, die zu deren Leckage an Dritte führen könnten. . Im Gegensatz zu quasi-staatlichen Institutionen (staatliche autonome Einrichtungen) werden nichtstaatliche Organisationen für ähnliche Mängel jedoch deutlich härter bestraft. Das nächste kritische Problem war die Anforderung, mit externen Datenträgern zu arbeiten, auf denen zweifellos viele schädliche Software vorhanden sein wird. Die Wahrscheinlichkeit, dass Malware aus dem Internet eingeschleppt wird, wurde als weniger wahrscheinlich erachtet, da der Zugang zum Internet durch eine Whitelist von Adressen eingeschränkt ist. Mitarbeiter anderer Abteilungen beteiligten sich an der Ausarbeitung der Anforderungen und brachten ihre Anforderungen und Wünsche ein. Die endgültigen Anforderungen sahen wie folgt aus:
Sicherheitsanforderungen
- Nach der Nutzung müssen alle Benutzerdaten (einschließlich temporärer Dateien und Registrierungsschlüssel) gelöscht werden.
- Alle vom Benutzer gestarteten Prozesse sollten nach Beendigung der Arbeit beendet werden.
- Zugang zum Internet nur über eine Whitelist von Adressen.
- Einschränkungen beim Start von Drittanbieter-Code.
- Bei Inaktivität von mehr als 5 Minuten sollte die Sitzung automatisch beendet werden, und die Station sollte eine Bereinigung durchführen.
Anforderungen des Kunden
- Maximalanzahl der Arbeitsstationen pro Filiale – nicht mehr als 4.
- Minimale Wartezeit bis zur Betriebsbereitschaft des Systems, vom Moment des 'Setzens auf den Stuhl' bis zum Start der Arbeit mit der Client-Software.
- Möglichkeit, Peripheriegeräte (Scanner,USB-Sticks) direkt am Ort der Installation der 'Selbstbedienungsstation' anzuschließen.
- Kundenwünsche
- Präsentation von Werbematerialien (Bilder) während der Leerlaufzeiten des Systems.
Das kreative Ringen
Nach zahlreichen Experimenten mit Windows Live-CDs sind wir zu dem einhelligen Schluss gekommen, dass die entstandene Lösung in mindestens 3 kritischen Punkten nicht zufriedenstellend war. Sie starten entweder langsam, sind nicht wirklich live oder ihre Anpassung war mit enormen Schwierigkeiten verbunden. Möglicherweise haben wir nicht gründlich genug gesucht, und Sie könnten einen Werkzeug-Set empfehlen; ich wäre Ihnen dankbar.
Danach begannen wir, uns in Richtung VDI umzusehen, doch die meisten Lösungen für diese Aufgabe erwiesen sich als entweder zu teuer oder erforderten eine sorgfältige Überwachung. Wir wollten jedoch ein einfaches Tool mit minimalem Aufwand, dessen viele Probleme durch einen einfachen Neustart bzw. Dienstneustart gelöst werden konnten. Glücklicherweise hatten wir Serverhardware der niedrigeren Klasse in den Filialen, die von einem abgeschalteten Dienst stammt, die wir als technologische Basis nutzen konnten.
Was ist letztendlich daraus geworden? Was genau dabei herauskam, kann ich Ihnen leider nicht erzählen, da ich an eine Geheimhaltungsvereinbarung gebunden bin, aber im Verlauf unserer Suche haben wir ein interessantes Schema entwickelt, das sich in Laborversuchen gut bewährt hat, auch wenn es nicht in die Serienproduktion ging.
Einige Hinweise: Der Autor beansprucht nicht, dass die vorgeschlagene Lösung alle gestellten Aufgaben vollständig löst und tut dies freiwillig und mit einem Lächeln. Der Autor stimmt dem Gedanken zu, dass sein Englisch sehr schlecht ist. Da die Lösung nicht weiterentwickelt wird, ist mit Bugfixes oder funktionalen Änderungen nicht zu rechnen – alles liegt in Ihrer Hand. Der Autor geht davon aus, dass Sie zumindest ein wenig mit KVM vertraut sind und einen Überblick über das Spice-Protokoll gelesen haben sowie ein wenig mit CentOS oder einer anderen GNU/Linux-Distribution gearbeitet haben.
In diesem Artikel möchte ich den Kern der entstandenen Lösung untersuchen, insbesondere die Interaktion zwischen Client und Server sowie die Lebenszyklusprozesse virtueller Maschinen im Rahmen der betrachteten Lösung. Sollte der Artikel für das Publikum von Interesse sein, werde ich Details zur Implementierung von Live-Images zur Erstellung von Thin Clients auf Fedora-Basis beschreiben und über die Details der Konfiguration virtueller Maschinen berichten. KVM-Server zur Optimierung der Leistung und Sicherheit.
Wenn man buntes Papier nimmt,
Farben, Pinsel und Kleber,
Und ein wenig Geschick…
Kann man hundert Rubel machen!
Schema und Beschreibung des Teststandes

Alle Geräte befinden sich im Netzwerk der Niederlassung, nach außen führt nur der Internetkanal. Ein Proxy-Server war historisch bereits vorhanden, er stellt nichts Außergewöhnliches dar. Doch genau auf diesem wird unter anderem der Datenverkehr von virtuellen Maschinen (kurz VMs im Folgenden) gefiltert. Nichts hindert uns daran, diesen Dienst auf einem KVM-Server bereitzustellen, das einzige, was zu beachten ist, ist, wie sich die Belastung auf das Speichersystem ändern wird.
Client Station – das sind tatsächlich die „Selbstbedienungsstationen“, das „Frontend“ unseres Services. Sie bestehen aus Lenovo IdeaCentre Nettops. Was macht dieses Gerät besonders? Fast alles, besonders erfreut die große Anzahl an USB-Anschlüssen und der Kartenleser an der Vorderseite. In unserem Setup ist eine SD-Karte mit aktivem Schreibschutz im Kartenleser eingelegt, auf der ein modifiziertes Live-Image von Fedora 28 gespeichert ist. Natürlich ist der Nettop mit einem Monitor, einer Tastatur und einer Maus verbunden.
Switch – ein unauffälliger hardwaremäßiger Switch der zweiten Ebene, steht im Serverraum und blinkt mit Lichtern. Er ist mit keinem Netzwerk außer dem Netzwerk der „Selbstbedienungsstationen“ verbunden.
KVM_Server – Das Kernstück des Systems, das in den Benchmarks mit einem Core 2 Quad Q9650 und 8 GB RAM sicher 3 virtuelle Maschinen mit Windows 10 unterstützte. Die Speicherlösung – Adaptec 3405 mit 2 Festplatten im RAID 1 + SSD. In praktischen Tests bewältigten Xeon 1220 mit einem leistungsfähigeren LSI 9260 + SSD problemlos 5-6 VMs. Diese Server hätten wir von einem auslaufenden Dienst erhalten, die Investitionskosten wären gering gewesen. Auf diesem Server (bzw. diesen Servern) wurde das KVM-Virtualisierungssystem mit dem Pool virtueller Maschinen pool_Vm eingerichtet.
Vm – die virtuelle Maschine, das Backend unseres Dienstes. Hier erfolgt die Benutzerarbeit.
Enp5s0 – das Netzwerkinterface, das zur Seite der "Selbstbedienungsstationen" zeigt, darauf laufen dhcpd, ntpd, httpd, und xinetd lauscht dem "signal" Port.
Lo0 – ein Pseudo-Loopback-Interface. Standardmäßig.
Spice_console – Ein sehr interessantes Tool. Im Gegensatz zum klassischen RDP entsteht beim Einsatz der KVM+Spice-Protokoll-Kombination eine zusätzliche Komponente – der Konsolenport der virtuellen Maschine. Durch die Verbindung zu diesem TCP-Port erhalten wir Zugriff auf die VM-Konsole, ohne die VM über ihr Netzwerkschnittstelle erreichen zu müssen. Alle Signalübertragungen zur VM werden vom Server übernommen. Der näheste Funktionsteilnehmer ist das IPKVM. Das Bild des VM-Monitors wird über diesen Port übertragen, wobei auch die Mausbewegungen gesendet werden. Besonders wichtig ist, dass die Interaktion über das Spice-Protokoll eine nahtlose Weiterleitung von USB-Geräten zur virtuellen Maschine ermöglicht, als wäre das Gerät direkt an die VM angeschlossen. Dies wurde für USB-Sticks, Scanner und Webcams überprüft.
Vnet0, virbr0 und virtuelle Netzwerkkarten der VM bilden ein Netzwerk für virtuelle Maschinen.
Wie DAS funktioniert
Von der Client-Station-Seite
Die Client-Station wird im grafischen Modus mit einem modifizierten Live-Image von Fedora 28 gestartet und erhält eine IP-Adresse per DHCP aus dem Adressraum des Netzwerks 169.254.24.0/24. Während des Startvorgangs werden Firewall-Regeln erstellt, die Verbindungen zu den "signal" und "spice" Ports des Servers ermöglichen. Nach dem Abschluss des Starts wartet die Station auf die Authentifizierung des Benutzers "Client". Nach der Authentifizierung 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, auf dem xinetd läuft
vdi_spice_port — der Netzwerkport des KVM_Server, über den die Verbindung vom 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 Anzeige der Bullshit-Animation stammen
background_folder — der Ordner, aus dem die Bilder für die Präsentation im Wartezustand stammen. Weitere Informationen zur Animation finden Sie im nächsten Abschnitt des Artikels.
Das Skript remote.sh entnimmt die Einstellungen aus der Konfigurationsdatei /etc/client.conf und stellt mithilfe von nc eine Verbindung zum KVM-Server über den Port »vdi_signal_port« her, um einen Datenstrom vom Server zu empfangen, wobei es auf die Zeichenfolgen »RULE ADDED, CONNECT NOW« wartet. Beim Empfang der gesuchten Zeichenfolge wird der Prozess remote-viewer im Kioskmodus gestartet, wobei die Verbindung zum Port »vdi_spice_port« des Servers hergestellt wird. Die Ausführung des Skripts wird bis zum Ende der Ausführung des remote-viewers pausiert.
Remote-viewer verbindet sich über den Port »vdi_spice_port« und gelangt durch eine Umleitung auf der Serverseite zum Port »spice_console« der Schnittstelle lo0, d.h. zur Konsole der virtuellen Maschine, und ermöglicht so die Benutzerinteraktion. Während der Wartezeit auf die Verbindung wird dem Benutzer eine schlechte Animation in Form einer Diashow von JPEG-Dateien angezeigt, wobei der Pfad zum Verzeichnis mit Bildern durch den Wert der Variable animation_folder aus der Konfigurationsdatei bestimmt wird.
Bei einem Verlust der Verbindung zur »spice_console«-Port der virtuellen Maschine, der das Herunterfahren oder Neustarten der virtuellen Maschine signalisiert (d.h. das tatsächliche Ende der Benutzersitzung), werden alle Prozesse, die im Namen des autorisierten Benutzers gestartet wurden, beendet. Dies führt zum Neustart von lightdm und zur Rückkehr zum Anmeldebildschirm.
Von der KVM-Server-Seite
Am »signal«-Port der Netzwerkkarte enp5s0 wartet xinetd auf Verbindungen. Nach dem Verbindungsaufbau zum »signal«-Port startet xinetd das Skript vm_manager.sh, ohne diesem Eingabeparameter zu übergeben, und leitet das Ergebnis der Skriptausführung in die nc-Sitzung 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 der Konfigurationsdatei vm_manager.conf
srv_scripts_dir — Verzeichnis für die Skripte vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — Größe des Vm-Pools
srv_start_port_pool — Anfangsport, nach dem die Ports der spice-Konsolen der virtuellen Maschinen platziert werden
srv_tmp_dir — Verzeichnis für temporäre Dateien
base_host — die grundlegende VM (Gold-Image), von der Klone der VM im Pool erstellt werden
input_iface — das Netzwerkinterface des Servers, das zu den Clientstationen zeigt
vdi_spice_port — der Netzwerkport des Servers, über den die Anfrage zur Verbindung vom Remote-Viewer-Client an den Spice-Port der zugewiesenen VM weitergeleitet wird
count_conn_tryes — der Wartezeit-Timer, nach dessen Ablauf angenommen wird, dass keine Verbindung zur VM hergestellt wurde (Details zur Funktionsweise siehe vm_connect.sh)
Das Skript vm_manager.sh liest die Konfigurationsdatei vm_manager.conf ein und bewertet den Status der virtuellen Maschinen im Pool anhand mehrerer Parameter. Dazu gehört, wie viele VMs bereitgestellt sind und ob es freie, nicht zugewiesene VMs gibt. Hierfür wird die Datei clear.list ausgelesen, in der die Nummern der 'spice_console'-Ports der 'neuen' (siehe unten im Erstellungszyklus der VMs) virtuellen Maschinen aufgeführt sind. Es wird geprüft, ob eine Verbindung zu ihnen besteht. Wenn ein Port mit einer aktiven Netzwerkverbindung gefunden wird (was kategorisch nicht der Fall sein sollte), wird eine Warnung ausgegeben und der Port wird in waste.list verschoben. Bei der Erkennung des ersten Ports aus der Datei clear.list, zu dem momentan keine Verbindung besteht, ruft vm_manager.sh das Skript vm_connect.sh auf und übergibt ihm die Portnummer 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 eine Weiterleitung des "vdi_spice_port" des Server-Interfaces enp5s0 auf den "spice console port" der VM ermöglichen, die sich am lo0-Interface des Servers befindet und als Startparameter übergeben wird. Der Port wird in der conn_wait.list gespeichert, und die VM wird als wartend auf eine Verbindung angesehen. An den "signal"-Port des Servers wird die Nachricht "RULE ADDED, CONNECT NOW" gesendet, die von dem auf der Client Station laufenden Skript remote.sh erwartet wird. Es beginnt eine Schleife zum Warten auf eine Verbindung mit einer Anzahl an Versuchen, die durch den Wert der Variablen "count_conn_tryes" aus der Konfigurationsdatei definiert wird. Jede Sekunde wird in die nc-Sitzung die Nachricht "RULE ADDED, CONNECT NOW" gesendet und die bestehende Verbindung zum "spice_console"-Port überprüft.
Wenn nach der festgelegten Anzahl an Versuchen keine Verbindung hergestellt werden konnte, wird der "spice_console"-Port zurück in die clear.list verschoben. Die Ausführung von vm_connect.sh wird beendet, und die Ausführung von vm_manager.sh wird fortgesetzt, das einen Reinigungszyklus startet.
Wenn eine Verbindung der Client Station zum „spice_console“-Port auf der Schnittstelle lo0 festgestellt wird, werden die Firewall-Regeln, die eine Weiterleitung zwischen dem „spice“-Port des Servers und dem „spice_console“-Port erstellen, entfernt. Die weitere Aufrechterhaltung der Verbindung erfolgt über den Mechanismus zur Zustandsbestimmung der Firewall. Im Falle einer Trennung der Verbindung kann der Kontakt zum „spice_console“-Port nicht wiederhergestellt werden. Der „spice_console“-Port wird in die waste.list verschoben, die VM gilt als „schmutzig“ und kann nicht ohne Bereinigung in den Pool „sauberer“ virtueller Maschinen zurückkehren. Die Ausführung von vm_connect.sh wird beendet, und die Ausführung von vm_manager.sh wird wieder aufgenommen, die den Bereinigungszyklus startet.
Der Reinigungszyklus beginnt mit der Überprüfung der Datei waste.list, in die die Nummern der "spice_console"-Ports der virtuellen Maschinen übertragen werden, zu denen eine Verbindung hergestellt wurde. Es wird festgestellt, ob an jedem "spice_console"-Port aus der Liste eine aktive Verbindung besteht. Ist keine Verbindung vorhanden, wird die virtuelle Maschine als nicht mehr verwendet betrachtet, und der Port wird in die recycle.list verschoben. Daraufhin wird der Prozess zur Löschung der virtuellen Maschine gestartet (siehe unten), zu der dieser Port gehörte. Wird eine aktive Netzwerkverbindung am Port festgestellt, gilt die virtuelle Maschine als in Gebrauch, und es werden keine Maßnahmen ergriffen. Wenn der Port nicht überwacht wird, wird davon ausgegangen, dass die VM ausgeschaltet und nicht mehr benötigt wird. Der Port wird in die recycle.list verschoben und der Löschprozess der virtuellen Maschine wird eingeleitet. Zu diesem Zweck wird das Skript vm_delete.sh aufgerufen, dem die Nummer des "spice_console"-Ports der zu löschenden VM als Parameter ü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
Das Löschen einer virtuellen Maschine ist ein relativ trivialer Vorgang. Das Skript vm_delete.sh bestimmt den Namen der virtuellen Maschine, zu der der übergebene Port gehört. Es erfolgt ein erzwungener Stopp der VM, das Löschen der VM aus dem Hypervisor sowie das Entfernen der virtuellen Festplatte dieser VM. Der Port „spice_console“ wird aus der recycle.list entfernt. Die Ausführung von vm_delete.sh wird beendet und die Ausführung von vm_manager.sh wird fortgesetzt.
Das Skript vm_manager.sh beginnt, nach Abschluss der Bereinigung überflüssiger virtueller Maschinen aus der Liste waste.list, mit der Schleife zum Erstellen von virtuellen Maschinen im Pool.
Der Prozess beginnt mit der Bestimmung der verfügbaren „spice_console“-Ports für die Bereitstellung. Basierend auf dem Parameter der Konfigurationsdatei „srv_start_port_pool“, der den Startport für den „spice_console“-Pool von virtuellen Maschinen festlegt, und dem Parameter „srv_pool_size“, der die maximale Anzahl virtueller Maschinen angibt, erfolgt eine sequenzielle Überprüfung aller möglichen Port-Optionen. Für jeden bestimmten Port wird in den Dateien clear.list, waste.list, conn_wait.list und recycle.list nach seiner Existenz gesucht. Wenn der Port in einer dieser Dateien gefunden wird, gilt er als belegt und wird übersprungen. Wenn der Port in den angegebenen Dateien nicht gefunden wird, wird er in die Datei recycle.list eingetragen, und der Prozess zur Erstellung einer neuen virtuellen Maschine beginnt. Dazu wird das Skript vm_create.sh aufgerufen, dem die Nummer des „spice_console“-Ports übergeben wird, für den die 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 den Wert der Variable «base_host» aus der Konfigurationsdatei, welche das Vorbild der virtuellen Maschine definiert, von dem der Clone erstellt wird. Es extrahiert die XML-Konfiguration der VM aus der Hypervisor-Datenbank, führt eine Reihe von Prüfungen am qcow-Image der VM durch und erstellt bei erfolgreichem Abschluss eine XML-Konfigurationsdatei für die neue VM sowie ein „linked clone“-Image der neuen VM. Daraufhin wird die XML-Konfiguration der neuen VM in die Hypervisor-Datenbank geladen und die VM gestartet. Der Port „spice_console“ wird von recycle.list nach clear.list verschoben. Das Skript vm_create.sh wird beendet und die Ausführung von vm_manager.sh abgeschlossen.
Bei der nächsten Verbindung beginnt alles von vorne.
Für Notfälle gibt es das Skript vm_clear.sh, das zwangsweise alle VMs aus dem Pool durchläuft und sie mit Rücksetzung der Listenwerte entfernt. Der Aufruf zu Beginn ermöglicht es, mit einem frischen Blatt (Fehler-)VDI 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="
Damit möchte ich den ersten Teil meiner Erzählung abschließen. Der Inhalt sollte ausreichen, damit Systemadministratoren das VDI in der Praxis ausprobieren können. Wenn die Community dieses Thema interessant findet, werde ich im zweiten Teil über die Modifikation des LiveCD Fedora und deren Umwandlung in einen Kiosk berichten.
Quelle: habr.com
