KVM (défaillant) VDI avec des machines virtuelles jetables utilisant bash

À qui s'adresse cet article

Cet article peut intéresser les administrateurs système qui ont la tâche de créer un service de « postes de travail à usage unique ».

Prologue

Le service informatique d'une jeune entreprise dynamique en pleine croissance, avec un petit réseau régional, a demandé à organiser des « stations d'auto-service » à destination de leurs clients externes. Ces stations étaient censées être utilisées pour s'inscrire sur les portails externes de l'entreprise, télécharger des données à partir de dispositifs externes et travailler avec des portails gouvernementaux.

Un aspect important était que la majorité des logiciels étaient « conçus » pour MS Windows (par exemple, « Déclaration »), et malgré le mouvement vers des formats ouverts, MS Office reste la norme dominante dans l'échange de documents électroniques. Ainsi, nous ne pouvions pas nous passer de MS Windows pour résoudre ce problème.

Le principal problème était la possibilité d'accumuler des données de session utilisateur qui pourraient être divulguées à des tiers. Une telle situation a déjà mis en difficulté le MFC. Mais contrairement aux quasi-organismes d'État (établissement autonome d'État), des organisations non gouvernementales seront bien plus sévèrement sanctionnées pour de telles carences. Le problème suivant le plus critique était l'exigence de travailler avec des supports de données externes, sur lesquels il y aura indéniablement une multitude de logiciels malveillants. La probabilité d'introduction de logiciels malveillants via Internet était considérée comme moins probable, en raison de la limitation de l'accès Internet à une liste blanche d'adresses. Les employés d'autres départements ont été impliqués dans l'élaboration des exigences, ajoutant leurs propres exigences et souhaits, les exigences finales ressemblaient à ceci :

Exigences en matière de sécurité de l'information

  • Après utilisation, toutes les données utilisateur (y compris les fichiers temporaires et les clés de registre) doivent être supprimées.
  • Tous les processus lancés par l'utilisateur doivent être arrêtés à la fin du travail.
  • Accès Internet sur la base d'une liste blanche d'adresses.
  • Restrictions sur la possibilité de lancer du code tiers.
  • En cas d'inactivité de la session pendant plus de 5 minutes, la session doit se terminer automatiquement, la station doit procéder à un nettoyage.

Exigences du client

  • Nombre de stations clients par agence – pas plus de 4.
  • Le temps d'attente minimum pour la préparation du système, depuis le moment où l'on s'assoit jusqu'au début du travail avec le logiciel client.
  • Possibilité de connecter des périphériques (scanners, clés USB) directement depuis le lieu d'installation de la « station de libre-service ».
  • Souhaits du client
  • Démonstration de matériel publicitaire (images) pendant les temps d'arrêt du complexe.

Les douleurs créatives

Après avoir largement joué avec les livecd Windows, nous sommes parvenus à un consensus : la solution obtenue ne satisfait pas au moins 3 critères critiques. Soit elles démarrent lentement, soit elles ne sont pas vraiment live, soit leur personnalisation a entraîné des douleurs considérables. Peut-être avons-nous mal cherché, et vous pourrez recommander un ensemble d'outils, je vous en serais reconnaissant.

Par la suite, nous avons commencé à nous intéresser au VDI, mais pour cette tâche, la plupart des solutions sont soit trop coûteuses, soit nécessitent une attention particulière. Nous recherchions un outil simple, avec un minimum de magie, dont la plupart des problèmes pouvaient être résolus par un simple redémarrage/redémarrage du service. Heureusement, nous disposions de matériel serveur de classe basique dans les filiales, provenant d'un service obsolète, que nous pouvions utiliser comme base technologique.

Que nous est-il finalement arrivé ? Eh bien, je ne pourrai pas vous le dire, car c'est soumis à un NDA, mais au cours de nos recherches, nous avons développé un schéma intéressant qui s'est bien comporté lors des tests en laboratoire, même s'il n'est pas passé en production.

Quelques disclaimers : l'auteur ne prétend pas que la solution proposée résout complètement toutes les tâches posées et le fait bénévolement et avec enthousiasme. L'auteur convient à l'avance que son anglais est très mauvais. Comme cette solution n'est plus développée, il ne faut pas s'attendre à des corrections de bugs ou à des modifications de fonctionnalités, tout est entre vos mains. L'auteur suppose que vous avez au moins une connaissance de base de KVM et que vous avez lu un article de présentation sur le protocole Spice, et que vous avez un peu travaillé avec Centos ou une autre distribution GNU Linux.

Dans cet article, je voudrais examiner l'ossature de la solution obtenue, à savoir l'interaction entre le client et le serveur ainsi que la nature des processus liés au cycle de vie des machines virtuelles dans le cadre de la solution abordée. Si l'article suscite l'intérêt du public, je décrirai les détails de la mise en œuvre d'images live pour créer des clients légers basés sur Fedora et parlerai des détails de la configuration des machines virtuelles et serveurs KVM pour optimiser les performances et la sécurité.

Si l'on prend du papier coloré,
Des peintures, des pinceaux et de la colle,
Et encore un peu d'habilité…
On peut gagner cent roubles !

Schéma et description du banc d'essai

KVM (défaillant) VDI avec des machines virtuelles jetables utilisant bash

Tout l'équipement se trouve à l'intérieur du réseau de la filiale, seule la connexion Internet sort. Un serveur proxy existait déjà historiquement, il n'a rien d'extraordinaire. Mais c'est précisément sur celui-ci, entre autres, que filtrera le trafic des machines virtuelles (abrégé en VM dans le texte). Rien n'empêche de placer ce service sur un serveur KVM, la seule chose à surveiller est comment la charge sur le système de disque en sera affectée.

Client Station – les « stations de libre-service », le « frontend » de notre service. Ce sont des nettops Lenovo IdeaCentre. Qu'est-ce qui rend cet appareil intéressant ? À peu près tout, surtout le grand nombre de ports USB et le lecteur de cartes en façade. Dans notre schéma, une carte SD avec une protection matérielle contre l'écriture est insérée dans le lecteur de cartes, sur laquelle est enregistrée une image live modifiée de Fedora 28. Bien sûr, un moniteur, un clavier et une souris sont connectés au nettop.

Switch – un switch matériel de deuxième niveau ordinaire, se trouve dans la salle des serveurs et clignote avec des voyants. Il n'est connecté à aucun réseau, sauf au réseau des stations de libre-service.

KVM_Server – le noyau du schéma, lors des tests sur banc, un Core 2 Quad Q9650 avec 8 Go de RAM gérait confortablement 3 machines virtuelles tournant sous Windows 10. Le système de disque – adaptec 3405 avec 2 disques en Raid 1 + SSD. Lors des tests sur le terrain, un Xeon 1220 avec un LSI 9260 plus performant + SSD gérait facilement 5 à 6 VM. Les serveurs nous ont été fournis par un service abandonné, les coûts d'investissement n'auraient pas été élevés. Sur ce(s) serveur(s), un système de virtualisation KVM avec un pool de machines virtuelles pool_Vm a été déployé.

Vm – machine virtuelle, backend de notre service. C'est là que se fait le travail de l'utilisateur.

Enp5s0 – une interface réseau se dirigeant vers le réseau des « stations en libre-service », où résident dhcpd, ntpd, httpd, et xinetd écoute le port « signal ».

Lo0 – une interface pseudo de boucle de retour. Standard.

Spice_console – une chose très intéressante, car contrairement au RDP classique, avec la combinaison KVM + le protocole Spice, apparaît une entité supplémentaire – le port de la console de la machine virtuelle. En fait, en se connectant à ce port TCP, on obtient la console de la VM, sans avoir besoin de se connecter à la VM via son interface réseau. Toute interaction avec la VM pour le transfert de signal est gérée par le serveur. L'analogue le plus proche par fonction – IPKVM. C'est-à-dire que sur ce port est transmis l'image de l'écran de la VM, ainsi que les données de déplacement de la souris, et (ce qui est le plus important) l'interaction via le protocole Spice permet de rediriger sans latence des périphériques USB vers la machine virtuelle, comme si ce périphérique était connecté à la VM elle-même. Testé pour les clés USB, les scanners, et les webcams.

Vnet0, virbr0 et les cartes réseau virtuelles de la VM forment le réseau des machines virtuelles.

Comment CECI fonctionne

Du côté de la Station Client

La station cliente démarre en mode graphique à partir d'une image live modifiée de Fedora 28 et obtient une adresse IP via DHCP dans l'espace d'adresses du réseau 169.254.24.0/24. Au cours du démarrage, des règles de pare-feu sont créées, permettant des connexions aux ports « signal » et « spice » du serveur. Après le démarrage, la station attend l'autorisation de l'utilisateur « Client ». Après l'authentification de l'utilisateur, le gestionnaire de fenêtres « openbox » est lancé et le script d'automatisation autostart est exécuté au nom de l'utilisateur authentifié. Parmi d'autres choses, le script d'automatisation lance le script remote.sh.

$HOME/.config/openbox/scripts/remote.sh

#!/bin/sh

server_ip=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "server_ip" 
|/usr/bin/cut -d "=" -f2)
vdi_signal_port=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "vdi_signal_port" 
 |/usr/bin/cut -d "=" -f2)
