Poner en pausa máquinas y contenedores de Proxmox VE de forma automática

Publicado por P. Ruiz en

Logo de Proxmox VEEn mi trabajo, tenemos un servidor Proxmox VE donde los usuarios disponen de varias máquinas virtuales y contenedores, que usan como servidores o escritorios remotos en los que prueban diferentes aspectos de su actividad diaria. No todas las máquinas virtuales y contenedores necesitan ser ejecutados de forma simultánea, si no que cada cual recurre la que necesita, en el momento que la necesita.

Todo funciona de forma adecuada, pero vengo observando que, en ocasiones, hay usuarios que dejan sus máquinas funcionando de forma continuada, aunque no las estén necesitando en esos momentos. Supongo que el objetivo es tenerlas listas para su uso en cualquier momento, sin tener que esperar a que se inicien.

El problema es que, mientras las máquinas están funcionando, consumen recursos del servidor… Y tenerlas funcionando innecesariamente, perjudica el rendimiento del resto de los usuarios del sistema.

Para resolverlo, se me ha ocurrido una idea, que consiste en pausar todas las máquinas y contenedores del servidor, de forma automática, una vez terminada la jornada laboral. Así, al día siguiente, los usuarios que necesiten sus máquinas, las iniciarán de forma sencilla y rápida. Y las que no sean necesarias, seguirán pausadas.

Lo he conseguido programando una tarea que ejecuta un script, muy sencillo, que se encarga de llevar a cabo el trabajo. Hoy te cuento cómo lo he hecho, por si te sirve de ayuda.

Comenzaremos dividiendo el script en tres partes:

  • La primera, de una sola línea, guardará en un array el contenido de un archivo, que almacena la lista de máquinas y contenedores que no deben pausarse.

  • La segunda, pausará las máquinas virtuales que no estén en la lista anterior.

  • La tercera hará lo mismo con los contenedores.

Cargar el array de excepciones

Una de las cuestiones a tener en cuenta es que, dentro del servidor existen algunas máquinas virtuales y contenedores con funciones específicas, que no deben ser detenidas.

Para resolverlo, usaremos un archivo de texto donde cada línea contiene un número de máquina que no debe ser pausada. Lo llamaremos nopausar.txt y lo guardaremos en la ruta /scripts/datos.

En definitiva, lo que vemos en la imagen…

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-008

Por lo tanto, comenzaremos cargando el contenido de este archivo en un array. Más adelante, usaremos ese array para comprobar, en el momento de pausar una máquina, o un contenedor, que su identificador no aparece en él.

Lo haremos con la siguiente orden:

readarray nopausar < /scripts/datos/nopausar.txt

En todo el script usaremos rutas absolutas para no depender de la ruta activa en el momento de ejecutarlo. Por el mismo motivo, crearemos una variable PATH con el contenido que debe tener de forma predeterminada:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Recuerda modificar el script si no utilizas las mismas rutas de nuestro ejemplo.

Pausar las máquinas virtuales

El primer problema al que nos enfrentaremos será el de pausar todas las máquinas que se encuentren en funcionamiento. Y para resolverlo, iremos paso a paso:

1º Averiguar las máquinas que tenemos en el servidor y el estado en el que se encuentran.

Esto lo conseguiremos con la orden qm, seguida del argumento list. Es decir:

qm list

En mi caso, obtengo un resultado como el de la imagen siguiente.

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-001

Como ves en la imagen, la lista contiene el identificador de la máquina virtual (VMID), el nombre, la situación en la que se encuentra (Status), la memoria que tiene asignada, medida en MegaBytes (MEM), su capacidad en disco, medida en GigaBytes (BOOTDISK) y, finalmente, su identificador de proceso, en el caso de que se encuentre en ejecución es ese momento.

2º Seleccionar solo las máquinas que se encuentren en ejecución.

Lograrlo puede ser tan sencillo como enviar la salida anterior al comando grep, que seleccionará únicamente las líneas donde encuentre el texto que nosotros indiquemos. En este caso, running:

qm list | grep running

El resultado del comando será como en la imagen.

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-002

Como ves, ahora solo aparecen las máquinas que se están ejecutando.

