KVM (bajo)VDI con máquinas virtuales desechables usando bash

¿A quién está destinado este artículo?

Este artículo puede ser de interés para los administradores de sistemas que se enfrentan a la tarea de crear un servicio de estaciones de trabajo "únicas".

Prólogo

Al departamento de soporte de TI de una empresa joven en desarrollo dinámico con una pequeña red regional se le pidió que organizara "estaciones de autoservicio" para uso de sus clientes externos. Se suponía que estas estaciones se utilizarían para registrarse en portales externos de empresas, descargar datos de dispositivos externos y trabajar con portales gubernamentales.

Un aspecto importante fue el hecho de que la mayor parte del software está "adaptado" a MS Windows (por ejemplo, "Declaración") y, a pesar del movimiento hacia formatos abiertos, MS Office sigue siendo el estándar dominante para el intercambio de documentos electrónicos. Por tanto, no podíamos rechazar MS Windows a la hora de solucionar este problema.

El principal problema era la posibilidad de acumular diversos datos de las sesiones de los usuarios, lo que podría dar lugar a su filtración a terceros. Esta situación ya le ha fallado al MFC.. Pero a diferencia del MFC cuasi estatal (institución estatal autónoma), las organizaciones no estatales serán castigadas mucho más severamente por tales deficiencias. El siguiente problema más crítico era la necesidad de trabajar con medios de almacenamiento externos, que definitivamente contendrían una gran cantidad de malware malicioso. La probabilidad de que se importara malware desde Internet se consideró menos posible debido a las restricciones de acceso a Internet mediante una lista blanca de direcciones. Los empleados de otros departamentos se unieron al desarrollo de los requisitos, presentaron sus requisitos y deseos, los requisitos finales se vieron como esto:

Requisitos de seguridad de la información

  • Después del uso, se deben eliminar todos los datos del usuario (incluidos los archivos temporales y las claves de registro).
  • Todos los procesos iniciados por el usuario deben finalizar al finalizar el trabajo.
  • Acceso a Internet mediante una lista blanca de direcciones.
  • Restricciones a la capacidad de ejecutar código de terceros.
  • Si la sesión permanece inactiva durante más de 5 minutos, la sesión debería finalizar automáticamente y la estación debería limpiarse sola.

Requerimientos del cliente

  • El número de estaciones de clientes por sucursal no es superior a 4.
  • Tiempo mínimo de espera para que el sistema esté listo, desde el momento en que te “sentas” hasta el inicio de trabajar con el software cliente.
  • Posibilidad de conectar dispositivos periféricos (escáneres, unidades flash) directamente desde el lugar de instalación de la “estación de autoservicio”.
  • Deseos del cliente
  • Demostración de material publicitario (imágenes) cuando el complejo esté inactivo.

Los dolores de la creatividad

Habiendo jugado mucho con los livecds de Windows, llegamos a la conclusión unánime de que la solución resultante no satisface al menos 3 puntos críticos. O tardan mucho en cargarse, o no están del todo activos, o su personalización se asoció con dolores de cabeza. Quizás no buscamos bien y puedes recomendar un conjunto de herramientas, te lo agradeceré.

Luego empezamos a mirar hacia VDI, pero para esta tarea la mayoría de las soluciones son demasiado caras o requieren mucha atención. Pero quería una herramienta simple, con una cantidad mínima de magia, la mayoría de cuyos problemas pudieran resolverse con un simple reinicio del servicio. Afortunadamente, en las sucursales disponíamos de equipos de servidores de gama baja procedentes del servicio que estaba fuera de servicio, que pudimos utilizar para la base tecnológica.

¿Que pasó al final? Pero no puedo decirles qué sucedió al final, porque es un NDA, pero en el proceso de búsqueda desarrollamos un esquema interesante que funcionó bien en las pruebas de laboratorio, aunque no entró en producción.

Algunas advertencias: el autor no afirma que la solución propuesta resuelva completamente todos los problemas asignados y lo hace de forma voluntaria y con entusiasmo. El autor está de antemano de acuerdo con la afirmación de que Sein Englishe sprache es zehr schlecht. Dado que la solución ya no se está desarrollando, no puede contar con una corrección de errores o un cambio en la funcionalidad, todo está en sus manos. El autor supone que usted está al menos algo familiarizado con KVM y ha leído un artículo de revisión sobre el protocolo Spice y ha trabajado un poco con Centos u otra distribución GNU Linux.