vdi_spice_port=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "vdi_spice_port" 
|/usr/bin/cut -d "=" -f2)
animation_folder=$(/usr/bin/cat /etc/client.conf |/usr/bin/grep "animation_folder" 
|/usr/bin/cut -d "=" -f2)

process=/usr/bin/remote-viewer

while true
do
 if [ -z `/usr/bin/pidof feh` ]
 then
 /usr/bin/echo $animation_folder
 /usr/bin/feh -N -x -D1 $animation_folder &
 else
 /usr/bin/echo
 fi
/usr/bin/nc -i 1 $server_ip $vdi_signal_port |while read line
 do
  if /usr/bin/echo "$line" |/usr/bin/grep "RULE ADDED, CONNECT NOW!"
  then
   /usr/bin/killall feh
   pid_process=$($process "spice://$server_ip:$vdi_spice_port"  
   "--spice-disable-audio" "--spice-disable-effects=animation"  
   "--spice-preferred-compression=auto-glz" "-k" 
   "--kiosk-quit=on-disconnect" | /bin/echo $!)
   /usr/bin/wait $pid_process
   /usr/bin/killall -u $USER
   exit
  else
   /usr/bin/echo $line >> /var/log/remote.log
  fi
 done
done

/etc/client.conf

server_ip=169.254.24.1
vdi_signal_port=5905
vdi_spice_port=5906
animation_folder=/usr/share/backgrounds/animation
background_folder=/usr/share/backgrounds2/fedora-workstation

