Escenario con tres máquinas y dos redes, todo por línea de comandos:
- Router: conecta las máquinas virtuales con Internet
- Servidor NAS (Alpine): almacena los archivos de la página web y los comparte por NFS
- Servidor Web (Ubuntu): monta el recurso NFS y sirve la página con Nginx
Introducción
Asociar la IP flotante
Una vez creada la instancia de OpenStack, hay que asociar la IP flotante (en este caso, 172.22.201.35):

Accedemos por ssh -A para poder saltar desde ahí a las otras máquinas. Metemos la clave pública en el authorized_keys:
1sudo nano ~/.ssh/authorized_keys

Configuración máquina openstack
Actualizamos el sistema e instalamos los paquetes necesarios:
1sudo apt update && sudo apt upgrade -y
2
3sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager libguestfs-tools genisoimage cpu-checker
Comprobamos que la CPU soporta virtualización:
1sudo kvm-ok
Tiene que decir “KVM acceleration can be used”.
Añadimos el usuario a los grupos libvirt y kvm:
1sudo usermod -aG libvirt $USER
2sudo usermod -aG kvm $USER
Iniciamos el servicio y lo hacemos persistente:
1sudo systemctl start libvirt
2sudo systemctl enable libvirtd
3sudo systemctl status libvirtd
Creación de redes
Borramos la red por defecto y creamos dos redes nuevas: una default con MTU 1442, y una red_intra muy aislada.
Paramos y borramos la red por defecto:
1sudo virsh net-destroy default && sudo virsh net-undefine default
Nos vamos al directorio de redes de libvirt:
1cd /usr/share/libvirt/networks
2sudo rm -r default.xml
Red Default
Creamos la red-default con MTU 1442:
1sudo nano red-default.xml
1<network>
2 <name>default</name>
3 <forward mode='nat'/>
4 <bridge name='virbr0' stp='on' delay='0'/>
5 <mtu size='1442'/>
6 <ip address='192.168.122.1' netmask='255.255.255.0'>
7 <dhcp>
8 <range start='192.168.122.2' end='192.168.122.254'/>
9 </dhcp>
10 </ip>
11</network>
Definimos e iniciamos la red:
1sudo virsh net-define red-default.xml
2sudo virsh net-start default
3sudo virsh net-autostart default
Red Intra
Lo mismo para la red intra, esta sin NAT ni DHCP — totalmente aislada:
1sudo nano red-intra.xml
1<network>
2 <name>red_intra</name>
3 <bridge name='virbr1' stp='on' delay='0'/>
4 <mtu size='1442'/>
5</network>
1sudo virsh net-define red-intra.xml
2sudo virsh net-start red_intra
3sudo virsh net-autostart red_intra
Verificamos que las dos redes se crearon bien:
1sudo virsh net-list --all

Para ver el XML de una red y verificar que está bien:
1sudo virsh net-dumpxml red_intra
Creación de máquinas y Configuración
Creación de la máquina Router
Instalación por red. Primero el disco:
1sudo qemu-img create -f qcow2 /var/lib/libvirt/images/router-josemanuel.qcow2 10G
1sudo virt-install \
2 --name router-josemanuel \
3 --ram 1024 \
4 --vcpus 1 \
5 --disk path=/var/lib/libvirt/images/router-josemanuel.qcow2,size=10 \
6 --network network=default \
7 --network network=red_intra \
8 --location http://deb.debian.org/debian/dists/trixie/main/installer-amd64/ \
9 --graphics none \
10 --console pty,target_type=serial \
11 --extra-args 'console=ttyS0,115200n8 serial' \
12 --autostart
El router queda con dos interfaces: una en default (salida a Internet) y otra en red_intra (hacia el NAS y el servidor web).
Configuración router
1ssh -A user@192.168.122.170

Metemos la clave pública propia y la del profesor:
1mkdir -p ~/.ssh
2chmod 700 ~/.ssh
3nano ~/.ssh/authorized_keys
4chmod 600 ~/.ssh/authorized_keys

Configuramos sudo sin contraseña:
1sudo visudo
Al final del archivo:
1user ALL=(ALL) NOPASSWD:ALL

Configuramos las interfaces de red, dejando la de red_intra en estático (es la que conecta con el NAS y el servidor web):
1sudo nano /etc/network/interfaces
1# red muy aislada
2auto enp2s0
3iface enp2s0 inet static
4 address 192.168.200.1
5 netmask 255.255.255.0