En este artículo, me gustaría explorar la esencia de la solución resultante, específicamente la interacción cliente-servidor y la esencia de los procesos del ciclo de vida de las máquinas virtuales dentro de la solución. Si el artículo es de interés para el público, describiré los detalles de implementación de imágenes en vivo para crear clientes ligeros basados ​​en Fedora y analizaré los detalles de la configuración de máquinas virtuales. Servidores KVM para optimizar el rendimiento y la seguridad.

Si tomas papel de colores,
Pinturas, pinceles y pegamento,
Y un poco más de habilidad...
¡Puedes ganar cien rublos!

Diagrama y descripción del banco de pruebas.

KVM (bajo)VDI con máquinas virtuales desechables usando bash

Todo el equipo está ubicado dentro de la red de sucursales, solo el canal de Internet sale al exterior. Históricamente ya ha existido un servidor proxy, no es nada extraordinario. Pero es allí, entre otras cosas, donde se filtrará el tráfico de las máquinas virtuales (abreviadas VM más abajo en el texto). No hay nada que le impida colocar este servicio en un servidor KVM, lo único que debe observar es cómo cambia la carga en el subsistema de disco.

Client Station es en realidad una “estación de autoservicio”, la “front-end” de nuestro servicio. Son nettops Lenovo IdeaCentre. ¿Qué tiene de bueno esta unidad? Sí, casi todo el mundo está especialmente satisfecho con la gran cantidad de conectores USB y el lector de tarjetas en el panel frontal. En nuestro esquema, se inserta una tarjeta SD con protección de hardware contra escritura habilitada en el lector de tarjetas, en la que se graba una imagen en vivo modificada de Fedora 28. Por supuesto, un monitor, teclado y mouse están conectados a la nettop.

Switch es un conmutador de hardware de segundo nivel sin complicaciones, que se encuentra en la sala de servidores y tiene luces parpadeantes. No está conectado a ninguna otra red que no sea la red de “estaciones de autoservicio”.

KVM_Server es el núcleo del esquema; en las pruebas de banco, el Core 2 Quad Q9650 con 8 GB de RAM admitió con seguridad 3 máquinas virtuales con Windows 10. Subsistema de discos – adaptec 3405 2 discos Raid 1 + SSD. En las pruebas de campo del Xeon 1220, el LSI 9260 + SSD más serio admitió fácilmente entre 5 y 6 máquinas virtuales. Los servidores los adquiriríamos del servicio retirado, no habría grandes costes de capital. Este servidor tiene un sistema de virtualización KVM implementado con un grupo de máquinas virtuales pool_Vm.

Vm es una máquina virtual, el backend de nuestro servicio. Es donde se desarrolla el trabajo del usuario.

Enp5s0 es una interfaz de red frente a la red de "estaciones de autoservicio", dhcpd, ntpd, httpd viven en ella y xinetd escucha el puerto de "señal".

Lo0 es una interfaz pseudo loopback. Estándar.

Spice_console – Algo muy interesante, el hecho es que, a diferencia del RDP clásico, cuando expandes el paquete de protocolos KVM+Spice, aparece una entidad adicional: el puerto de consola de la máquina virtual. De hecho, al conectarnos a este puerto TCP obtenemos la consola Vm, sin tener que conectarnos a Vm a través de su interfaz de red. El servidor asume toda la interacción con Vm para la transmisión de señales. El análogo más cercano en función es IPKVM. Aquellos. La imagen del monitor VM se transmite a este puerto, también se transmiten datos sobre el movimiento del mouse y (lo más importante) la interacción a través del protocolo Spice le permite redirigir sin problemas dispositivos USB a la máquina virtual, como si este dispositivo estuviera conectado al propio Vm. Probado para unidades flash, escáneres y cámaras web.

Las tarjetas de red virtuales Vnet0, virbr0 y Vm forman una red de máquinas virtuales.

Cómo funciona

Desde la estación del cliente