Description des variables du fichier client.conf
server_ip — adresse du KVM_Server
vdi_signal_port — port du KVM_Server sur lequel « repose » xinetd
vdi_spice_port — port réseau du KVM_Server à partir duquel la requête de connexion du client remote-viewer sera redirigée vers le port spice de la VM dédiée (plus de détails ci-dessous)
animation_folder — dossier d'où proviennent les images pour la démonstration de l'animation bullshit
background_folder — dossier d'où proviennent les images pour la présentation des démonstrations en attente. Plus d'informations sur l'animation dans la prochaine partie de l'article.

Le script remote.sh prend les paramètres du fichier de configuration /etc/client.conf et établit une connexion via nc sur le port "vdi_signal_port" du serveur KVM pour recevoir un flux de données du serveur, en attendant la chaîne "RULE ADDED, CONNECT NOW". Lors de la réception de cette chaîne, le processus remote-viewer se lance en mode kiosque en établissant une connexion sur le port "vdi_spice_port" du serveur. L'exécution du script est suspendue jusqu'à la fin de l'exécution de remote-viewer.

Remote-viewer, en se connectant au port "vdi_spice_port", grâce à un redirigeant côté serveur, se retrouve sur le port "spice_console" de l'interface lo0, c'est-à-dire sur la console de la machine virtuelle, permettant ainsi l'interaction de l'utilisateur. Pendant l'attente de la connexion, l'utilisateur voit une animation de type bullshit, sous la forme d'un diaporama de fichiers jpeg, le chemin vers le dossier contenant les images étant déterminé par la variable animation_folder dans le fichier de configuration.