1sudo systemctl restart networking
Comprobamos que se aplicó bien:
1ip a

Activación del IP forwarding
1echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
2sudo sysctl -p
Ya que estamos, configuramos el SNAT para que, al poner la gateway en el NAS y en el servidor web, tengan salida a Internet durante su instalación:
1sudo apt update && sudo apt install iptables-persistent -y
SNAT
1sudo nano /etc/iptables/rules.v4
1*filter
2:INPUT ACCEPT [0:0]
3:FORWARD ACCEPT [0:0]
4:OUTPUT ACCEPT [0:0]
5COMMIT
6
7*nat
8:PREROUTING ACCEPT [0:0]
9:INPUT ACCEPT [0:0]
10:OUTPUT ACCEPT [0:0]
11:POSTROUTING ACCEPT [0:0]
12
13# SNAT
14-A POSTROUTING -s 192.168.200.0/24 -o enp1s0 -j MASQUERADE
15
16COMMIT
Guardamos y aplicamos los cambios:
1sudo systemctl restart netfilter-persistent
Y verificamos las reglas con sudo iptables -t nat -L -n -v.
Una vez hecho esto, salimos y seguimos con las dos máquinas que faltan: el NAS y el servidor web.
Creación de la máquina Alpine
Va a ser el servidor NAS. Descargamos la ISO:
1wget https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-standard-3.22.0-x86_64.iso
2sudo mv alpine-standard-3.22.0-x86_64.iso /var/lib/libvirt/images/
Creamos los discos (uno para el sistema, otro para los datos):
1sudo qemu-img create -f qcow2 /var/lib/libvirt/images/nas-josemanuel.qcow2 10G
2sudo qemu-img create -f qcow2 /var/lib/libvirt/images/nas-josemanuel-data.qcow2 1G
Instalación desde ISO:
1sudo virt-install \
2 --name nas-josemanuel \
3 --ram 1024 \
4 --vcpus 1 \
5 --disk path=/var/lib/libvirt/images/nas-josemanuel.qcow2,size=10 \
6 --disk path=/var/lib/libvirt/images/nas-josemanuel-data.qcow2,size=1 \
7 --network network=red_intra \
8 --cdrom /var/lib/libvirt/images/alpine-standard-3.22.0-x86_64.iso \
9 --osinfo alpinelinux3.19 \
10 --graphics none \
11 --console pty,target_type=serial \
12 --autostart
Configuración Alpine
Login como root (la primera vez no pide contraseña):

Con setup-alpine empezamos la configuración. Primero el hostname (nas-josemanuel):

Configuración de red, en estático, dentro de la red aislada:

Contraseña de root:

Zona horaria (Europe/Madrid):

El proxy lo dejamos vacío:

En el APK Mirror, si hay internet, simplemente enter; si no, se pone directamente la URL oficial en /etc/apk/repositories:
1http://dl-cdn.alpinelinux.org/alpine/v3.22/main
2http://dl-cdn.alpinelinux.org/alpine/v3.22/community
Creamos el usuario, su contraseña, y le decimos que instale OpenSSH:

Especificamos el disco de instalación:

Ctrl + ] para salir de la consola serie.
Configuración Alpine (post-instalación)
1ssh user@192.168.200.2
Desde root:
1apk update && apk add sudo nano lsblk
Sudo sin contraseña con visudo (editor vi: i para insertar, Ctrl+Fin, Esc y :wq para guardar):
1user ALL=(ALL) NOPASSWD:ALL

Metemos también la clave del profesor:
1mkdir -p /home/user/.ssh
2nano /home/user/.ssh/authorized_keys
3chown -R user:user /home/user/.ssh
4chmod 700 /home/user/.ssh
5chmod 600 /home/user/.ssh/authorized_keys

Vemos el disco extra que añadimos:
1lsblk

Está sin montar. Lo formateamos:
1mkfs.ext4 /dev/vdb
2mkdir -p /srv/data
3mount /dev/vdb /srv/data
Para hacerlo persistente:
1nano /etc/fstab
1/dev/vdb /srv/data ext4 defaults 0 0

