Introducción #
Usando Proxmox es muy útil la función de habilitar el puerto serial de las máquinas virtuales para poder comunicarse directamente con la consola sin usar SSH o noVNC.
Esto nos permite algunas ventajas interesantes.
La primera es tener una opción alternativa a noVNC, ya que esta saca la imagen en video de la pantalla. Este comportamiento a veces puede ser molesto, sobre todo cuando se trabajan con tamaños de ventana pequeños ya que se puede mostrar muy distorsionada la pantalla. De otro modo, noVNC no nos permite copiar y pegar texto en la terminal, cosa que para mi resulta muy útil.
Una solución a este problema podría ser habilitar el servicio de SSH, pero esto tiene implicaciones como tener que abrir puertos, configurar el firewall, reglas de autenticación, fail2ban, etc.
Proxmox ofrece, de forma nativa, un terminal serial (basado en xterm.js) accesible desde el propio nodo en lugar de noVNC. En contenedores LXC esta funcionalidad ya viene preconfigurada, pero en máquinas virtuales (KVM/QEMU) es necesario habilitarla manualmente.
Desde la wiki oficial de Proxmox, nos proponen este método de instalación, un método basado en Upstart, que está en desuso desde Ubuntu 15.04 y solo se mantiene por retrocompatibilidad. A continuación se muestra cómo hacerlo usando systemd, de forma más sencilla y adaptada a distribuciones modernas.
Configuración en el nodo de Proxmox #
Primero en el nodo de Proxmox, con la máquina virtual apagada, ejecutamos en la terminal:
qm set <VM-ID> -serial0 socket
En dónde substituiremos <VM-ID>
por el número de ID de la máquina virtual a la que queramos añadir la funcionalidad. Una vez hayamos ejecutado esta orden en el nodo de proxmox, ya podremos iniciar la máquina virtual.
Configuración en la máquina virtual (VM) #
Estas instrucciones estan pensadas para un equipo que utilize systemd como init system. Estás instrucciones no funcionarán en máquinas que utilicen otros sistemas de init.
Ya desde la terminal de la máquina virtual, debemos configurar el servicio getty en systemd
systemctl enable serial-getty@ttyS0.service
systemctl start serial-getty@ttyS0.service
Finalmente, tendremos que editar el archivo /etc/default/grub
, el cual es un fichero crucial del sistema, por lo que será recomendable realizar una copia de seguridad del mismo.
En este fichero buscaremos la línea:
GRUB_CMDLINE_LINUX=""
Y la cambiaremos por lo siguiente:
GRUB_CMDLINE_LINUX="quiet console=tty0 console=ttyS0,115200"
Finalmente, regeneraremos grub con sudo update-grub
en entornos con base Debian o sudo grub2-mkconfig
en entornos RHEL, reiniciaremos la máquina virtual y ya debería de funcionar.
Script de automatización #
Para facilitar la instalación en entornos con Debian/Ubuntu con systemd o Upstart como init system he realizado el siguiente script en python para ejecutar en la VM.
Los pasos a ejecutar en el nodo, se deberán seguir igualmente de forma manual.
Este script detecta si se usa Upstart o Systemd, crea un backup del fichero /etc/default/grub
en backup/grub
, edita el fichero grub, regenera la configuración de grub y configura automáticamente Upstart o Systemd según se haya detectado.
##############################################################################
# Licensed under the GNU GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)
# Filename: configure_serial_console.py
# Version: 1.0
# Author: Jordi Fàbregas
# Contributors:
##############################################################################
'''
**WARNING:**
Use at your own risk. Always back up and verify system restore before running.
Configure serial console access on Linux virtual machines running on Proxmox Virtual Environment (Proxmox VE).
Compatible only with Debian-based Linux systems that use Systemd or Upstart as the init systems.
Only tested with Debian and Ubuntu.
This script detects the init system in use (Systemd or Upstart),
creates a backup of the GRUB configuration file, updates the serial console parameters in grub,
and configures the appropriate service to enable a serial getty on ttyS0.
Proxmox Node Configuration:
From the Proxmox node shell, with the virtual machine powered off, execute:
qm set <VMID> -serial0 socket
Then, start the virtual machine.
Usage (inside the virtual machine):
Run this script as root or with sudo:
sudo python3 configure_serial_console.py
'''
import subprocess
import sys
import os
GRUB_CONF_FILE = '/etc/default/grub'
UPSTART_PATH = '/etc/init/'
UPSTART_TTY_FILE = 'ttyS0.conf'
BACKUP_PATH = 'backup/'
BACKUP_SUFIX = '.backup'
GRUB_CMDLINE_LINUX = 'quiet console=tty0 console=ttyS0,115200'
def check_root():
'''
Check if the script is executed with superuser (root) privileges.
Input: None
Output: Boolean
'''
if os.geteuid() == 0:
return True
else:
return False
def check_systemd():
'''
Check if init system is systemd
Input: None
Output: Boolean
'''
if os.path.exists('/run/systemd/system'):
return True
else:
return False
def check_upstart():
'''
Check if init systems is upstart
Input: None
Output: Boolean
'''
if os.path.exists('/var/log/upstart'):
return True
else:
return False
def backup_grub():
'''
Create a backup of the GRUB configuration file.
Saves a copy of grub file to BACKUP_PATH
Input: None
Output: Boolean (True if successful, False on error)
'''
os.makedirs(BACKUP_PATH, exist_ok=True)
try:
with open(GRUB_CONF_FILE, 'r') as grub_file:
grub_content = grub_file.read()
with open(BACKUP_PATH + 'grub' + BACKUP_SUFIX, 'w') as grub_backup:
grub_backup.write(grub_content)
print(f"Backup created successfully at {BACKUP_PATH}grub{BACKUP_SUFIX}", file=sys.stdout)
return True
except Exception as e:
print(f"Error creating backup: {e}", file=sys.stderr)
return False
def configure_grub():
'''
Configure GRUB to set serial console parameters.
Updates GRUB_CMDLINE_LINUX with console settings for tty0 and ttyS0.
Then runs 'update-grub' to apply changes.
Input: None
Output: Boolean (True if successful, False on error)
'''
try:
with open (GRUB_CONF_FILE, 'r') as f:
lines = f.readlines()
new_file = ''
for line in lines:
if line.startswith('GRUB_CMDLINE_LINUX='):
new_file += f'GRUB_CMDLINE_LINUX="{GRUB_CMDLINE_LINUX}"\n'
else:
new_file += line
with open (GRUB_CONF_FILE, 'w') as f:
f.write(new_file)
subprocess.run(['update-grub'], check=True)
print("GRUB configuration updated successfully", file=sys.stdout)
return True
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e}", file=sys.stderr)
return False
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
return False
def configure_tty_systemd():
'''
Enable and start the serial console service for ttyS0 using systemd.
Runs 'systemctl enable' and 'systemctl start' for serial-getty@ttyS0.service.
Input: None
Output: Boolean (True if successful, False on error)
'''
try:
# 2. Habilitar servicio serial
subprocess.run(['systemctl', 'enable', 'serial-getty@ttyS0.service'], check=True)
subprocess.run(['systemctl', 'start', 'serial-getty@ttyS0.service'], check=True)
print('Serial console service configured successfully (Systemd)', file=sys.stdout)
return True
except subprocess.CalledProcessError as e:
print(f'Error executing command: {e}', file=sys.stderr)
return False
except Exception as e:
print(f'Unexpected error: {e}', file=sys.stderr)
return False
def configure_tty_upstart():
'''
Create an Upstart configuration file for ttyS0 serial console.
Writes a job configuration under /etc/init/ to manage getty on ttyS0.
Input: None
Output: Boolean (True after file creation)
'''
try:
os.makedirs(UPSTART_PATH, exist_ok=True)
with open (UPSTART_PATH + UPSTART_TTY_FILE, 'w') as f:
f.write(
'# ttyS0 - getty \n'\
'# \n '\
'# This service maintains a getty on ttyS0 from the point the system is \n' \
'# started until it is shut down again. \n'
'start on stopped rc RUNLEVEL=[12345]\n'
'stop on runlevel [!12345]\n'
'respawn\n'
'exec /sbin/getty -L 115200 ttyS0 vt102\n'
)
print("Serial console configuration file created for Upstart", file=sys.stdout)
return True
except Exception as e:
print(f"Error creating Upstart configuration: {e}", file=sys.stderr)
return False
if __name__ == "__main__":
if check_root():
if check_systemd():
backup_grub()
configure_grub()
configure_tty_systemd()
elif check_upstart():
backup_grub()
configure_grub()
configure_tty_upstart()
else:
print('Error: Unrecognized init system. This script is only compatible with Systemd and Upstart.', file=sys.stderr)
else:
print('Error: This script needs to be executed with root privileges.', file=sys.stderr)