Lors de la perte de la connexion avec le port "spice_console" de la machine virtuelle, signalant l'arrêt/redémarrage de la machine virtuelle (c'est-à-dire la fin effective de la session utilisateur), tous les processus lancés au nom de l'utilisateur autorisé sont terminés, ce qui entraîne le redémarrage de lightdm et le retour à l'écran d'authentification.

Du côté du serveur KVM

Sur le port "signal" de l'interface réseau enp5s0, xinetd attend une connexion. Après la connexion au port "signal", xinetd exécute le script vm_manager.sh sans lui passer de paramètres d'entrée et redirige le résultat de l'exécution du script vers la session nc de la Station Client.

/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

Description des variables du fichier de configuration vm_manager.conf
srv_scripts_dir — dossier contenant les scripts vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — taille du pool Vm
srv_start_port_pool — port initial après lequel débutera l'attribution des ports des consoles spice des machines virtuelles
srv_tmp_dir — dossier pour le stockage des fichiers temporaires
base_host — Vm de base (image maître) à partir de laquelle des clones Vm seront créés dans le pool
input_iface — interface réseau du serveur, orientée vers les Stations Clients
vdi_spice_port — port réseau du serveur, à partir duquel la demande de connexion du client remote-viewer sera redirigée vers le port spice de la Vm dédiée
count_conn_tryes — temporisateur d'attente, après lequel il est considéré qu'aucune connexion à la VM n'a eu lieu (voir le fonctionnement dans vm_connect.sh)

Le script vm_manager.sh lit le fichier de configuration depuis vm_manager.conf, évalue l'état des machines virtuelles dans le pool selon plusieurs paramètres, à savoir : combien de VM sont déployées, s'il y a des VM libres. Pour ce faire, il lit le fichier clear.list qui contient les numéros des ports « spice_console » des machines virtuelles « nouvellement créées » (voir ci-dessous le cycle de création des VM) et vérifie la présence d'une connexion établie avec elles. Lorsqu'il détecte un port avec une connexion réseau active (ce qui ne devrait absolument pas se produire), un avertissement est émis et le port est transféré à waste.list. Lorsqu'il découvre le premier port dans clear.list avec lequel il n'y a actuellement pas de connexion, vm_manager.sh appelle le script vm_connect.sh et lui transmet comme paramètre le numéro de ce port.

/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

Le script vm_connect.sh ajoute des règles de pare-feu qui créent un redirection « vdi_spice_port » du port du serveur d'interface enp5s0 vers le « port spice console » de la VM, situé sur l'interface lo0 du serveur, passé comme paramètre de lancement. Le port est transféré à conn_wait.list, la VM est considérée comme en attente de connexion. À la station client, une chaîne « RULE ADDED, CONNECT NOW » est envoyée sur le port « signal » du serveur, attendue par le script remote.sh en cours d'exécution. Un cycle d'attente de connexion commence avec un nombre de tentatives défini par la variable « count_conn_tryes » dans le fichier de configuration. Chaque seconde, la chaîne « RULE ADDED, CONNECT NOW » sera envoyée à la session nc et la présence d'une connexion établie avec le port « spice_console » sera vérifiée.

Si, après le nombre de tentatives défini, aucune connexion n'est établie, le port « spice_console » est transféré à nouveau dans clear.list. L'exécution de vm_connect.sh se termine, la reprise de l'exécution de vm_manager.sh commence, qui lance le cycle de nettoyage.

Si une connexion Client Station est établie au port «spice_console» sur l'interface lo0, les règles du pare-feu qui créent une redirection entre le port «spice» du serveur et le port «spice_console» sont supprimées, et le maintien de la connexion se fait grâce au mécanisme de détection de l'état du pare-feu. En cas de rupture de la connexion, il ne sera pas possible de rétablir la connexion avec le port «spice_console». Le port «spice_console» est déplacé dans waste.list, la VM est considérée comme «sale» et ne pourra pas revenir dans le pool des machines virtuelles «propres» sans passer par un processus de nettoyage. L'exécution de vm_connect.sh se termine, et l'exécution de vm_manager.sh reprend, qui lance le cycle de nettoyage.