Permisos y página web de prueba:
1chmod 755 /srv/data
2nano /srv/data/index.html
1<!DOCTYPE html>
2<html lang="es">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Jose manuel sierra</title>
7 <style>
8 body {
9 font-family: Arial, sans-serif;
10 text-align: center;
11 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12 color: white;
13 padding: 50px;
14 }
15 .container {
16 background: rgba(255, 255, 255, 0.1);
17 padding: 30px;
18 border-radius: 15px;
19 max-width: 600px;
20 margin: 0 auto;
21 }
22 h1 { font-size: 2.5em; }
23 p { font-size: 1.2em; }
24 </style>
25</head>
26<body>
27 <div class="container">
28 <h1>José Manuel Sierra Aroca</h1>
29 </div>
30</body>
31</html>
Ahora instalaremos el servidor NFS
1apk add nfs-utils
2rc-update add nfs
3rc-service nfs start
Verificamos:
1rc-service nfs status

Configuramos la exportación:
1nano /etc/exports
1/srv/data 192.168.200.0/24(rw,sync,no_subtree_check,no_root_squash)

Aplicamos y verificamos:
1exportfs -ra
2exportfs -v

Creación del Servidor web Ubuntu
Servidor Web Ubuntu 24.04 con cloud-init. Descargamos la imagen cloud:
1wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
2sudo mv noble-server-cloudimg-amd64.img /var/lib/libvirt/images/
Disco con clonación enlazada (backing file, para no duplicar la imagen base):
1sudo qemu-img create -f qcow2 -F qcow2 -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img /var/lib/libvirt/images/web-josemanuel.qcow2
Ficheros de configuración de cloud-init:
1sudo nano meta-data
1instance-id: web-josemanuel
2local-hostname: web-josemanuel
1sudo nano user-data
1users:
2 - name: user
3 sudo: ALL=(ALL) NOPASSWD:ALL
4 shell: /bin/bash
5 lock_passwd: false
6 plain_text_passwd: ********
7 ssh_authorized_keys:
8 - ssh-ed25519 AAAA... arooca27@gmail.com
9 - ssh-rsa AAAA... jose@debian
1sudo nano network-config
1version: 2
2ethernets:
3 enp1s0:
4 addresses:
5 - 192.168.200.3/24
6 routes:
7 - to: default
8 via: 192.168.200.1
9 nameservers:
10 addresses:
11 - 8.8.8.8
Generamos la ISO de cloud-init:
1sudo genisoimage -output /var/lib/libvirt/images/web-josemanuel-cidata.iso -volid cidata -joliet -rock meta-data user-data network-config
Y creamos la máquina:
1sudo virt-install \
2 --name web-josemanuel \
3 --ram 1024 \
4 --vcpus 1 \
5 --disk path=/var/lib/libvirt/images/web-josemanuel.qcow2 \
6 --disk path=/var/lib/libvirt/images/web-josemanuel-cidata.iso,device=cdrom \
7 --network network=red_intra \
8 --osinfo ubuntu24.04 \
9 --graphics none \
10 --console pty,target_type=serial \
11 --import \
12 --autostart
Ctrl + ] para salir.
Configuración servidor web
Desde el router, por ssh:
1ssh user@192.168.200.3
Instalamos Nginx:
1sudo apt update && sudo apt install -y nginx
2sudo systemctl enable nginx
3sudo systemctl start nginx
4sudo systemctl status nginx

Comprobamos con curl:
1curl http://localhost

Ahora nos instalaremos el cliente NFS
1sudo apt install -y nfs-common
2sudo mkdir -p /var/www/data
3sudo mount -t nfs 192.168.200.2:/srv/data /var/www/data
Verificamos el montaje y el contenido:
1df -h | grep srv/data

1ls -la /var/www/data

Montaje persistente:
1sudo nano /etc/fstab
1192.168.200.2:/srv/data /var/www/data nfs defaults 0 0

Si mount -a no muestra nada, es que está bien:
1sudo mount -a

Virtual host
1sudo nano /etc/nginx/sites-available/data
1server {
2 listen 80;
3 server_name data.josemanuel.org;
4
5 root /var/www/data;
6 index index.html index.htm;
7
8 location / {
9 try_files $uri $uri/ =404;
10 }
11
12 access_log /var/log/nginx/data.josemanuel.org-access.log;
13 error_log /var/log/nginx/data.josemanuel.org-error.log;
14}
Lo habilitamos y desactivamos el sitio por defecto (daba conflicto):
1sudo ln -s /etc/nginx/sites-available/data /etc/nginx/sites-enabled/
2sudo rm /etc/nginx/sites-enabled/default
Verificamos y recargamos:
1sudo nginx -t

1sudo systemctl reload nginx
Probamos:
1curl -H "Host: data.josemanuel.org" http://localhost

Y ahora probamos desde el router:
1curl http://192.168.200.3