3º Quitar los espacios sobrantes

Para que el script pueda identificar los diferentes valores, usaremos el espacio en blanco. Sin embargo, en cada línea encontramos numerosos espacios en blanco para dar formato a la salida.

Para simplificar la tarea cambiaremos cada pareja de espacios por uno solo. Lo conseguiremos enviando la salida anterior al comando sed y usándolo para buscar todos los lugares donde aparezcan dos espacios y sustituirlos por uno solo. Por lo tanto, la nueva versión de la orden nos quedará así:

qm list | grep running | sed 's:  : :g'

Y su salida será como en la imagen

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-003

Ahora estamos listos para procesar cada línea, usando el espacio en blanco como separador.

4º Quedarnos solo con el identificador de cada máquina virtual.

Ya estamos acabando. Solo nos queda usar el comando cut para dividir la línea en campos y quedarnos con el que nos interese.

Como el primer carácter de la línea es, precisamente un espacio en blanco, el comando cut interpretará que hay un campo vacío a su izquierda. Por lo tanto, el campo que nos interesa (el ID de la máquina), será el segundo.

En definitiva, la sintaxis queda así:

qm list | grep running | sed 's:  ::g' | cut -d ' ' -f2

Como habrás supuesto, el argumento -d nos permite indicar el carácter que usamos como separador, y con el argumento -f indicamos la posición del campo que nos interesa.

Comprobamos su funcionamiento

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-004

Efectivamente, hemos comprobado que la salida que obtenemos es correcta.

5º Volcar la salida a un archivo

Bueno, esta parte es sencilla, solo tenemos que reenviar la salida del comando anterior a un archivo con el nombre que nos interese:

qm list | grep running | sed 's:  ::g' | cut -d ' ' -f2 > runing1.txt

6º Recorrer el archivo, pausando cada máquina virtual

Lo primero será pensar cómo recorremos el archivo, obteniendo el identificador de cada máquina virtual.

En nuestro caso, usaremos un bucle while, que reciba como entrada el archivo del punto anterior, y que guarde cada línea en una variable, a la que llamaremos ID. Básicamente, lo que vemos a continuación:

while read -r ID
do
...
done < "/scripts/datos/runing1.txt"

Dentro del bucle, la tarea a realizar por cada valor de ID es muy sencilla:

  • Usaremos una variable que actuará como testigo, a la que, inicialmente, le daremos el valor «pausar». A esta variable la llamaremos accion.

  • Después, buscaremos el ID que acabamos de leer, en el array que cargamos al principio. En caso de encontrarlo, cambiaremos el valor de la variable accion a «no pausar».

  • Por último, si la variable sigue siendo «pausar», es que no se ha encontrado en el array. Es decir, debemos pausar la máquina.

    Para lograrlo, usaremos la orden qm suspend, con el ID que estamos procesando.

En definitiva, el bucle quedará finalmente así:

while read -r ID
do
  accion="pausar"

  case "${nopausar[@]}" in  *"$ID"*) accion="no pausar" ;; esac
 
  if [[ $accion == "pausar" ]]
  then
    qm suspend $ID
  fi
done < "/scripts/datos/runing1.txt"

Con esto, habremos terminado la segunda parte del script.

Pausar los contenedores

Como dijimos al principio, lo siguiente será poner en pausa los contenedores.

Básicamente, la tarea es similar a la anterior, con alguna ligera variación.

1º comenzaremos seleccionando los identificadores de los contenedores que se están ejecutando.

En este caso, el comando a utilizar es pct list, cuya principal diferencia con qm list es que no incluye espacios innecesarios en su salida.

En la práctica, esto significa que no necesitaremos el comando sed y que, al dividir la línea en campos, usando el espacio como separador, el ID de los contenedores será el primer campo.

Por lo tanto, la sintaxis será así:

pct list | grep running | cut -d ' ' -f1

Y comprobamos que la salida sea satisfactoria

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-005

2º Volcar la salida a un archivo

Como antes, esta parte es sencilla, solo tenemos que reenviar la salida del comando anterior a un archivo con el nombre que nos interese:

pct list | grep running | cut -d ' ' -f1 > "/scripts/datos/runing2.txt"