Le cycle de nettoyage commence par l'examen du fichier waste.list, dans lequel sont transférés les numéros des ports «spice_console» des machines virtuelles avec lesquelles une connexion a été établie. La présence d'une connexion active sur chaque port «spice_console» de la liste est déterminée. En l'absence de connexion, on considère que la machine virtuelle n'est plus utilisée, et le port est déplacé dans recycle.list, déclenchant le processus de suppression de la machine virtuelle (voir ci-dessous) à laquelle ce port appartenait. Si une connexion réseau active est détectée sur le port, la machine virtuelle est considérée comme utilisée, et aucune action n'est entreprise à son égard. Si le port n'est pas en écoute, on considère que la VM est éteinte et n'est plus nécessaire. Le port est transféré dans recycle.list et le processus de suppression de la machine virtuelle est lancé. Pour cela, le script vm_delete.sh est appelé, avec le numéro du port «spice_console» de la VM à supprimer comme paramètre.

/home/admin/scripts_vdi_new/vm_delete.sh


#!/bin/sh

#<Set local VARIABLES>#
port_to_delete="$1"
date=$(/usr/bin/date)
#</Set local VARIABLES>#

/usr/bin/echo "# $date START EXECUTE VM_DELETE.SH#"
/usr/bin/echo "TRY DELETE VM ON PORT: $vm_port"

#<VM NAME SETUP>#
vm_name_part1=$(/usr/bin/cat /etc/vm_manager.conf |/usr/bin/grep 'base_host' 
|/usr/bin/cut -d'=' -f2)
vm_name=$(/usr/bin/echo "$vm_name_part1""-""$port_to_delete")
#</VM NAME SETUP>#

#<SHUTDOWN AND DELETE VM>#
/usr/bin/virsh destroy $vm_name
/usr/bin/virsh undefine $vm_name
/usr/bin/rm -f /var/lib/libvirt/images_write/$vm_name.qcow2
/usr/bin/sed -i "/$port_to_delete/d" $SRV_TMP_DIR/recycle.list
#</SHUTDOWN AND DELETE VM>#

/usr/bin/echo "VM ON PORT $vm_port HAS BEEN DELETE AND REMOVE" 
 "FROM RECYCLE.LIST. EXIT FROM VM_DELETE.SH"
/usr/bin/echo "# $date STOP EXECUTE VM_DELETE.SH#"
exit

La suppression d'une machine virtuelle est une opération relativement triviale, le script vm_delete.sh détermine le nom de la machine virtuelle à laquelle appartient le port passé en paramètre. Un arrêt forcé de la VM est effectué, la VM est supprimée de l'hyperviseur, et le disque dur virtuel de cette VM est supprimé. Le port «spice_console» est supprimé de recycle.list. L'exécution de vm_delete.sh se termine, et l'exécution de vm_manager.sh reprend.

Le script vm_manager.sh, à la fin des opérations de nettoyage des machines virtuelles superflues de la liste waste.list, commence le cycle de création de machines virtuelles dans le pool.

Le processus commence par la détermination des ports « spice_console » disponibles pour l'hébergement. Pour ce faire, en fonction du paramètre dans le fichier de configuration « srv_start_port_pool », qui définit le port de départ pour le pool « spice_console » des machines virtuelles, et du paramètre « srv_pool_size », qui détermine le nombre maximal de machines virtuelles, toutes les options de ports possibles sont examinées successivement. Pour chaque port identifié, une recherche dans clear.list, waste.list, conn_wait.list, recycle.list est réalisée. Si le port est trouvé dans l'un de ces fichiers, il est considéré comme occupé et est ignoré. Si le port n'est pas trouvé dans les fichiers spécifiés, il est ajouté au fichier recycle.list et le processus de création d'une nouvelle machine virtuelle commence. Pour cela, le script vm_create.sh est appelé avec comme paramètre le numéro de port « spice_console » pour lequel il est nécessaire de créer la VM.