Sabiendo que se ve desde el router, ahora hacemos un DNAT en el router para verlo desde la instancia de OpenStack, y por último un proxy inverso en la máquina de OpenStack para verlo desde el host físico. Se hace en este orden (de derecha a izquierda: host → máquina OpenStack → router → servidor web) para saber exactamente en qué capa falla algo si deja de funcionar.
Configuración DNAT en el router
1sudo nano /etc/iptables/rules.v4
1*nat
2:PREROUTING ACCEPT [0:0]
3:OUTPUT ACCEPT [0:0]
4:POSTROUTING ACCEPT [0:0]
5:INPUT ACCEPT [0:0]
6
7#SNAT
8-A POSTROUTING -s 192.168.200.0/24 -o enp1s0 -j MASQUERADE
9
10#DNAT
11-A PREROUTING -i enp1s0 -p tcp --dport 80 -j DNAT --to-destination 192.168.200.3:80
12
13COMMIT
1sudo systemctl restart netfilter-persistent.service
Desde la máquina de OpenStack, curl a la IP del router para comprobar el DNAT:
1curl http://192.168.122.170

La vemos, así que está bien. Ahora, en la máquina de OpenStack, configuramos el proxy inverso — instalamos Nginx:
1sudo apt update && sudo apt install -y nginx
Archivo de configuración del proxy:
1sudo nano /etc/nginx/sites-available/proxy-router
1server {
2 listen 80 default_server;
3 listen [::]:80 default_server;
4
5 server_name _;
6
7 location / {
8 proxy_pass http://192.168.122.170/;
9 proxy_redirect http://192.168.122.170/ /;
10 proxy_set_header Host $host;
11 proxy_set_header X-Real-IP $remote_addr;
12 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
13 proxy_set_header X-Forwarded-Proto $scheme;
14 }
15}
Activamos la configuración y desactivamos el sitio por defecto:
1sudo ln -s /etc/nginx/sites-available/proxy-router /etc/nginx/sites-enabled/
2sudo rm /etc/nginx/sites-enabled/default
Verificamos y reiniciamos:
1sudo nginx -t
2sudo systemctl restart nginx
3sudo systemctl status nginx
Comprobación
Como antes se modificó el index.html tanto del servidor web como el de la propia máquina de OpenStack, sirve para distinguir cuál de las dos páginas se está viendo realmente al entrar por la IP — y así confirmar que es la del servidor web y no la de OpenStack por defecto (que es justo lo que no queremos). Para ello también se configuró el /etc/hosts del host físico para poder resolver por nombre:

Y si buscamos desde el host, por navegador, vemos que nos muestra la página del servidor web y no la de OpenStack:


Redimensionar disco NAS
Con todo funcionando, redimensionamos el disco NAS. Hay que apagar la máquina primero, porque no se puede redimensionar en caliente:
1sudo virsh shutdown nas-josemanuel
2sudo virsh list --all

Redimensionamos la imagen del disco de datos:
1sudo qemu-img resize /var/lib/libvirt/images/nas-josemanuel-data.qcow2 2G
2sudo qemu-img info /var/lib/libvirt/images/nas-josemanuel-data.qcow2

Iniciamos el NAS de nuevo y nos conectamos:
1sudo virsh start nas-josemanuel
2ssh -A user@192.168.122.170
3ssh user@192.168.200.2
Vemos el disco ya de 2GB:
1fdisk -l /dev/vdb

Ahora redimensionamos también el sistema de archivos. Instalamos el paquete necesario:
1sudo apk add e2fsprogs-extra
2sudo resize2fs /dev/vdb

Miramos el espacio disponible:
1df -h | grep /srv/data

Creación de SNAPSHOTS
Desde la instancia de OpenStack, un snapshot por máquina:
1sudo virsh snapshot-create-as router-josemanuel \
2 snapshot-configuracion \
3 "Snapshot después de configurar el router"
4
5sudo virsh snapshot-create-as nas-josemanuel \
6 snapshot-configuracion \
7 "Snapshot después de configurar NFS y redimensionar disco"
8
9sudo virsh snapshot-create-as web-josemanuel \
10 snapshot-configuracion \
11 "Snapshot después de configurar nginx y montar NFS"
Listamos todos los snapshots para comprobar que se crearon bien:
1sudo virsh snapshot-list router-josemanuel
2sudo virsh snapshot-list nas-josemanuel
3sudo virsh snapshot-list web-josemanuel