La estación cliente arranca en modo gráfico desde una imagen en vivo modificada de Fedora 28, recibe una dirección IP a través de dhcp desde el espacio de direcciones de red 169.254.24.0/24. Durante el proceso de descarga, se crean reglas de firewall que permiten conexiones a los puertos "señal" y "spice" del servidor. Una vez completada la descarga, la estación espera la autorización del usuario “Cliente”. Después de la autorización del usuario, se inicia el administrador de escritorio "openbox" y se ejecuta el script de inicio automático en nombre del usuario autorizado. Entre otras cosas, el script de ejecución automática ejecuta el script remoto.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

Descripción de las variables del archivo client.conf
server_ip — Dirección del servidor KVM
vdi_signal_port: puerto KVM_Server en el que “se asienta” xinetd
vdi_spice_port: puerto de red KVM_Server desde el cual se redirigirá la solicitud de conexión desde el cliente del visor remoto al puerto Spice de la máquina virtual dedicada (detalles a continuación)
carpeta_animación: la carpeta de donde se toman las imágenes para la demostración de animación de mierda.
carpeta_de_fondo: la carpeta desde donde se toman las imágenes para mostrar presentaciones en modo de espera. Más detalles sobre la animación en la siguiente parte del artículo.

El script remoto.sh toma la configuración del archivo de configuración /etc/client.conf y usa nc para conectarse al puerto “vdi_signal_port” del servidor KVM y recibe un flujo de datos del servidor, entre los que espera las líneas “REGLA AÑADIDA , CONECTE AHORA". Cuando se recibe la cadena requerida, el proceso del visor remoto se inicia en modo quiosco, estableciendo una conexión con el puerto del servidor “vdi_spice_port”. La ejecución del script se suspende hasta que el visor remoto finalice la ejecución.

El visor remoto que se conecta al puerto “vdi_spice_port”, debido a una redirección en el lado del servidor, llega al puerto “spice_console” de la interfaz lo0, es decir. a la consola de la máquina virtual y el trabajo del usuario ocurre directamente. Mientras espera la conexión, al usuario se le muestra una animación de mierda, en forma de presentación de diapositivas de archivos jpeg, la ruta al directorio con imágenes está determinada por el valor de la variable carpeta_animación del archivo de configuración.

Si se pierde la conexión al puerto “spice_console” de la máquina virtual, lo que indica el apagado/reinicio de la máquina virtual (es decir, el final real de la sesión del usuario), todos los procesos que se ejecutan en nombre del usuario autorizado finalizan, lo que conduce para reiniciar lightdm y regresar a la pantalla de autorización.

Desde el lado del servidor KVM

En el puerto de “señal” de la tarjeta de red, enp5s0 está esperando que xinetd se conecte. Después de conectarse al puerto de “señal”, xinetd inicia el script vm_manager.sh sin pasarle ningún parámetro de entrada y redirige el resultado del script a la sesión de Client Station nc.

/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

Descripción de variables en el archivo de configuración vm_manager.conf
srv_scripts_dir — carpeta donde se encuentran los scripts vm_manager.sh, vm_connect.sh, vm_delete.sh, vm_create.sh, vm_clear.sh
srv_pool_size — Tamaño del grupo de máquinas virtuales
srv_start_port_pool: el puerto de inicio, después del cual comenzará la ubicación de los puertos Spice para las consolas de máquinas virtuales.
srv_tmp_dir - carpeta para almacenar archivos temporales
base_host — Vm base (imagen dorada) a partir de la cual se realizarán clones de Vm en el grupo
input_iface — interfaz de red del servidor, frente a las Estaciones Cliente
vdi_spice_port: puerto de red del servidor desde el cual la solicitud de conexión del cliente del visor remoto se redirigirá al puerto Spice de la máquina virtual dedicada.
count_conn_tryes: temporizador de espera, después del cual se considera que no se ha producido ninguna conexión con Vm (para más detalles, consulte vm_connect.sh)