/home/admin/scripts_vdi_new/vm_create.sh


#!/bin/sh
/usr/bin/echo "#" "$date" "START RUNNING VM_CREATE.SH#"

new_vm_port=$1
date=$(/usr/bin/date)
a=0
/usr/bin/echo SRV_TMP_DIR=$SRV_TMP_DIR

#<SET LOCAL VARIABLES FOR SCRIPT>#
base_host=$(/usr/bin/cat /etc/vm_manager.conf |/usr/bin/grep "base_host" 
|/usr/bin/cut -d "=" -f2)
/usr/bin/echo "base_host=$base_host"
#</SET LOCAL VARIABLES FOR SCRIPT>#

hdd_image_locate() {

/bin/echo "Run STEP 1 - hdd_image_locate"

hdd_base_image=$(/usr/bin/virsh dumpxml $base_host  
|/usr/bin/grep "source file" |/usr/bin/grep "qcow2" |/usr/bin/head -n 1 
|/usr/bin/cut -d "'" -f2)
if [ -z "$hdd_base_image" ]
then
 /bin/echo "base hdd image not found!"
else
 /usr/bin/echo "hdd_base_image found is a $hdd_base_image. Run next step 2"

#< CHECK FOR SNAPSHOT ON BASE HDD >#

  if [ 0 -eq `/usr/bin/qemu-img info "$hdd_base_image" | /usr/bin/grep -c "Snapshot"` ]
  then
  /usr/bin/echo "base image haven't snapshot, run NEXT STEP 3"
  else
  /usr/bin/echo "base hdd image have a snapshot, can't use this image"
  exit
  fi
#</ CHECK FOR SNAPSHOT ON BASE HDD >#

#< CHECK FOR HDD IMAGE IS LINK CLONE >#
  if [ 0 -eq `/usr/bin/qemu-img info "$hdd_base_image" |/usr/bin/grep -c "backing file"
  then
  /usr/bin/echo "base image is not a linked clone, NEXT STEP 4"
  /usr/bin/echo "Base image check complete!"
  else
  /usr/bin/echo "base hdd image is a linked clone, can't use this image"
  exit
  fi
fi
#</ CHECK FOR HDD IMAGE IS LINK CLONE >#
cloning
    }

cloning() {
# <Step_1 turn the base VM off >#
 /usr/bin/virsh shutdown $base_host > /dev/null 2>&1
 # </Step_1 turn the base VM off >#

#<Create_vm_config>#

/usr/bin/echo "Free port for Spice VM is $new_vm_port"

 #<Setup_name_for_new_VM>#
new_vm_name=$(/bin/echo $base_host"-"$new_vm_port)
#</Setup_name_for_new_VM>#

#<Make_base_config_as_clone_base_VM>#
/usr/bin/virsh dumpxml $base_host > $SRV_TMP_DIR/$new_vm_name.xml
#<Make_base_config_as_clone_base_VM>#

##<Setup_New_VM_Name_in_config>##
/usr/bin/sed -i "s%<name>$base_host</name>%<name>$new_vm_name</name>%g" $SRV_TMP_DIR/$new_vm_name.xml
#</Setup_New_VM_Name_in_config>#

#<UUID Changing>#
old_uuid=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml |/usr/bin/grep "<uuid>")
/usr/bin/echo old UUID $old_uuid
new_uuid_part1=$(/usr/bin/echo "$old_uuid" |/usr/bin/cut -d "-" -f 1,2)
new_uuid_part2=$(/usr/bin/echo "$old_uuid" |/usr/bin/cut -d "-" -f 4,5)
new_uuid=$(/bin/echo $new_uuid_part1"-"$new_vm_port"-"$new_uuid_part2)
/usr/bin/echo $new_uuid
/usr/bin/sed -i "s%$old_uuid%$new_uuid%g" $SRV_TMP_DIR/$new_vm_name.xml
#</UUID Changing>#


#<Spice port replace>#
old_spice_port=$(/usr/bin/cat  $SRV_TMP_DIR/$new_vm_name.xml  
|/usr/bin/grep "graphics type='spice' port=")
/bin/echo old spice port $old_spice_port
new_spice_port=$(/usr/bin/echo "<graphics type='spice' port='$new_vm_port' autoport='no' listen='127.0.0.1'>")
/bin/echo $new_spice_port
/usr/bin/sed -i "s%$old_spice_port%$new_spice_port%g" $SRV_TMP_DIR/$new_vm_name.xml
#</Spice port replace>#

#<MAC_ADDR_GENERATE>#
mac_new=$(/usr/bin/hexdump -n6 -e '/1 ":%02X"' /dev/random|/usr/bin/sed s/^://g)
/usr/bin/echo New Mac is $mac_new
#</MAC_ADDR_GENERATE>#

#<GET OLD MAC AND REPLACE>#
mac_old=$(/usr/bin/cat $SRV_TMP_DIR/$new_vm_name.xml |/usr/bin/grep "mac address=")
/usr/bin/echo old mac is $mac_old
/usr/bin/sed -i "s%$mac_old%$mac_new%g" $SRV_TMP_DIR/$new_vm_name.xml
#<GET OLD MAC AND REPLACE>#

#<new_disk_create>#
/usr/bin/qemu-img create -f qcow2 -b $hdd_base_image /var/lib/libvirt/images_write/$new_vm_name.qcow2
#</new_disk_create>#

#<attach_new_disk_in_confiig>#
/usr/bin/echo hdd base image is $hdd_base_image
/usr/bin/sed -i "s%<source file='$hdd_base_image'/>%<source file='/var/lib/libvirt/images_write/$new_vm_name.qcow2'/>%g" $SRV_TMP_DIR/$new_vm_name.xml
#</attach_new_disk_in_confiig>#

starting_vm
    #</Create_vm config>#
}

starting_vm() {

/usr/bin/virsh define $SRV_TMP_DIR/$new_vm_name.xml
/usr/bin/virsh start $new_vm_name
while [ $a -ne 1 ]
do
if /usr/bin/virsh list --all |/usr/bin/grep "$new_vm_name" |/usr/bin/grep "running" > /dev/null 2>&1
then
a=1
/usr/bin/sed -i "/$new_vm_port/d" $SRV_TMP_DIR/recycle.list
/usr/bin/echo $new_vm_port >> $SRV_TMP_DIR/clear.list
/usr/bin/echo "#" "$date" "VM $new_vm_name IS STARTED #"
else
 /usr/bin/echo "#VM $new_vm_name is not ready#"
a=0
/usr/bin/sleep 2s
fi
done
/usr/bin/echo "#$date  EXIT FROM VM_CREATE.SH#"
exit
}

hdd_image_locate

Le processus de création d'une nouvelle machine virtuelle

Le script vm_create.sh lit à partir du fichier de configuration la valeur de la variable « base_host », qui détermine le modèle de la machine virtuelle à partir duquel un clone sera créé. Il effectue un téléchargement de la configuration XML de la VM depuis la base de données du hyperviseur, réalise plusieurs vérifications de l'image disque qcow de la VM et, en cas de réussite, crée le fichier de configuration XML pour la nouvelle VM ainsi qu'un disque « linked clone » de la nouvelle VM. Ensuite, la configuration XML de la nouvelle VM est chargée dans la base de données du hyperviseur et la VM est lancée. Le port « spice_console » est déplacé de recycle.list à clear.list. L'exécution de vm_create.sh se termine et l'exécution de vm_manager.sh se termine également.
Lors de la prochaine connexion, tout recommence depuis le début.

Pour les cas d'urgence, un script vm_clear.sh est inclus, qui parcourt toutes les VM du pool et les supprime en réinitialisant les valeurs des listes. L'appeler au moment du démarrage permet de commencer le travail (inachevé) du VDI avec une ardoise vierge.

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

Je voudrais conclure cette première partie de mon récit ici. Ce qui a été exposé devrait suffire aux administrateurs système pour essayer le VDI inachevé en action. Si la communauté trouve ce sujet intéressant, je parlerai dans la deuxième partie de la modification de livecd Fedora et de sa transformation en kiosque.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster