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):

Panel de OpenStack mostrando la instancia con la IP flotante asociada

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

Contenido del authorized_keys con las claves públicas añadidas

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

Listado de redes con default y red_intra activas y persistentes

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

Comprobación de la IP asignada al router

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

authorized_keys del router con ambas claves añadidas

Configuramos sudo sin contraseña:

1sudo visudo

Al final del archivo:

1user ALL=(ALL) NOPASSWD:ALL

Línea NOPASSWD añadida al final del sudoers

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

Contenido de /etc/network/interfaces con la interfaz de la red aislada en estático

1sudo systemctl restart networking

Comprobamos que se aplicó bien:

1ip a

Salida de ip a confirmando la IP 192.168.200.1/24 en la interfaz de red_intra

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):

Pantalla de bienvenida de Alpine Linux tras el primer arranque

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

Asistente setup-alpine pidiendo el hostname

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

Configuración de red estática del NAS: IP 192.168.200.2/24, gateway 192.168.200.1

Contraseña de root:

Asistente pidiendo la contraseña de root

Zona horaria (Europe/Madrid):

Selección de zona horaria Europe/Madrid

El proxy lo dejamos vacío:

Prompt de proxy HTTP/FTP dejado en blanco

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:

Creación del usuario, contraseña e instalación de OpenSSH

Especificamos el disco de instalación:

Selección del disco vda para la instalación del sistema

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

Línea NOPASSWD añadida al sudoers del NAS

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

authorized_keys del usuario del NAS con las claves añadidas

Vemos el disco extra que añadimos:

1lsblk

Salida de lsblk mostrando el disco vdb sin montar

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

Línea añadida al fstab del NAS para montar /dev/vdb en /srv/data

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

Servicio NFS con estado started

Configuramos la exportación:

1nano /etc/exports
1/srv/data    192.168.200.0/24(rw,sync,no_subtree_check,no_root_squash)

Línea de exportación NFS añadida a /etc/exports

Aplicamos y verificamos:

1exportfs -ra
2exportfs -v

Salida de exportfs -v confirmando el recurso /srv/data exportado

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

Estado del servicio nginx activo y corriendo

Comprobamos con curl:

1curl http://localhost

Página por defecto de nginx devuelta por curl

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

Montaje NFS verificado con df -h

1ls -la /var/www/data

Contenido del NAS montado en el servidor web: index.html visible

Montaje persistente:

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

Línea del montaje NFS añadida a /etc/fstab en el servidor web

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

1sudo mount -a

Ejecución de mount -a sin errores

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

Verificación de sintaxis de nginx correcta

1sudo systemctl reload nginx

Probamos:

1curl -H "Host: data.josemanuel.org" http://localhost

Respuesta del virtual host data.josemanuel.org servida desde el NAS por NFS

Y ahora probamos desde el router:

1curl http://192.168.200.3

Página servida correctamente al hacer curl desde el router al servidor web

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

Página web recibida correctamente vía DNAT al hacer curl desde la instancia OpenStack

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:

Contenido de /etc/hosts del host físico, con la entrada data.josemanuel.org apuntando a la IP flotante

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

Navegador mostrando la página por IP flotante, con el nombre José Manuel Sierra Aroca

Navegador mostrando la misma página accediendo por el nombre data.josemanuel.org

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

Confirmación de que nas-josemanuel está apagada, mientras router y web siguen corriendo

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

Disco de datos redimensionado a 2 GiB, confirmado con qemu-img info

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

fdisk -l mostrando el disco vdb ya con 2048 MB

Ahora redimensionamos también el sistema de archivos. Instalamos el paquete necesario:

1sudo apk add e2fsprogs-extra
2sudo resize2fs /dev/vdb

resize2fs ampliando el sistema de archivos de /dev/vdb en caliente

Miramos el espacio disponible:

1df -h | grep /srv/data

Espacio disponible en /srv/data ya reflejando los 2 GB

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

Listado de snapshots de las tres máquinas, todos en estado running