El script vm_manager.sh lee el archivo de configuración del archivo vm_manager.conf y evalúa el estado de las máquinas virtuales en el grupo en función de varios parámetros, a saber: cuántas máquinas virtuales están implementadas y si hay máquinas virtuales limpias y libres. Para hacer esto, lee el archivo clear.list, que contiene los números de puerto “spice_console” de las máquinas virtuales “recién creadas” (ver más abajo el ciclo de creación de VM) y verifica si hay una conexión establecida con ellas. Cuando se detecta un puerto con una conexión de red establecida (lo que no debería ser así), se muestra una advertencia y el puerto se transfiere a Waste.list. Cuando se detecta el primer puerto del archivo clear.list con el que actualmente no hay conexión. , vm_manager.sh llama al script vm_connect.sh y le transmite el número de este puerto como parámetro.

/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

El script vm_connect.sh introduce reglas de firewall que crean una redirección del puerto del servidor “vdi_spice_port” de la interfaz enp5s0 al “puerto de consola Spice” de la VM ubicada en la interfaz del servidor lo0, pasada como parámetro de inicio. El puerto se transfiere a conn_wait.list y se considera que la VM está esperando una conexión. La cadena "REGLA AÑADIDA, CONECTAR AHORA" se envía a la sesión de la estación cliente en el puerto de "señal" del servidor, que es lo que espera el script remoto.sh que se ejecuta en él. Un ciclo de espera de conexión comienza con el número de intentos determinado por el valor de la variable "count_conn_tryes" del archivo de configuración. Cada segundo se enviará la cadena “REGLA AÑADIDA, CONECTAR AHORA” a la sesión nc y se comprobará la presencia de una conexión establecida al puerto “spice_console”.

Si durante el número de intentos establecido no se produce ninguna conexión, el puerto "spice_console" se transfiere de nuevo a clear.list. La ejecución de vm_connect.sh finaliza, se reanuda la ejecución de vm_manager.sh, lo que inicia el ciclo de limpieza.

Si se detecta una conexión de Client Station al puerto “spice_console” en la interfaz lo0, las reglas de firewall que crean una redirección entre el puerto “spice” del servidor y el puerto “spice_console” se eliminan y la conexión se mantiene utilizando el estado del firewall mecanismo de detección. Si la conexión se interrumpe, no será posible restablecer la conexión con el puerto “spice_console”. El puerto "spice_console" se mueve a Waste.list, la VM se considera "sucia" y no podrá regresar al grupo de máquinas virtuales "limpias" sin someterse a una limpieza. La ejecución de vm_connect.sh finaliza y se reanuda la ejecución de vm_manager.sh, lo que inicia el ciclo de limpieza.

El ciclo de limpieza comienza con la visualización del archivo Waste.list, al que se transfieren los números de puerto "spice_console" de las máquinas virtuales a las que se estableció la conexión. La presencia de una conexión activa se determina en cada puerto "spice_console" de la lista. Si no hay conexión, se supone que la máquina virtual ya no está en uso y el puerto se mueve a recycle.list y se inicia el proceso de eliminación de la máquina virtual (ver más abajo) a la que pertenecía este puerto. Si se detecta una conexión de red activa en un puerto, se considera que la máquina virtual está en uso y no se realiza ninguna acción al respecto. Si el puerto no está escuchando, se considera que la VM está apagada y ya no es necesaria. El puerto se transfiere a recycle.list y se inicia el proceso de eliminación de la máquina virtual. Para hacer esto, se llama al script vm_delete.sh, al que se le pasa el número "spice_console" como parámetro al puerto de la VM que desea eliminar.

/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

Eliminar una máquina virtual es una operación bastante trivial; el script vm_delete.sh determina el nombre de la máquina virtual propietaria del puerto pasado como parámetro de inicio. Se fuerza la detención de la máquina virtual, se elimina del hipervisor y se elimina el disco duro virtual de esta máquina virtual. El puerto "spice_console" se elimina de recycle.list. Finaliza la ejecución de vm_delete.sh, se reanuda la ejecución de vm_manager.sh

El script vm_manager.sh, al finalizar las operaciones para limpiar máquinas virtuales innecesarias de la lista Waste.list, comienza el ciclo de creación de máquinas virtuales en el grupo.

