El curso lleva cuatro módulos echando en falta lo mismo. En el Módulo 4, el release 3.3.0 no arrancó y desplegar.sh hizo un rollback automático —correcto— pero nadie llegó a diagnosticar por qué no arrancaba, porque no había dónde reproducirlo. En 07-01 practicaste la recuperación de arranque rompiendo el fstab del servidor real, con snapshot previo y cruzando los dedos. En 07-03, el último ejercicio pedía vaciar la caché de página, quitar el ionice y parar la aplicación para medir el planificador de E/S: tres cosas que no se hacen en producción. Y quedó pendiente comprobar si la CPU expone AES-NI al huésped, que es un cambio de configuración de la máquina virtual.
Todo eso apunta a la misma carencia: falta un entorno de pruebas. Esta lección lo construye, y por el camino te enseña la pila de virtualización desde dentro — que es lo que hará que la lección siguiente, contenedores, se entienda por contraste y no por analogía equivocada.
Contenido
- Qué es virtualizar: los tres modelos
- Hipervisores de tipo 1 y de tipo 2
- Virtualización frente a contenedores
- KVM, QEMU y libvirt: quién hace qué
- Instalación y comprobación del soporte
- virsh: gestionar máquinas desde la línea de comandos
- Crear una máquina con virt-install
- Aprovisionamiento desatendido con cloud-init
- Almacenamiento: formatos, pools y snapshots
- Red: NAT, aislada y puente
- Rendimiento: virtio y configuración de CPU
- Clonar y crear srv-tramontana-pruebas
Qué es virtualizar: los tres modelos
Virtualizar es ejecutar un sistema operativo completo dentro de otro, haciéndole creer que tiene su propio hardware. El problema técnico de fondo: ciertas instrucciones de la CPU son privilegiadas y solo puede ejecutarlas el kernel. Si el sistema huésped cree ser el kernel y ejecuta una de ellas, hay que interceptarla y hacer algo con ella. Las tres formas de resolverlo definen los tres modelos:
| Modelo | Cómo maneja las instrucciones privilegiadas | Rendimiento | Requiere modificar el huésped |
|---|---|---|---|
| Emulación | Traduce cada instrucción por software | 10-100 veces más lento | No |
| Paravirtualización | El huésped sabe que está virtualizado y pide los servicios al hipervisor por una API | 90-95 % del nativo | Sí |
| Asistida por hardware | La CPU tiene un modo específico (VT-x/AMD-V) que las intercepta sola | 95-99 % del nativo | No |
La emulación sigue teniendo su uso: es lo que permite ejecutar ARM sobre x86 (qemu-system-aarch64) o un sistema de 1985. Para virtualizar Linux sobre Linux en la misma arquitectura, es absurdamente lenta.
La paravirtualización fue la técnica dominante antes de 2006 (Xen la popularizó) y exigía un kernel modificado. Hoy sobrevive en algo mucho más importante de lo que parece: los drivers paravirtualizados, virtio, que verás más adelante y que son la diferencia entre una VM lenta y una rápida.
La virtualización asistida por hardware es lo que usa srv-tramontana y lo que se usa en cualquier sitio hoy. Intel la llama VT-x y AMD, AMD-V; ambas añaden un nivel de privilegio por debajo del kernel donde vive el hipervisor.
Hipervisores de tipo 1 y de tipo 2
| Tipo 1 (nativo) | Tipo 2 (alojado) | |
|---|---|---|
| Dónde corre | Directamente sobre el hardware | Como aplicación sobre un SO |
| Ejemplos | VMware ESXi, Xen, Hyper-V | VirtualBox, VMware Workstation |
| Rendimiento | Mayor: sin SO intermedio | Menor, aunque poco con VT-x |
| Uso típico | Centros de datos, nube | Escritorio, desarrollo, laboratorio |
Y KVM desafía esa clasificación, que es lo interesante: es un módulo del kernel de Linux, así que técnicamente hay un sistema operativo debajo (tipo 2), pero ese sistema operativo se convierte en el hipervisor al cargarlo, con acceso directo a las extensiones de virtualización (tipo 1). La respuesta honesta es que es un híbrido, y en la práctica rinde como un tipo 1.
Tu portátil está ejecutando VirtualBox, que es tipo 2. srv-tramontana es una VM dentro de él. Y en esta lección vas a crear una VM dentro de srv-tramontana, lo que se llama virtualización anidada y requiere que VirtualBox exponga las extensiones de CPU al huésped. Volveremos a ello en el apartado de instalación.
Virtualización frente a contenedores
Esta comparación es el puente hacia la lección siguiente, y conviene verla antes de tocar contenedores para no adquirir la analogía equivocada:
graph TB
subgraph VM["Maquina virtual"]
H1["Hardware fisico"] --> K1["Kernel anfitrion + KVM"]
K1 --> Q1["QEMU"] & Q2["QEMU"]
Q1 --> KG1["Kernel huesped 1"] --> A1["Bibliotecas + app"]
Q2 --> KG2["Kernel huesped 2"] --> A2["Bibliotecas + app"]
end
subgraph CT["Contenedores"]
H2["Hardware fisico"] --> K2["Kernel anfitrion UNICO"]
K2 --> N1["namespace + cgroup"] --> B1["Bibliotecas + app"]
K2 --> N2["namespace + cgroup"] --> B2["Bibliotecas + app"]
end
La diferencia estructural está en una sola fila del diagrama: cada VM tiene su propio kernel; los contenedores comparten el del anfitrión. De ahí se derivan todas las demás diferencias:
| Máquina virtual | Contenedor | |
|---|---|---|
| Kernel | Propio | Compartido con el anfitrión |
| Arranque | 10-60 segundos | Milisegundos |
| Peso en disco | GB (sistema completo) | MB (solo la aplicación y sus bibliotecas) |
| Sobrecarga de memoria | 200 MB - 1 GB por VM | Prácticamente nula |
| Aislamiento | Fuerte: frontera de hardware virtual | Más débil: frontera de kernel |
| Puede ejecutar otro SO | Sí (Windows, BSD, otro kernel Linux) | No: solo Linux, y el del anfitrión |
| Densidad típica | Decenas por anfitrión | Cientos o miles |
Y las dos consecuencias que hay que retener:
- Para aislamiento de seguridad, la VM es superior. Un escape de contenedor es un fallo del kernel compartido y da acceso al anfitrión. Un escape de VM requiere vulnerar el hipervisor, que es una superficie mucho menor. Por eso los proveedores de nube ejecutan cargas de clientes distintos en VM separadas, no en contenedores del mismo kernel.
- Para densidad y velocidad, el contenedor gana por órdenes de magnitud. Arrancar cien contenedores es cuestión de segundos; cien VM son cien kernels.
No son alternativas: se combinan. Lo habitual hoy es ejecutar contenedores dentro de máquinas virtuales, aprovechando el aislamiento de la VM entre inquilinos y la densidad del contenedor dentro de cada uno.
KVM, QEMU y libvirt: quién hace qué
Tres piezas que se confunden constantemente. Cada una resuelve un problema distinto:
| Pieza | Qué es | Qué resuelve |
|---|---|---|
| KVM | Un módulo del kernel (/dev/kvm) |
Dar acceso a VT-x/AMD-V: ejecutar la CPU virtual a velocidad nativa |
| QEMU | Un programa de espacio de usuario | Emular todo lo demás: disco, red, teclado, gráficos, BIOS |
| libvirt | Un demonio y una biblioteca | Gestión: definir, arrancar, parar, red, almacenamiento, API estable |
La división de trabajo entre las dos primeras es la clave: KVM se encarga de la CPU y la memoria, que es donde el rendimiento importa; QEMU emula los dispositivos, que se acceden mucho menos. Sin KVM, QEMU emularía también la CPU y tendrías el modelo lento del primer apartado. La combinación se suele escribir «QEMU/KVM».
Y libvirt aporta algo que no es obvio hasta que gestionas más de dos máquinas: una capa de abstracción estable. La línea de comandos de QEMU es larguísima y cambia entre versiones; libvirt define la máquina en XML, y virsh habla siempre igual. Además gestiona redes virtuales, pools de almacenamiento y permisos, y es la API que usan virt-manager, Vagrant, OpenStack y Terraform.
Instalación y comprobación del soporte
Primero, comprobar que hay soporte de hardware. Y aquí aparece la complicación de la virtualización anidada:
$ sudo apt install cpu-checker
$ kvm-ok
INFO: /dev/kvm does not exist
HINT: sudo modprobe kvm_intel
INFO: Your CPU supports KVM extensions
INFO: KVM (vmx) is disabled by your BIOSsrv-tramontana es una VM de VirtualBox, y por defecto VirtualBox no expone las extensiones de virtualización al huésped. Hay que activarlo desde el anfitrión, con la máquina apagada:
# En el portatil anfitrion, con srv-tramontana APAGADA
$ VBoxManage modifyvm srv-tramontana --nested-hw-virt on
$ VBoxManage showvm srv-tramontana --machinereadable | grep -i nested
nestedHWVirt="on"En VMware la opción equivalente es Virtualize Intel VT-x/EPT, y en un servidor físico basta con activar VT-x/AMD-V en la UEFI. Tras encender de nuevo:
$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used
$ grep -o -m1 -E 'vmx|svm' /proc/cpuinfo
vmx
$ lsmod | grep kvm
kvm_intel 376832 0
kvm 1146880 1 kvm_intel
irqbypass 12288 1 kvmSi kvm-ok sigue fallando, la virtualización funcionará por emulación pura: utilizable para probar la mecánica de esta lección, inutilizable para trabajar de verdad.
$ sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients \
virtinst bridge-utils libguestfs-tools cloud-image-utils
$ systemctl is-active libvirtd
active
# El grupo libvirt permite gestionar VM sin sudo.
# AVISO de seguridad: quien puede definir una VM puede montar cualquier disco
# del anfitrion dentro de ella y leerlo. Pertenecer a este grupo es
# equivalente a root en la practica. Es la misma advertencia que hara
# falta con el grupo docker en 07-05.
$ sudo usermod -aG libvirt operador
$ newgrp libvirt
$ virsh --connect qemu:///system version
Compiled against library: libvirt 10.0.0
Using library: libvirt 10.0.0
Using API: QEMU 10.0.0
Running hypervisor: QEMU 8.2.2Fíjate en qemu:///system frente a qemu:///session: el primero son máquinas del sistema, gestionadas por el demonio como root, con acceso a las redes y pools compartidos. El segundo son máquinas del usuario, sin privilegios, con red limitada. Para un servidor, siempre system, y conviene fijarlo para no tener que escribirlo:
Y una comprobación importante que cierra el círculo con 07-01: al instalar libvirt aparece virbr0, la interfaz de la red NAT por defecto, y es exactamente la que costaba 35 segundos de arranque porque systemd-networkd-wait-online esperaba a que tuviera enlace:
$ ip -brief addr show virbr0
virbr0 DOWN 192.168.122.1/24
$ systemd-analyze | tail -1
Startup finished in 3.402s (kernel) + 9.118s (userspace) = 12.520sSigue en 12 segundos porque en el ejercicio de 07-01 limitaste systemd-networkd-wait-online a enp0s3 con un drop-in. Sin ese arreglo, instalar libvirt habría vuelto a añadir 35 segundos al arranque, y probablemente nadie habría relacionado las dos cosas.
virsh: gestionar máquinas desde la línea de comandos
virsh es la herramienta principal. Sus operaciones esenciales:
$ virsh list --all
Id Name State
--------------------
$ virsh net-list --all
Name State Autostart Persistent
------------------------------------------------
default active yes yes
$ virsh pool-list --all
Name State Autostart
-------------------------------
default active yes| Comando | Qué hace |
|---|---|
virsh list --all |
Todas las máquinas definidas, con su estado |
virsh dominfo <vm> |
CPU, memoria, autoarranque, seguridad |
virsh start <vm> |
Arranca |
virsh shutdown <vm> |
Apagado ordenado: pide al huésped que se apague (ACPI) |
virsh destroy <vm> |
Corte de corriente: inmediato y sin avisar al huésped |
virsh reboot <vm> |
Reinicio ordenado |
virsh autostart <vm> |
Arranca al iniciar el anfitrión |
virsh console <vm> |
Consola serie: la vía cuando no hay red |
virsh edit <vm> |
Edita el XML con validación |
virsh dumpxml <vm> |
Vuelca la definición |
virsh undefine <vm> |
Borra la definición (no los discos, salvo --remove-all-storage) |
virsh domifaddr <vm> |
Direcciones IP de la máquina |
La distinción entre shutdown y destroy merece énfasis porque el nombre engaña: destroy no borra nada, es el equivalente a tirar del cable. No destruye la definición ni los discos, pero sí puede dejar el sistema de ficheros del huésped inconsistente. Es la misma lógica del shutdown frente a tirar del cable de 01-05, y el orden correcto es siempre intentar shutdown primero y esperar:
$ virsh shutdown srv-tramontana-pruebas
Domain 'srv-tramontana-pruebas' is being shutdown
$ for i in {1..30}; do
[[ "$(virsh domstate srv-tramontana-pruebas)" == "shut off" ]] && break
sleep 2
done
$ virsh domstate srv-tramontana-pruebas
shut offEs exactamente el patrón SIGTERM → esperar → verificar → SIGKILL que aprendiste en 03-06, aplicado a máquinas enteras.
Y virsh console es la herramienta que hace practicable todo lo de 07-01: da acceso a la consola serie del huésped, así que puedes ver el menú de GRUB, entrar en modo emergencia y recuperar un fstab roto sin necesidad de interfaz gráfica. Se sale con Ctrl+].
Crear una máquina con virt-install
virt-install es la forma no interactiva de definir y arrancar una máquina. El ejemplo completo, comentado línea a línea:
$ sudo virt-install \
--name srv-tramontana-pruebas \
--memory 2048 \
--vcpus 2 \
--cpu host-passthrough \
--disk path=/var/lib/libvirt/images/pruebas.qcow2,size=20,format=qcow2,bus=virtio \
--network network=default,model=virtio \
--os-variant ubuntu24.04 \
--graphics none \
--console pty,target_type=serial \
--location 'http://archive.ubuntu.com/ubuntu/dists/noble/main/installer-amd64/' \
--extra-args 'console=ttyS0,115200n8'| Opción | Por qué ese valor |
|---|---|
--cpu host-passthrough |
Expone la CPU real al huésped, incluido AES-NI — el pendiente de 07-03 |
bus=virtio |
Driver paravirtualizado de disco: imprescindible para el rendimiento |
model=virtio |
Lo mismo para la red |
--os-variant |
Permite a libvirt elegir los valores óptimos para ese sistema |
--graphics none |
Un servidor no necesita pantalla virtual |
--console pty,target_type=serial |
Consola serie, que es lo que usa virsh console |
console=ttyS0,115200n8 |
Le dice al kernel del huésped que hable por el puerto serie |
Los dos últimos van juntos y son la pareja que hace que virsh console funcione: sin console=ttyS0 en la línea de comandos del kernel, el huésped escribiría en una pantalla virtual que nadie mira, y verías una consola en blanco. Es un fallo desconcertante y muy común.
Una instalación interactiva desde el instalador de Ubuntu tarda veinte minutos y hay que responder preguntas. Para un entorno de pruebas que quieres poder recrear, eso no sirve. La alternativa es la siguiente sección.
Aprovisionamiento desatendido con cloud-init
Las distribuciones publican imágenes de nube: discos ya instalados, con cloud-init dentro, que se configuran solos en el primer arranque a partir de unos datos que les entregas.
$ cd /var/lib/libvirt/images
$ sudo wget -q https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
$ sudo qemu-img info noble-server-cloudimg-amd64.img
image: noble-server-cloudimg-amd64.img
file format: qcow2
virtual size: 3.5 GiB (3758096384 bytes)
disk size: 588 MiBFíjate en la diferencia entre virtual size y disk size: 3,5 GiB declarados, 588 MiB ocupados. Es el aprovisionamiento fino de qcow2, que se explica en el apartado siguiente.
Los datos de configuración se entregan en dos ficheros YAML:
$ mkdir -p ~/cloud-init && cd ~/cloud-init
$ cat > user-data <<'EOF'
#cloud-config
hostname: srv-tramontana-pruebas
fqdn: srv-tramontana-pruebas.tramontana.example
manage_etc_hosts: true
users:
- name: operador
groups: [sudo, adm]
shell: /bin/bash
sudo: 'ALL=(ALL) NOPASSWD:ALL'
lock_passwd: false
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@portatil-alumno
# Coherente con lo aprendido en 06-02: nada de contrasenas por SSH
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: true
packages:
- postgresql-16
- restic
- ufw
- auditd
- libpam-pwquality
- sysstat
- linux-tools-generic
write_files:
- path: /etc/tramontana/app.conf
owner: root:root
permissions: '0640'
content: |
db_host=127.0.0.1
db_port=5432
db_name=tramontana_reservas
max_conexiones=80
timeout_consulta=30
log_nivel=debug
escucha=127.0.0.1
- path: /etc/sysctl.d/70-rendimiento.conf
owner: root:root
permissions: '0644'
content: |
# Copiado de produccion para que las pruebas sean representativas
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
runcmd:
- [ install, -d, -m, '0750', -o, root, -g, root, /opt/tramontana ]
- [ install, -d, -m, '2770', /srv/tramontana/backups ]
- [ sysctl, --system ]
- [ systemctl, enable, --now, sysstat ]
final_message: "srv-tramontana-pruebas listo tras $UPTIME segundos"
EOF
$ cat > network-config <<'EOF'
version: 2
ethernets:
enp1s0:
dhcp4: true
EOFLos dos ficheros se empaquetan en una imagen ISO minúscula que la máquina lee como si fuera un CD:
$ cloud-localds -N network-config semilla.iso user-data
$ ls -lh semilla.iso
-rw-rw-r-- 1 operador operador 366K ago 18 18:04 semilla.iso
$ sudo mv semilla.iso /var/lib/libvirt/images/Y ahora se crea la máquina, con un detalle importante: la imagen descargada no se usa directamente, se crea un disco nuevo que la usa como respaldo:
$ cd /var/lib/libvirt/images
# El disco de la VM se respalda en la imagen base, que queda intacta
$ sudo qemu-img create -f qcow2 -F qcow2 \
-b noble-server-cloudimg-amd64.img pruebas.qcow2 20G
Formatting 'pruebas.qcow2', fmt=qcow2 cluster_size=65536 ...
$ sudo virt-install \
--name srv-tramontana-pruebas \
--memory 2048 --vcpus 2 --cpu host-passthrough \
--disk path=/var/lib/libvirt/images/pruebas.qcow2,device=disk,bus=virtio \
--disk path=/var/lib/libvirt/images/semilla.iso,device=cdrom \
--network network=default,model=virtio \
--os-variant ubuntu24.04 \
--graphics none --console pty,target_type=serial \
--import --noautoconsole
Domain creation completed.
$ virsh list
Id Name State
----------------------------------------
1 srv-tramontana-pruebas running
$ sleep 60 && virsh domifaddr srv-tramontana-pruebas
Name MAC address Protocol Address
-------------------------------------------------------------------------------
vnet0 52:54:00:8a:c1:f2 ipv4 192.168.122.104/24
$ ssh [email protected] 'hostname; sysctl -n net.ipv4.tcp_congestion_control'
srv-tramontana-pruebas
bbrMenos de dos minutos, sin una sola pregunta, y con la configuración de producción ya aplicada. Y —esto es lo que hace valioso el método— el user-data es un fichero de texto que se versiona en git: la máquina de pruebas es reproducible. Es la misma idea de infraestructura como código que verás con Ansible en 07-06, en su forma más simple.
Almacenamiento: formatos, pools y snapshots
raw frente a qcow2
$ sudo qemu-img create -f raw prueba.raw 10G
$ sudo qemu-img create -f qcow2 prueba.qcow2 10G
$ ls -lh prueba.raw prueba.qcow2
-rw-r--r-- 1 root root 10G ago 18 18:12 prueba.raw
-rw-r--r-- 1 root root 193K ago 18 18:12 prueba.qcow2
$ du -h prueba.raw prueba.qcow2
0 prueba.raw
196K prueba.qcow2Fíjate en la diferencia entre ls y du para prueba.raw: ls dice 10 GB, du dice 0. Es un fichero disperso (sparse), así que raw también hace aprovisionamiento fino en sistemas de ficheros que lo soportan. La diferencia real entre los formatos está en otra parte:
raw |
qcow2 |
|
|---|---|---|
| Rendimiento | El máximo posible | 95-98 % del de raw |
| Aprovisionamiento fino | Sí, con ficheros dispersos | Sí, nativo |
| Snapshots internos | No | Sí |
| Discos de respaldo | No | Sí |
| Compresión y cifrado | No | Sí |
| Se puede montar en el anfitrión | Directamente con losetup |
Requiere qemu-nbd |
La decisión práctica: qcow2 salvo que midas que el 2-5 % de rendimiento importa. Los snapshots y los discos de respaldo valen mucho más que ese margen en un entorno de pruebas, y para producción existen alternativas mejores que ambos (un volumen LVM directo, que es lo que usan muchos hipervisores).
$ sudo qemu-img info pruebas.qcow2
image: pruebas.qcow2
file format: qcow2
virtual size: 20 GiB (21474836480 bytes)
disk size: 1.24 GiB
backing file: noble-server-cloudimg-amd64.img
backing file format: qcow2
# Ampliar un disco: primero el fichero, DESPUES el sistema de ficheros dentro
$ sudo qemu-img resize pruebas.qcow2 +10G
Image resized.
# Y dentro del huesped, con lo aprendido en 05-04:
$ ssh [email protected] 'sudo growpart /dev/vda 1 && sudo resize2fs /dev/vda1'
# Convertir entre formatos
$ sudo qemu-img convert -f qcow2 -O raw pruebas.qcow2 pruebas.rawEl disco de respaldo (backing file) es la pieza que hace eficiente un laboratorio: la imagen base se comparte y cada VM solo guarda sus diferencias. Diez máquinas de pruebas ocupan la base más diez pequeños ficheros de cambios, no diez sistemas completos. Con una regla que hay que respetar: si modificas la imagen base, todos los discos que se respaldan en ella se corrompen. La base es de solo lectura, en la práctica.
# Aplanar un disco: incorporar el contenido de la base y romper la dependencia
$ sudo qemu-img rebase -p -b "" pruebas.qcow2Pools de almacenamiento
libvirt organiza el almacenamiento en pools, que abstraen dónde viven los discos:
$ virsh pool-list --all --details
Name State Autostart Persistent Capacity Allocation Available
-------------------------------------------------------------------------------
default running yes yes 14.51 GiB 1.31 GiB 13.20 GiB
$ virsh pool-dumpxml default | grep -E '<path>|<type|name>'
<pool type='dir'>
<name>default</name>
<path>/var/lib/libvirt/images</path>
# Crear un pool en el volumen de datos, que tiene mas sitio
$ virsh pool-define-as pruebas dir --target /srv/tramontana/vm
$ virsh pool-build pruebas && virsh pool-start pruebas
$ virsh pool-autostart pruebas
$ virsh vol-list pruebas --detailsUn pool puede ser un directorio, un grupo de volúmenes LVM, un dispositivo iSCSI, NFS o Ceph. El de tipo logical sobre LVM es interesante aquí: vg-datos ya existe desde 05-04 y usar volúmenes lógicos directos como discos evita la capa de fichero.
Snapshots, y la advertencia
$ virsh snapshot-create-as srv-tramontana-pruebas \
--name limpia-24.04 \
--description "Recien aprovisionada por cloud-init, antes de pruebas" \
--atomic
Domain snapshot limpia-24.04 created
$ virsh snapshot-list srv-tramontana-pruebas
Name Creation Time State
-----------------------------------------------------
limpia-24.04 2026-08-18 18:22:41 +0200 shutoff
# Romper algo, probar, y volver atras
$ virsh snapshot-revert srv-tramontana-pruebas --snapshotname limpia-24.04
$ virsh snapshot-delete srv-tramontana-pruebas --snapshotname limpia-24.04Y la advertencia, con el mismo peso que la de 05-04 sobre RAID:
Un snapshot no es una copia de seguridad. Vive en el mismo fichero (o junto a él), en el mismo disco, en la misma máquina. Si el disco falla, si el fichero se corrompe o si alguien borra el directorio, se van el snapshot y el original juntos. No cumple ninguno de los tres requisitos de la regla 3-2-1 de 05-08.
Lo que un snapshot es: un punto de retorno rápido para una operación arriesgada. Exactamente lo que necesitas antes de probar un cambio de kernel. Y tiene dos costes que hay que conocer: cada snapshot activo degrada el rendimiento de escritura, porque cada bloque modificado exige copiar el original primero; y acumular decenas de snapshots encadenados puede hacer el disco inmanejable.
Los snapshots con la máquina en marcha (--live) capturan también la memoria y son mucho más delicados: si la aplicación tiene datos a medio escribir, el estado restaurado puede ser inconsistente. La opción --quiesce pide al agente del huésped que vacíe los sistemas de ficheros antes, y requiere qemu-guest-agent instalado dentro. Para un entorno de pruebas, apagar la máquina y hacer el snapshot en frío es más simple y más fiable.
Red: NAT, aislada y puente
Los tres modos, y cuándo cada uno:
| Modo | La VM ve | La red ve la VM | Entre VM | Cuándo |
|---|---|---|---|---|
NAT (default) |
Internet, sí | No | Sí | Por defecto; laboratorio |
| Aislada | Nada fuera | No | Sí | Pruebas sin salida; malware |
| Puente | Todo | Sí, con IP propia | Sí | Servidores que deben ser accesibles |
$ virsh net-dumpxml default
<network>
<name>default</name>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>En NAT, libvirt monta un puente (virbr0), un servidor DHCP y DNS (dnsmasq) y las reglas de traducción de direcciones. La VM sale a Internet con la IP del anfitrión, y desde la red nadie puede iniciar una conexión hacia ella. Para un entorno de pruebas es lo correcto: aislamiento por defecto sin configurar nada.
Y aquí conviene cerrar el asunto de virbr0 y el arranque, porque ahora se ve el mecanismo completo: la interfaz existe desde que libvirt arranca, pero no tiene enlace hasta que una VM se conecta a ella. systemd-networkd-wait-online, en su configuración por defecto, esperaba a que todas las interfaces gestionadas estuvieran en línea, y esa nunca lo estaría con las máquinas apagadas. De ahí los 35 segundos.
$ virsh net-define /dev/stdin <<'EOF'
<network>
<name>aislada</name>
<bridge name='virbr1' stp='on' delay='0'/>
<ip address='192.168.200.1' netmask='255.255.255.0'>
<dhcp><range start='192.168.200.10' end='192.168.200.100'/></dhcp>
</ip>
</network>
EOF
$ virsh net-start aislada && virsh net-autostart aisladaSin elemento <forward>, la red no tiene salida: las máquinas se ven entre sí y con el anfitrión, y nada más. Es lo que se usa para probar una configuración de firewall o analizar algo sospechoso.
El puente es el modo que hace falta cuando la VM debe ser un servidor accesible. Requiere reconfigurar la red del anfitrión con netplan, aplicando 06-01 — y con el mismo cuidado, porque te puede dejar sin acceso a la máquina:
$ sudo cp -p /etc/netplan/50-cloud-init.yaml \
/etc/netplan/50-cloud-init.yaml.bak-$(date +%F)
$ sudo tee /etc/netplan/60-puente.yaml >/dev/null <<'EOF'
network:
version: 2
ethernets:
enp0s3:
dhcp4: false
dhcp6: false
bridges:
br0:
interfaces: [enp0s3]
addresses: [10.0.2.15/24]
routes:
- to: default
via: 10.0.2.2
nameservers:
addresses: [10.0.2.2, 1.1.1.1]
parameters:
stp: false
forward-delay: 0
EOF
$ sudo chmod 600 /etc/netplan/60-puente.yaml
# netplan try con reversion automatica a los 120 s: la red de seguridad de 06-01
$ sudo netplan try
Do you want to keep these settings?
Press ENTER before the timeout to accept the new configurationEse netplan try no es opcional. Configurar un puente por SSH es exactamente el caso para el que existe: si el puente sale mal, pierdes la sesión, y la reversión automática a los 120 segundos te devuelve la máquina.
Y una advertencia específica: el puente reasigna la IP de la interfaz física al puente. Durante la transición hay un corte de red de unos segundos, y si la configuración es incorrecta el corte es permanente. En un servidor remoto sin consola, esto se hace con muchísimo cuidado.
Rendimiento: virtio y configuración de CPU
virtio: la diferencia entre lenta y rápida
Los dispositivos que QEMU presenta al huésped pueden ser emulados (imita un dispositivo real, como una tarjeta Intel e1000) o paravirtualizados (virtio: un dispositivo que no existe en el mundo real, diseñado para hablar directamente con el hipervisor).
La diferencia es grande y merece verse medida:
| Dispositivo | Emulado | virtio |
|---|---|---|
| Disco | ~40 % del rendimiento nativo | 95-98 % |
| Red | ~1 Gbit/s con mucha CPU | 10 Gbit/s+ con poca CPU |
$ virsh dumpxml srv-tramontana-pruebas | grep -A2 -E "<disk|<interface"
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native' discard='unmap'/>
<source file='/var/lib/libvirt/images/pruebas.qcow2'/>
<target dev='vda' bus='virtio'/>
<interface type='network'>
<mac address='52:54:00:8a:c1:f2'/>
<source network='default'/>
<model type='virtio'/>Las pistas de que virtio está activo: el disco se llama vda (no sda) y el bus es virtio. Si ves sda con bus sata, estás usando emulación y perdiendo más de la mitad del rendimiento de disco.
Los tres atributos del <driver> también importan:
| Atributo | Valor | Por qué |
|---|---|---|
cache |
none |
Evita la doble caché anfitrión/huésped; más seguro ante cortes |
io |
native |
E/S asíncrona del kernel; mejor con cache=none |
discard |
unmap |
Propaga el TRIM del huésped: el fichero qcow2 se reduce al borrar |
discard=unmap es el que evita que un disco qcow2 crezca indefinidamente: sin él, borrar datos dentro del huésped no libera espacio en el anfitrión.
Configuración de CPU
$ virsh dumpxml srv-tramontana-pruebas | grep -A3 '<cpu'
<cpu mode='host-passthrough' check='none' migratable='on'/>Los tres modos:
| Modo | Qué expone | Migración en vivo |
|---|---|---|
host-passthrough |
La CPU real, todas sus extensiones | Solo a anfitriones idénticos |
host-model |
Un modelo equivalente al del anfitrión | A anfitriones similares |
custom |
Un modelo concreto que tú eliges | A cualquier anfitrión con ese modelo o superior |
Y aquí se resuelve el pendiente de 07-03, el de AES-NI:
$ ssh [email protected] 'grep -o -m1 aes /proc/cpuinfo'
aes
$ ssh [email protected] 'openssl speed -evp aes-256-cbc 2>&1 | tail -2'
type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes
aes-256-cbc 1284412.10k 3841204.88k 4218844.12k 4402118.42k 4441204.88k4,4 GB/s de cifrado AES: eso solo es posible con aceleración por hardware. Con host-model en lugar de host-passthrough, o con una CPU virtual genérica, el mismo openssl speed daría entre 200 y 600 MB/s, y ahí está el factor de cinco a diez que se planteaba como hipótesis en el último ejercicio de 07-03 para la lentitud de la copia cifrada.
La contrapartida de host-passthrough es la migración en vivo: una máquina que ve las extensiones exactas de un procesador no puede migrarse a otro anfitrión que no las tenga, porque el huésped las está usando. Para un entorno de pruebas en un único anfitrión, host-passthrough es la elección correcta.
Dos ajustes más de memoria, mencionados por completitud:
$ virsh dumpxml srv-tramontana-pruebas | grep -A3 -E '<memballoon|<memoryBacking'
<memballoon model='virtio'>El ballooning permite al anfitrión recuperar memoria de un huésped que no la está usando, mediante un driver dentro del huésped que la «infla». Es útil para densidad, y contraproducente para una base de datos, que reserva memoria compartida y no espera perderla. Las hugepages (<memoryBacking><hugepages/></memoryBacking>) reducen los fallos de TLB del huésped, con la misma lógica que las THP de 07-03, y son habituales en máquinas grandes de base de datos.
Clonar y crear srv-tramontana-pruebas
La máquina de cloud-init es limpia y reproducible, pero para reproducir un problema de producción a veces hace falta una copia del estado real. virt-clone la hace:
# La maquina origen debe estar APAGADA
$ virsh shutdown srv-tramontana-pruebas
$ virt-clone --original srv-tramontana-pruebas \
--name srv-tramontana-pruebas-2 \
--auto-clone
Allocating 'pruebas-2.qcow2' | 20 GB 00:00:04
Clone 'srv-tramontana-pruebas-2' created successfully.virt-clone copia los discos y genera direcciones MAC y UUID nuevos, que es lo que evita el conflicto que produce copiar el fichero a mano. Pero deja intacto el interior del huésped: mismo nombre de máquina, mismas claves de host de SSH, mismo machine-id. Y eso da problemas reales:
$ sudo virt-sysprep -d srv-tramontana-pruebas-2 \
--hostname pruebas-2 \
--operations defaults,-ssh-userdir
[ 0.0] Examining the guest ...
[ 4.2] Performing "abrt-data" ...
[ 4.2] Performing "bash-history" ...
[ 4.3] Performing "machine-id" ...
[ 4.4] Performing "ssh-hostkeys" ...
[ 4.5] Performing "logfiles" ...
[ 4.8] Setting a random seed
[ 4.9] Setting the machine ID in /etc/machine-id
[ 5.0] Setting the hostname: pruebas-2virt-sysprep limpia lo que hace única a una máquina: machine-id —que systemd y el journal usan como identificador—, claves de host de SSH, registros, historial de shell, y configuración de red asociada a la MAC anterior. Sin este paso, dos máquinas con el mismo machine-id producen entradas de journal mezcladas si se centralizan, y dos con las mismas claves de host provocan el aviso de huella cambiada de 06-02.
Y libguestfs permite además trabajar con el disco de una máquina apagada sin arrancarla, lo que resuelve el escenario de 07-01 sin necesidad de un medio de rescate:
# Inspeccionar un disco sin arrancar la maquina
$ sudo virt-ls -d srv-tramontana-pruebas /etc/tramontana/
app.conf
# Leer un fichero
$ sudo virt-cat -d srv-tramontana-pruebas /etc/fstab
# Y ARREGLAR un fstab roto sin arrancar ni usar un Live CD
$ sudo guestfish -d srv-tramontana-pruebas -i edit /etc/fstabEso es lo que convierte la recuperación de 07-01 en una operación de dos minutos cuando la máquina es virtual: en lugar de arrancar desde un ISO, montar y hacer chroot, se edita el fichero directamente en el disco apagado.
El entorno de pruebas, y para qué sirve
Con srv-tramontana-pruebas en marcha, las cuatro deudas del principio de la lección se pueden liquidar:
# 1. El release 3.3.0 que no arrancaba (deuda del Modulo 4)
$ virsh snapshot-create-as srv-tramontana-pruebas --name antes-de-3.3.0 --atomic
$ scp /srv/tramontana/backups/envios/tramontana-3.3.0.tar.gz \
[email protected]:/tmp/
$ ssh [email protected] \
'sudo ~/scripts/desplegar.sh 3.3.0; sudo journalctl -u tramontana -n 40'
# Y con el log completo delante, por fin se puede diagnosticar
# 2. El planificador de E/S (ejercicio 3 de 07-03), sin degradar produccion
$ ssh [email protected] \
'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches; \
echo mq-deadline | sudo tee /sys/block/vda/queue/scheduler'
# 3. La recuperacion de arranque de 07-01, con virsh console
$ virsh console srv-tramontana-pruebas
# (Ctrl+] para salir)
# 4. Y AES-NI, ya comprobado
$ ssh [email protected] 'grep -c -m1 aes /proc/cpuinfo'
1
# Volver al punto de partida cuando se acabe
$ virsh snapshot-revert srv-tramontana-pruebas --snapshotname antes-de-3.3.0Sobre la migración en vivo, para cerrar: libvirt permite mover una máquina en ejecución de un anfitrión a otro sin interrumpirla, copiando la memoria en varias pasadas hasta que queda tan poco por transferir que se puede pausar unos milisegundos y completar el cambio.
Requiere almacenamiento compartido (o --copy-storage-all, mucho más lento) y CPU compatibles —de ahí la contrapartida de host-passthrough—. Es la base del mantenimiento sin cortes: se vacían los anfitriones uno a uno para actualizarlos. Y es un adelanto de 07-07: mover una máquina no es lo mismo que tener alta disponibilidad, porque la migración requiere que el anfitrión origen siga funcionando. Frente a un fallo repentino, no sirve.
Herramientas gráficas y de más alto nivel, mencionadas: virt-manager es la interfaz gráfica de libvirt, cómoda para explorar y para acceder a la consola de una máquina con entorno gráfico; Vagrant define máquinas de desarrollo en un Vagrantfile versionable y funciona sobre libvirt, VirtualBox o VMware, y es la herramienta indicada cuando un equipo entero necesita entornos idénticos; y Terraform y OpenStack operan a escala de infraestructura, hablando también con libvirt por debajo.
Errores Comunes y Consejos
- No activar la virtualización anidada.
kvm-okdiceKVM is disabled by your BIOSy todo funciona por emulación, diez veces más lento. En VirtualBox,VBoxManage modifyvm <vm> --nested-hw-virt oncon la máquina apagada. - Olvidar
console=ttyS0en la línea de comandos del kernel.virsh consolemuestra una pantalla en blanco y parece que la máquina no arranca. Va emparejado con--console pty,target_type=serial. - Usar dispositivos emulados en vez de
virtio. Si el disco del huésped se llamasday novda, estás perdiendo más de la mitad del rendimiento de disco. Compruebabus='virtio'en el XML. - Confundir
destroycon borrar.virsh destroyes tirar del cable: no borra nada, pero puede dejar el sistema de ficheros del huésped inconsistente. Usashutdowny espera. - Modificar una imagen base que tiene discos respaldándose en ella. Corrompe todos los discos derivados. La base es de solo lectura en la práctica; usa
qemu-img rebasesi necesitas independizar uno. - Confiar en un snapshot como copia de seguridad. Vive en el mismo disco y en la misma máquina. No cumple ninguno de los tres requisitos de la regla 3-2-1.
- Acumular snapshots encadenados. Cada uno activo degrada la escritura, y una cadena larga hace el disco inmanejable. Consolida o borra.
- Clonar sin
virt-sysprep. Dos máquinas con el mismomachine-idy las mismas claves de host de SSH provocan journals mezclados y avisos de huella cambiada. - Configurar un puente por SSH sin
netplan try. Un puente mal definido te deja fuera de la máquina de forma permanente. La reversión automática a los 120 segundos existe exactamente para esto. - Omitir
discard=unmap. El ficheroqcow2crece indefinidamente porque borrar dentro del huésped no libera espacio en el anfitrión. - Activar ballooning en una máquina de base de datos. La base de datos reserva memoria compartida y no espera que se la quiten. Provoca degradación difícil de diagnosticar.
- Consejo de método. Versiona el
user-datade cloud-init en git junto a los scripts. Una máquina de pruebas que se recrea con un comando en dos minutos se usa; una que hay que reinstalar a mano se abandona en cuanto se desconfigura.
Ejercicios
Ejercicio 1
Diseña el user-data de cloud-init para una máquina srv-tramontana-pruebas que reproduzca la configuración de seguridad de producción lo más fielmente posible, y explica qué no se puede reproducir y por qué. Justifica cada decisión.
Ejercicio 2
El release 3.3.0 no arranca. Diseña el procedimiento completo para diagnosticarlo en el entorno de pruebas, usando lo aprendido en 07-01, 07-02 y esta lección, de forma que puedas repetir el intento tantas veces como haga falta.
Ejercicio 3
Marta pregunta si conviene mover Tramontana Reservas a máquinas virtuales gestionadas por KVM en un servidor propio, en lugar del VPS actual. Redacta el análisis: qué se gana, qué se pierde, y qué recomiendas.
Soluciones
Solución 1
#cloud-config
# user-data para srv-tramontana-pruebas
# Objetivo: reproducir produccion lo suficiente para que las pruebas sean
# validas, SIN copiar nada que sea un secreto real.
hostname: srv-tramontana-pruebas
fqdn: srv-tramontana-pruebas.tramontana.example
manage_etc_hosts: true
# --- Identidades: mismos uid/gid que produccion ---
# Motivo: los permisos, ACL y SGID solo son representativos si los
# identificadores numericos coinciden. Un 2770 sobre el gid 1002 solo se
# comporta igual si el grupo tramontana ES el 1002.
groups:
- tramontana: [operador]
users:
- name: operador
uid: 1000
groups: [sudo, adm, tramontana]
shell: /bin/bash
lock_passwd: false
sudo: 'ALL=(ALL) NOPASSWD:ALL'
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@portatil-alumno
- name: luis
uid: 1001
groups: [tramontana]
shell: /bin/bash
lock_passwd: true
- name: svc-tramontana
uid: 997
system: true
shell: /usr/sbin/nologin
homedir: /opt/tramontana
create_groups: false
no_create_home: true
ssh_pwauth: false
disable_root: true
# --- Paquetes: los mismos que produccion, mas los de diagnostico ---
package_update: true
package_upgrade: true
packages:
- postgresql-16
- restic
- ufw
- fail2ban
- auditd
- aide
- apparmor-utils
- libpam-pwquality
- needrestart
- sysstat
# Herramientas de diagnostico: en pruebas SI, en produccion no hacen falta
- linux-tools-generic
- strace
- bpfcc-tools
- bpftrace
- qemu-guest-agent
write_files:
# Configuracion de la aplicacion, con log_nivel=debug (unica diferencia)
- path: /etc/tramontana/app.conf
owner: root:tramontana
permissions: '0640'
content: |
db_host=127.0.0.1
db_port=5432
db_name=tramontana_reservas
max_conexiones=80
timeout_consulta=30
log_nivel=debug
escucha=127.0.0.1
# sysctl de rendimiento: identico a produccion. Sin esto, cualquier
# medicion comparativa seria invalida.
- path: /etc/sysctl.d/70-rendimiento.conf
owner: root:root
permissions: '0644'
content: |
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
fs.inotify.max_user_watches = 524288
# sysctl de seguridad: identico, INCLUIDO ptrace_scope.
# Decision deliberada: si en pruebas relajamos ptrace_scope, no
# reproduciriamos el "Operation not permitted" de 07-02, y las pruebas
# de diagnostico no serian representativas.
- path: /etc/sysctl.d/60-endurecimiento.conf
owner: root:root
permissions: '0644'
content: |
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.randomize_va_space = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
kernel.unprivileged_bpf_disabled = 1
kernel.yama.ptrace_scope = 1
- path: /etc/modprobe.d/blacklist-tramontana.conf
owner: root:root
permissions: '0644'
content: |
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install udf /bin/true
install dccp /bin/true
install sctp /bin/true
# Datos de prueba SINTETICOS, generados, nunca copiados de produccion
- path: /home/operador/datos/casas.txt
owner: operador:operador
permissions: '0640'
content: |
mas-figueres
can-ventos
la-solana
cal-ferrer
el-moli
runcmd:
# Rutas con los mismos permisos y SGID que produccion
- [ install, -d, -m, '0755', -o, root, -g, root, /opt/tramontana ]
- [ install, -d, -m, '2770', -o, svc-tramontana, -g, tramontana,
/opt/tramontana/shared/uploads ]
- [ install, -d, -m, '0750', -o, svc-tramontana, -g, adm,
/var/log/tramontana ]
- [ install, -d, -m, '2770', -o, operador, -g, tramontana,
/srv/tramontana/backups ]
# Firewall con la misma politica de lista blanca
- [ ufw, --force, default, deny, incoming ]
- [ ufw, --force, default, allow, outgoing ]
- [ ufw, limit, '22/tcp' ]
- [ ufw, allow, from, 192.168.122.0/24, to, any, port, '5432', proto, tcp ]
- [ ufw, --force, enable ]
# Aplicar y activar
- [ sysctl, --system ]
- [ update-initramfs, -u, -k, all ]
- [ systemctl, enable, --now, sysstat ]
- [ systemctl, enable, --now, qemu-guest-agent ]
# Generar reservas.csv sinteticas con la misma estructura
- [ bash, -c, 'printf "id;fecha;casa;huesped;noches;importe\n" > /home/operador/datos/reservas.csv' ]
- [ bash, -c, 'for i in $(seq 1001 1025); do printf "%s;2026-08-%02d;mas-figueres;Huesped %s;2;250,00\n" "$i" "$((i-1000))" "$i"; done >> /home/operador/datos/reservas.csv' ]
- [ chown, 'operador:operador', /home/operador/datos/reservas.csv ]
final_message: "srv-tramontana-pruebas listo tras $UPTIME segundos"Qué NO se puede reproducir, y por qué. Esta es la parte importante del ejercicio, porque determina qué pruebas son válidas:
| No reproducible | Por qué | Consecuencia para las pruebas |
|---|---|---|
Los secretos reales (db_password, contraseña de restic, clave GPG de pass) |
Copiarlos multiplicaría por dos su exposición y violaría la regla de 06-05 | Las pruebas usan credenciales propias. No se puede probar la rotación real |
El fichero .cred de systemd-creds |
Está cifrado con una clave derivada de la máquina de producción; es indescifrable aquí | Hay que generar uno nuevo. Es la limitación que ya se documentó en 06-05 |
| El certificado de Let's Encrypt | Requiere el dominio público y el desafío ACME. Además, cada emisión consume cuota | Se usa un autofirmado. No se puede probar la renovación real |
| Los datos personales de huéspedes | RGPD: un entorno de pruebas tiene menos controles, más accesos y más copias. Copiarlos sería una infracción | Datos sintéticos con la misma estructura y volumen |
| El volumen LUKS con su clave | La clave es un secreto y el disco es físico | Se puede crear un LUKS nuevo con clave de pruebas. Válido para medir rendimiento, no para restaurar copias reales |
| La base de datos de AIDE | Depende de las sumas de los ficheros reales de esta máquina | Se inicializa aquí. Válido para probar el mecanismo, no para comparar con producción |
| El hardware exacto | Es una VM anidada, con dos capas de hipervisor | Las mediciones absolutas de rendimiento no son comparables; solo las relativas antes/después dentro de la propia máquina |
Y las tres decisiones que conviene justificar porque son contraintuitivas:
- Mantener
ptrace_scope = 1en pruebas. La tentación es relajarlo para diagnosticar cómodamente. Sería un error: entonces no reproduciríamos elOperation not permittedde 07-02 y las pruebas de diagnóstico no valdrían. Un entorno de pruebas que difiere en seguridad no prueba la seguridad. - Mismos UID y GID numéricos. Es fácil dejar que cloud-init asigne los que quiera, y entonces el
2770sobre el grupotramontanano se comporta igual, y las pruebas de permisos son inválidas. Los identificadores numéricos son parte de la configuración. log_nivel=debugcomo única diferencia intencionada, más las herramientas de diagnóstico. Ambas cosas son más de lo que hay en producción, no menos, así que no invalidan nada — y hay que anotarlas, porque el volumen de logs sí difiere.
La última advertencia va al runbook: este user-data contiene la clave pública de SSH y los identificadores de la organización, así que se versiona en el repositorio interno, no en uno público. Y nunca debe contener un secreto real: para eso está pass y el aprovisionamiento posterior.
Solución 2
La clave del diseño es que el diagnóstico debe ser repetible: cada intento parte del mismo estado, para que las diferencias observadas se deban al cambio y no a residuos del intento anterior.
# --- Fase 0: punto de retorno limpio y repetible ---
$ virsh shutdown srv-tramontana-pruebas
$ for i in {1..30}; do
[[ "$(virsh domstate srv-tramontana-pruebas)" == "shut off" ]] && break
sleep 2
done
$ virsh snapshot-create-as srv-tramontana-pruebas \
--name base-3.2.1 \
--description "3.2.1 activa y verificada, antes de intentar 3.3.0" \
--atomic
$ virsh start srv-tramontana-pruebasEl snapshot en frío es deliberado: un --live capturaría la memoria y, sin --quiesce, podría dejar PostgreSQL inconsistente. Para un punto de retorno, apagado es más simple y más fiable.
# --- Fase 1: verificar el estado de partida ---
$ VM=192.168.122.104
$ ssh operador@$VM '~/scripts/revision_salud.sh; echo "estado: $?"'
estado: 0
$ ssh operador@$VM 'readlink /opt/tramontana/app'
releases/3.2.1Sin esta verificación, un fallo de 3.3.0 podría confundirse con un entorno de pruebas que ya estaba roto.
# --- Fase 2: preparar la observacion ANTES de provocar el fallo ---
# El error puede durar milisegundos: hay que estar mirando cuando ocurra.
$ ssh operador@$VM 'sudo journalctl -f -u tramontana' > /tmp/journal-3.3.0.log &
$ ssh operador@$VM 'sudo execsnoop-bpfcc -T' > /tmp/procesos-3.3.0.log &
$ ssh operador@$VM 'sudo journalctl -f -k | grep -i apparmor' > /tmp/apparmor.log &execsnoop-bpfcc es la elección informada aquí: si el binario arranca y muere en 200 ms, ps no lo verá nunca, y esta herramienta captura cada execve con su código de salida. Es exactamente el caso de uso de la tabla de 07-02.
# --- Fase 3: el intento, con el modo de simulacion primero ---
$ scp /srv/tramontana/backups/envios/tramontana-3.3.0.tar.gz \
/srv/tramontana/backups/envios/tramontana-3.3.0.tar.gz.sha256 \
operador@$VM:/tmp/
$ ssh operador@$VM 'cd /tmp && sha256sum -c tramontana-3.3.0.tar.gz.sha256'
tramontana-3.3.0.tar.gz: La suma coincide
# --dry-run primero: convencion del curso, y descarta problemas del script
$ ssh operador@$VM 'sudo ~/scripts/desplegar.sh --dry-run 3.3.0'
$ ssh operador@$VM 'sudo ~/scripts/desplegar.sh 3.3.0'# --- Fase 4: recoger la evidencia, de lo general a lo especifico ---
# 4a. El journal. La causa esta aqui el 70% de las veces.
$ ssh operador@$VM 'sudo journalctl -u tramontana -n 60 --no-pager -o short-precise'
# 4b. Como murio? El codigo de salida y la senal lo dicen todo.
$ ssh operador@$VM 'systemctl show tramontana -p ExecMainStatus -p ExecMainCode \
-p Result -p StatusErrno'Y aquí está el árbol de decisión que hace útil este procedimiento, porque cada síntoma tiene una herramienta distinta:
| Evidencia | Hipótesis | Herramienta que la confirma |
|---|---|---|
signal=SYS |
Filtro seccomp de SystemCallFilter |
ausyscall <n> sobre el syscall= del journal |
apparmor="DENIED" |
El perfil de AppArmor no contempla una ruta nueva | journalctl -k | grep DENIED |
status=127 o not found |
Falta una biblioteca compartida | ldd sobre el binario nuevo |
status=1 con mensaje propio |
Configuración: falta una clave en app.conf |
strace -e trace=%file | grep ENOENT |
Killed / oom-kill |
Excede MemoryMax=512M |
journalctl -k | grep -i oom |
| Arranca y muere sin mensaje | Cualquiera de las anteriores, silenciada | execsnoop + strace -f desde el arranque |
# 4c. Bibliotecas: la causa mas comun de un binario nuevo que no arranca
$ ssh operador@$VM 'ldd /opt/tramontana/releases/3.3.0/tramontana | grep -i "not found"'
libpq.so.6 => not found
# Y si aparece, la confirmacion y la causa raiz
$ ssh operador@$VM 'apt-cache policy libpq5; dpkg -l | grep libpq'# 4d. Si el journal no dice nada util: trazar el arranque desde el principio
$ ssh operador@$VM 'sudo -u svc-tramontana strace -f -o /tmp/traza.txt \
/opt/tramontana/releases/3.3.0/tramontana --config /etc/tramontana/app.conf; \
grep -E "ENOENT|EACCES|EPERM" /tmp/traza.txt | grep -v "lib\|locale" | tail -20'
# 4e. Comparar 3.2.1 con 3.3.0: que ha cambiado de verdad
$ ssh operador@$VM 'diff <(ldd /opt/tramontana/releases/3.2.1/tramontana | sort) \
<(ldd /opt/tramontana/releases/3.3.0/tramontana | sort)'
$ ssh operador@$VM 'sudo systemd-analyze security tramontana.service | tail -1'El paso 4e es el que más rendimiento da y el que suele olvidarse: comparar la versión que funciona con la que no. Si la única diferencia es una biblioteca nueva, ya tienes la respuesta sin trazar nada.
# --- Fase 5: volver al punto de partida y repetir ---
$ kill %1 %2 %3 2>/dev/null # cerrar los observadores
$ virsh shutdown srv-tramontana-pruebas
$ virsh snapshot-revert srv-tramontana-pruebas --snapshotname base-3.2.1
$ virsh start srv-tramontana-pruebas
$ ssh operador@$VM '~/scripts/revision_salud.sh; echo "estado: $?"'
estado: 0Tres decisiones de diseño que justifican el procedimiento:
- Los observadores se lanzan antes del intento. Un binario que muere en 200 ms no se puede observar a posteriori. Es la diferencia entre tener la evidencia y tener que repetir.
- El árbol de decisión está escrito antes de empezar. Bajo presión, la tentación es lanzar
stracea todo y ahogarse en salida. Con la tabla delante, cada síntoma lleva a una herramienta concreta. - El snapshot permite intentos ilimitados. Y esa es la razón de ser de la lección: este diagnóstico no se puede hacer en producción. Cada intento fallido deja un release a medio desplegar, un servicio caído y un rollback. En pruebas,
snapshot-reverty otra vez.
Nota final: cuando se encuentre la causa, la corrección se aplica y se prueba en pruebas hasta que revision_salud.sh devuelva 0, y solo entonces se despliega en producción. Que desplegar.sh tuviera rollback automático desde 04-07 evitó el desastre en su momento; tener dónde diagnosticar es lo que permite arreglarlo.
Solución 3
Análisis: migrar Tramontana Reservas a virtualización propia con KVM Para: Marta Vidal · De: Operaciones de sistemas · 18 de agosto de 2026
Recomendación breve: no ahora. Mantener el VPS y usar KVM únicamente para el entorno de pruebas, que es donde el beneficio es inmediato y el riesgo nulo. Detallo el razonamiento.
Qué ganaríamos
- Control completo de la pila. Podríamos ajustar la CPU de la máquina (
host-passthrough), la configuración de discos y el planificador de E/S. Recuerda que en las pruebas de rendimiento de esta semana no pudimos determinar si el cifrado de las copias iba acelerado por hardware, precisamente porque no controlamos esa capa.- Aislamiento fuerte entre servicios. Podríamos separar la base de datos de la aplicación en máquinas distintas, con una frontera de seguridad real entre ellas. Hoy comparten sistema operativo: un compromiso de la aplicación alcanza directamente a la base de datos.
- Snapshots antes de cada cambio. Un punto de retorno de segundos, en lugar de depender de la restauración de copias con su RTO de 8 horas.
- Coste predecible a partir de cierto volumen. Un servidor propio con capacidad para varias máquinas puede salir más barato que cuatro o cinco VPS.
- Sin dependencia del proveedor en cuanto a versiones de kernel, características disponibles o cambios de precio.
Qué perderíamos, y esto es lo determinante
- El hardware pasa a ser nuestro problema. Discos, fuentes, memoria, ventiladores. Un disco que falla a las tres de la madrugada hoy lo resuelve el proveedor; con servidor propio lo resolvemos nosotros, y necesitaríamos discos en RAID —recordando que RAID no es una copia de seguridad— y repuestos.
- La energía, la refrigeración y la red dejan de estar garantizadas. Un servidor en una oficina depende de un solo circuito eléctrico y de una sola línea de datos. Un corte de luz de dos horas es una caída de dos horas. Un alojamiento con SAI redundante y doble acometida cuesta dinero.
- Un único punto de fallo, y uno nuevo: el anfitrión. Hoy tenemos un servidor que puede fallar. Con virtualización propia tendríamos las máquinas virtuales más el anfitrión, y si el anfitrión cae, caen todas a la vez. Esto es importante: la virtualización no da alta disponibilidad, y creer lo contrario es el error de diseño más común del área. Con un solo anfitrión, el riesgo de caída total aumenta.
- Aumenta la carga de trabajo, no disminuye. Hoy administramos un sistema operativo. Con virtualización propia administraríamos el anfitrión, el hipervisor, la red virtual, el almacenamiento y cada máquina huésped. Con la plantilla actual, eso es tiempo que sale de otro sitio.
- Coste inicial real. Servidor con capacidad y redundancia de discos, más alojamiento adecuado, más los repuestos. No es despreciable, y hay que compararlo con el coste anual del VPS a varios años, no a uno.
- Implicaciones de cumplimiento. Tratamos datos personales de huéspedes. Un servidor propio nos convierte en responsables de la seguridad física del equipo, que hoy es del proveedor y que en nuestro modelo de amenazas figura explícitamente como fuera de alcance. Eso habría que revisarlo con el responsable de protección de datos, y probablemente exigiría cifrado completo del disco raíz y control de acceso al espacio físico.
Lo que sí propongo, y ya está en marcha
Usar KVM para el entorno de pruebas, en la propia máquina de administración o en el VPS actual. El beneficio es inmediato y el riesgo, ninguno:
- Esta semana ha permitido, por primera vez, poder diagnosticar el release 3.3.0 que falló en julio y que quedó sin explicación.
- Permite probar cambios de configuración y actualizaciones de kernel antes de aplicarlos en producción.
- Permite ensayar el procedimiento de recuperación del runbook sin tocar el servidor real.
- Se recrea desde cero en dos minutos con un fichero de configuración versionado en git.
Cuándo reconsiderarlo. El análisis cambia si se dan al menos dos de estas condiciones:
Condición Por qué cambia la decisión Necesitamos cuatro o más servidores El coste por máquina del servidor propio baja mucho Hay presupuesto para dos anfitriones Solo entonces la virtualización aporta disponibilidad, no la resta Hay alojamiento profesional disponible Resuelve energía, refrigeración y red Hay una persona dedicada a infraestructura La carga de trabajo deja de salir de otro sitio El coste del VPS supera claramente la alternativa a tres años Es cuando la inversión se amortiza de verdad Y una alternativa intermedia que merece considerarse antes. Si el objetivo real es separar la base de datos de la aplicación por seguridad, o tener un entorno de preproducción permanente, contratar un segundo VPS consigue eso mismo por una fracción del coste y sin asumir la responsabilidad del hardware. Es un paso reversible; comprar un servidor no lo es.
Resumen para decidir. La virtualización propia es la respuesta correcta cuando el problema es escala y hay recursos para hacerla bien. Nuestro problema hoy no es la escala: es que no teníamos entorno de pruebas —resuelto— y que tenemos un único punto de fallo, que es un problema de redundancia y no de virtualización. Ese segundo asunto lo estoy estudiando y te traeré una propuesta con números.
Conclusión
Ya tienes el entorno de pruebas que el curso llevaba cuatro módulos echando en falta, y entiendes la pila que lo sostiene. Sabes qué virtualiza cada pieza: KVM da acceso a las extensiones de la CPU, QEMU emula los dispositivos, libvirt gestiona el conjunto con una API estable, y virsh es la herramienta del día a día. Sabes que destroy es tirar del cable y no borrar, que virsh console es la vía para recuperar un arranque roto sin interfaz gráfica, y que virtio en el disco y en la red es la diferencia entre una máquina lenta y una rápida. Aprovisionas sin intervención con cloud-init a partir de un fichero versionado en git, eliges qcow2 por sus snapshots y sus discos de respaldo sabiendo lo que cuesta ese 2-5 % de rendimiento, y tienes claro que un snapshot no es una copia de seguridad.
Y de paso has cerrado tres asuntos pendientes. El virbr0 que costaba 35 segundos de arranque en 07-01 tiene ahora una explicación completa: una interfaz que existe pero no tiene enlace hasta que arranca una máquina. La duda de 07-03 sobre AES-NI está resuelta: con host-passthrough, el huésped ve las extensiones de la CPU y cifra a 4,4 GB/s, lo que confirma el factor de cinco a diez que se planteaba como hipótesis. Y el release 3.3.0 que falló en el Módulo 4 tiene por fin un sitio donde reproducirse tantas veces como haga falta, con un snapshot como punto de retorno y un árbol de decisión escrito antes de empezar.
Fíjate ahora en el precio que has pagado por el aislamiento fuerte de las máquinas virtuales. Cada VM lleva su propio kernel, tarda medio minuto en arrancar, ocupa gigabytes en disco y reserva cientos de megabytes de memoria solo por existir. Para un entorno de pruebas eso es perfectamente razonable. Para desplegar una aplicación y su base de datos, y poder recrearlas en segundos, es carísimo. Y en el diagrama del tercer apartado ya viste la alternativa: compartir el kernel del anfitrión y aislar solo lo necesario.
En la lección 07-05: Contenedores de Linux y Docker se construye eso, y empieza por la idea que hay que tener clara antes de escribir el primer docker run: un contenedor no es una máquina pequeña, es un proceso aislado. Verás las tres primitivas del kernel que lo hacen posible —los namespaces, que aíslan lo que un proceso ve; los cgroups v2, que son literalmente la misma tecnología del MemoryMax=512M que pusiste en 05-05; y las capabilities de 05-02—, y reconocerás en los namespaces de montaje al chroot con el que reparaste GRUB en 07-01. Después llega Docker: imágenes y capas, un Dockerfile con construcción en varias etapas para no enviar el compilador a producción, volúmenes frente a bind mounts, redes con resolución por nombre, y un compose.yaml que levanta la aplicación y PostgreSQL juntos con comprobaciones de salud. Con dos avisos que la lección se toma en serio: pertenecer al grupo docker equivale en la práctica a tener root, y aquel kernel.unprivileged_userns_clone que dejaste comentado en 06-06 con una nota explicando por qué — ha llegado el momento de entenderla del todo.
Curso de Linux: De Principiante a Administrador de Sistemas
Módulo 1: Introducción a Linux
- ¿Qué es Linux?
- Historia de Linux
- Distribuciones de Linux
- Instalando Linux
- Primer Contacto con el Sistema
- Estructura del Sistema de Archivos de Linux
Módulo 2: Comandos Básicos de Linux
- Introducción a la Línea de Comandos
- Obtener Ayuda y Documentación del Sistema
- Navegando el Sistema de Archivos
- Operaciones con Archivos y Directorios
- Visualización y Edición de Archivos
- Enlaces Duros y Simbólicos
- Permisos y Propiedad de Archivos
Módulo 3: Habilidades Avanzadas en la Línea de Comandos
- El Entorno del Shell: Variables, Alias e Historial
- Uso de Comodines y Expresiones Regulares
- Búsqueda de Archivos y Contenido: find, locate y grep
- Tuberías y Redirección
- Procesamiento de Texto: cut, sort, uniq, sed y awk
- Gestión de Procesos
- Programación de Tareas con Cron
- Comandos de Redes
Módulo 4: Scripting en Shell
- Introducción al Scripting en Shell
- Variables y Tipos de Datos
- Entrada, Salida y Argumentos de un Script
- Estructuras de Control
- Funciones y Librerías
- Depuración y Manejo de Errores
- Scripts de Producción: Buenas Prácticas
Módulo 5: Administración del Sistema
- Gestión de Usuarios y Grupos
- sudo y Permisos Especiales
- Gestión de Paquetes
- Gestión de Discos
- systemd y la Gestión de Servicios
- Registros del Sistema: journald y syslog
- Monitoreo del Sistema y Optimización del Rendimiento
- Respaldo y Restauración
Módulo 6: Redes y Seguridad
- Configuración de Redes
- SSH y Acceso Remoto
- Firewall y Seguridad Perimetral
- Sistemas de Detección de Intrusos
- Gestión de Secretos y Certificados TLS
- Asegurando Sistemas Linux
Módulo 7: Temas Avanzados
- El Proceso de Arranque y la Recuperación del Sistema
- Diagnóstico Avanzado: strace, perf y eBPF
- Optimización del Kernel de Linux
- Virtualización con Linux
- Contenedores de Linux y Docker
- Automatización con Ansible
- Alta Disponibilidad y Balanceo de Carga