3º Recorrer el archivo, pausando cada máquina virtual

De nuevo, la solución es similar a la aplicada en el paso anterior. Solo habrá que cambiar la orden qm suspend por pct suspend, seguida del ID que estamos procesando.

En definitiva, el bucle quedará finalmente así:

pct list | grep running | cut -d ' ' -f1 > "/scripts/datos/runing2.txt"
while read -r ID
do
  accion="pausar"

  case "${nopausar[@]}" in  *"$ID"*) accion="no pausar" ;; esac

  if [[ $accion == "pausar" ]]
  then
    pct suspend $ID
  fi
done < "/scripts/datos/runing2.txt"

4º Hacer limpieza

Como somos gente limpia y ordenada, acabaremos eliminando los elementos que hemos creado durante el script:

rm /scripts/datos/runing1.txt
rm /scripts/datos/runing2.txt
unset accion ID

Unirlo todo y programar la tarea

Ahora que conocemos las diferentes partes que formarán el script, vamos a mostrar cómo quedaría la versión completa:

#!/bin/bash
# Script para poner en pausa todas las máquinas y contenedores, salvo los contenidos en el archivo nopausar.txt
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

#Cargamos el contenido del archivo 'nopausar.txt' en el array 'nopausar'
readarray nopausar < /scripts/datos/nopausar.txt

#Creamos el archivo 'runing1.txt' con el ID de las máquinas que están corriendo
qm list | grep running | sed 's:  ::g' | cut -d ' ' -f2 > "/scripts/datos/runing1.txt"

#Leemos línea a línea el archivo 'runing1.txt' y guardamos su valor en la variable ID
while read -r ID
do
  accion="pausar"
  #Si encontramos el ID en el array 'nopausar' cambiamos el valor de la variable 'accion'
  case "${nopausar[@]}" in  *"$ID"*) accion="no pausar" ;; esac
 
  #Si la 'accion' sigue siendo 'pausar', suspendemos la máquina virtual
  if [[ $accion == "pausar" ]]
  then
    qm suspend $ID
  fi
done < "/scripts/datos/runing1.txt"

#Ahora repetimos el proceso con los contenedores

pct list | grep running | cut -d ' ' -f1 > "/scripts/datos/runing2.txt"

while read -r ID
do
  accion="pausar"
  case "${nopausar[@]}" in  *"$ID"*) accion="no pausar" ;; esac
 
  if [[ $accion == "pausar" ]]
  then
    pct suspend $ID
  fi
done < "/scripts/datos/runing2.txt"

#Por último, hacemos limpieza
rm /scripts/datos/runing1.txt
rm /scripts/datos/runing2.txt
unset accion ID

Por último, podemos crear una tarea programada que ejecute el script de forma automática a la hora que nos interese. Como Proxmox VE está basado en Debian, debemos utilizar el comando habitual para programar tareas en GNU/Linux:

crontab -e

Escribimos el comando y pulsamos la tecla Intro.

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-006

Una vez en el entorno del editor, nos desplazamos hasta la última línea y escribimos la tarea programada con una sintaxis como esta:

30 20 * * * /scripts/pausar.sh

En este caso, hemos supuesto que el script se llama pausar.sh y que se encuentra almacenado dentro de la carpeta /scripts. Además, queremos que se ejecute todos los días a las 20:30 horas. En tu caso, si has llamado al script de un modo diferente, lo has almacenado en una ruta distinta, o necesitas que se ejecute a otra hora, deberás realizar los ajustes adecuados.

Si necesitas ayuda con la programación de tareas en GNU/Linux, puedes echar un vistazo a nuestro artículo Programar una tarea repetitiva en Ubuntu Server 20.04 LTS.

Una vez completada la tarea, pulsamos Control + X para salir del editor, pero nos aseguramos de guardar los cambios

Poner-en-pausa-maquinas-y-contenedores-de-Proxmox-VE-de-forma-automatica-007

Con esto habremos terminado la tarea, solo nos queda esperar a que llegue la hora indicada y comprobar que la tarea se lleve a cabo de un modo satisfactorio.

Y hasta aquí el contenido del artículo. Espero que te resulte útil.