El proceso comienza identificando los puertos “spice_console” disponibles para su ubicación. Para hacer esto, según el parámetro del archivo de configuración “srv_start_port_pool”, que establece el puerto de inicio para el grupo de máquinas virtuales “spice_console”, y el parámetro “srv_pool_size”, que determina el número máximo de máquinas virtuales, se muestran todas las opciones de puerto posibles. buscado secuencialmente. Para cada puerto específico, se busca en clear.list, Waste.list, conn_wait.list, recycle.list. Si se encuentra un puerto en cualquiera de estos archivos, el puerto se considera ocupado y se omite. Si el puerto no se encuentra en los archivos especificados, se agrega al archivo recycle.list y comienza el proceso de creación de una nueva máquina virtual. Para hacer esto, se llama al script vm_create.sh, al que se le pasa como parámetro el número de puerto “spice_console” para el cual se debe crear 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

El proceso de creación de una nueva máquina virtual.

El script vm_create.sh lee el valor de la variable “base_host” del archivo de configuración, que determina la máquina virtual de muestra a partir de la cual se realizará la clonación. Descarga la configuración xml de la VM desde la base de datos del hipervisor, realiza una serie de comprobaciones qcow en la imagen del disco de la VM y, una vez completada con éxito, crea un archivo de configuración xml para la nueva VM y una imagen de disco de "clon vinculado" de la nueva. VM. Después de lo cual, la configuración xml de la nueva VM se carga en la base de datos del hipervisor y se inicia la VM. El puerto "spice_console" se mueve de recycle.list a clear.list. Finaliza la ejecución de vm_create.sh y finaliza la ejecución de vm_manager.sh.
La próxima vez que te conectes, todo empezará desde el principio.

Para emergencias, el kit incluye el script vm_clear.sh, que ejecuta a la fuerza todas las máquinas virtuales del grupo y las elimina, restableciendo los valores de la lista a cero. Llamarlo durante la fase de arranque le permite iniciar (bajo) VDI desde cero.

/home/admin/scripts_vdi_new/vm_clear.sh

#!/usr/bin/sh

#set VARIABLES#
SRV_SCRIPTS_DIR=$(/usr/bin/cat /etc/vm_manager.conf  
|/usr/bin/grep "srv_scripts_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR"
export SRV_SCRIPTS_DIR=$SRV_SCRIPTS_DIR

SRV_TMP_DIR=$(/usr/bin/cat /etc/vm_manager.conf  
|/usr/bin/grep "srv_tmp_dir" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_TMP_DIR=$SRV_TMP_DIR"
export SRV_TMP_DIR=$SRV_TMP_DIR

SRV_POOL_SIZE=$(/usr/bin/cat /etc/vm_manager.conf  
|/usr/bin/grep "srv_pool_size" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo "SRV_POOL_SIZE=$SRV_POOL_SIZE"

SRV_START_PORT_POOL=$(/usr/bin/cat /etc/vm_manager.conf  
|/usr/bin/grep "srv_start_port_pool" |/usr/bin/cut -d "=" -f2)
/usr/bin/echo SRV_START_PORT_POOL=$SRV_START_PORT_POOL
#Set VARIABLES#


/usr/bin/echo "= Cleanup ALL VM="

/usr/bin/mkdir $SRV_TMP_DIR

/usr/sbin/service iptables restart
/usr/bin/cat /dev/null > $SRV_TMP_DIR/clear.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/waste.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/recycle.list
/usr/bin/cat /dev/null > $SRV_TMP_DIR/conn_wait.list

port_to_delete=$(($SRV_START_PORT_POOL+$SRV_POOL_SIZE))

        while [ "$port_to_delete" -gt "$SRV_START_PORT_POOL" ]
          do
		$SRV_SCRIPTS_DIR/vm_delete.sh $port_to_delete
		port_to_delete=$(($port_to_delete-1))
        done

/usr/bin/echo "= EXIT FROM VM_CLEAR.SH="

Con esto me gustaría terminar la primera parte de mi historia. Lo anterior debería ser suficiente para que los administradores de sistemas prueben underVDI en acción. Si la comunidad encuentra interesante este tema, en la segunda parte hablaré sobre cómo modificar el livecd de Fedora y convertirlo en un kiosco.

Fuente: habr.com

Añadir un comentario