Plantillas, clonación, snapshots y cloud-init
Plantillas, clonación, snapshots y cloud-init
Instalar Debian desde la ISO cuesta unos minutos de asistente, un particionado, un apt update y la configuración de SSH. Hacerlo una vez es aceptable. Hacerlo doce veces porque necesitas doce nodos de un cluster de aplicación es una forma cara de perder la tarde, y además garantiza que las doce máquinas serán ligeramente distintas entre sí: la que instalaste el martes tiene otra versión de paquetes, en otra te olvidaste de desactivar el login por contraseña.
En el capítulo 3 montaste el hardware virtual pieza a pieza y en el capítulo 4 hiciste lo propio con Windows. Todo eso partía de una ISO y de un instalador interactivo. Aquí cambias el modelo: partes de una imagen cloud que ya viene instalada, la conviertes en una plantilla de solo lectura, y cada máquina nueva es un qm clone de unos segundos que se auto-configura en su primer arranque con cloud-init. Hostname, usuario, claves SSH, IP y paquetes iniciales llegan como datos, no como clics.
Al terminar el capítulo tendrás una plantilla Debian 13 funcional en el VMID 9000, un procedimiento de despliegue de una sola línea para cada VM nueva, snippets de cloud-init propios para lo que la configuración estándar no cubre, y el criterio para saber cuándo un snapshot te va a salvar y cuándo te va a bloquear la VM durante horas.
Todo lo que sigue está verificado contra Proxmox VE 9.2 sobre Debian 13 Trixie, con QEMU 11.0.
El ciclo completo, de un vistazo
Antes de los detalles conviene tener el recorrido entero en la cabeza. Son dos fases muy distintas: construir la plantilla se hace una vez, y desplegar desde ella se hace tantas veces como máquinas necesites.
flowchart TD
A["Descargar imagen cloud oficial"] --> B["qm create 9000 sin disco"]
B --> C["qm set --scsi0 con import-from"]
C --> D["qm set --ide2 storage:cloudinit"]
D --> E["qm set --boot order=scsi0"]
E --> F["qm set --serial0 socket --vga serial0"]
F --> G["qm template 9000"]
G --> H["qm clone 9000 123 --name web-01"]
H --> I["qm set 123 --ciuser --sshkeys --ipconfig0"]
I --> J["qm resize 123 scsi0 +30G"]
J --> K["qm start 123"]
K --> L["cloud-init lee el ISO y configura el sistema"]
L --> M["VM lista con IP y acceso SSH"]
La caja qm template 9000 es la frontera. A la izquierda editas; a la derecha solo clonas.
Full clone frente a linked clone
Proxmox VE tiene dos formas de copiar una VM y la diferencia no es de matiz: cambia el espacio ocupado, la velocidad y, sobre todo, si el origen se puede borrar después.
Un full clone lee y copia todos los datos de la imagen. La documentación es explícita:
“A full clone needs to read and copy all VM image data. This is usually much slower than creating a linked clone.”
Un linked clone no copia nada: crea un volumen nuevo que referencia la imagen original como base y solo escribe las diferencias (copy-on-write). Por eso es casi instantáneo y ocupa cero al principio. El precio es una dependencia permanente.
| Full clone | Linked clone | |
|---|---|---|
| Independencia del origen | Total, no comparte storage | Referencia la imagen original |
| Velocidad de creación | Lento, copia todos los datos | Casi instantáneo |
| Espacio inicial | Igual al origen | Cero |
| Cambiar storage destino | Sí, con --storage | No, es una feature interna del storage |
| Cambiar formato | Sí, con --format, si el driver lo soporta | No |
| Clonar desde un snapshot | Sí, con --snapname, en algunos storages | — |
| Origen admitido | VM normal o plantilla | Solo plantillas |
| ¿Se puede borrar el origen? | Sí | No mientras existan linked clones |
flowchart LR
T["Plantilla 9000 read-only"] --> FC["Full clone 124"]
T --> LC1["Linked clone 121"]
T --> LC2["Linked clone 122"]
FC --> FCD["Volumen propio<br/>copia completa de los datos"]
LC1 --> LCD1["Volumen delta<br/>solo diferencias"]
LC2 --> LCD2["Volumen delta<br/>solo diferencias"]
LCD1 -.depende de.-> T
LCD2 -.depende de.-> T
FCD -.no depende de nada.-> FIN["Se puede borrar la plantilla"]
Dos reglas que evitan sorpresas. Uno. El linked clone requiere un volumen de solo lectura, y eso solo lo garantiza una plantilla: si el origen es una VM normal, Proxmox hace siempre un full clone aunque no lo pidas. Dos. La cita oficial sobre snapshots al clonar dice que la copia final nunca incluye los snapshots adicionales del original:
“Some storage types allows to copy a specific Snapshot, which defaults to the ‘current’ VM data. This also means that the final copy never includes any additional snapshots from the original VM.”
Gotcha: un linked clone es maravilloso para laboratorios efímeros y una trampa para producción a largo plazo. Mientras exista un solo linked clone no puedes eliminar la plantilla, y si algún día quieres retirar esa plantilla tendrás que convertir cada clon en full clone antes (moviendo su disco a otro storage con qm disk move).
Convertir una VM en plantilla y qué implica ser read-only
El comando es corto:
qm template 9000
qm template 9000 --disk scsi0 # convertir solo un disco a imagen base
Después de eso, en /etc/pve/qemu-server/9000.conf aparece la línea template: 1 y la VM deja de poder arrancar. Cita literal:
“It is not possible to start templates, because this would modify the disk images.”
En storages basados en fichero el cambio es visible en el propio filesystem: el backend renombra el volumen de vm-9000-disk-0.qcow2 a base-9000-disk-0.qcow2, cambia el modo de acceso a 0444 y aplica el flag inmutable con chattr +i si el storage lo soporta. No es una convención cosmética: es lo que hace seguro que veinte linked clones apunten al mismo bloque de datos.
Consecuencia práctica: una plantilla no se edita. Si quieres actualizarla (nuevos paquetes, otro sshd_config), el procedimiento oficial es crear un linked clone, arrancarlo, modificarlo y volver a plantillar ese clon con un VMID nuevo. Por eso conviene versionar las plantillas por VMID: 9000 para Debian 13 de agosto, 9001 para la revisión siguiente, y así.
Advertencia: una plantilla sí ocupa espacio y sí entra en los backups si no la excluyes. Revisa tus jobs de vzdump cuando acumules plantillas viejas; lo verás en detalle en el capítulo 13.
qm clone: sintaxis completa y qué reescribe Proxmox
qm clone <vmid> <newid> [OPTIONS]
| Opción | Default | Notas |
|---|---|---|
--full <boolean> | — | Copia completa. Siempre se hace full si el origen es una VM normal. Desde una plantilla se intenta linked por defecto |
--name <string> | — | Nombre de la VM nueva |
--description <string> | — | Descripción, se guarda como comentario en la config |
--pool <string> | — | Añadir a un resource pool |
--storage <storage ID> | — | Storage destino. Solo válido para full clone |
--format <qcow2|raw|vmdk> | — | Solo full clone. Si el storage no lo soporta, usa su formato por defecto |
--snapname <string> | — | Clonar desde un snapshot concreto |
--target <string> | — | Nodo destino. Solo si el original está en shared storage disponible allí |
--bwlimit <integer> | límite de clone del datacenter o del storage | KiB/s |
# linked clone rápido desde plantilla
qm clone 9000 123 --name web-01
# full clone a otro storage con conversión de formato y límite de banda
qm clone 9000 124 --name web-02 --full 1 --storage ceph-pool --format raw --bwlimit 200000
# clon desde un snapshot concreto
qm clone 100 130 --name rollback-test --full 1 --snapname pre-update
Lo que Proxmox reescribe por ti al clonar, y que resuelve dos clases enteras de bugs de red:
Uno. Aleatoriza todas las direcciones MAC de las interfaces de red. Sin esto, dos clones en el mismo bridge se pelearían por la misma MAC y verías pérdidas de paquetes intermitentes imposibles de diagnosticar desde dentro del huésped.
Dos. Genera un UUID de BIOS nuevo en la clave smbios1. Muchos sistemas de inventario, licenciamiento y, sobre todo, cloud-init usan ese UUID como identidad de instancia.
Lo que Proxmox no reescribe está dentro del disco: el machine-id de systemd, las claves de host SSH y el estado de cloud-init. Eso lo tienes que limpiar tú antes de plantillar, y tiene sección propia más abajo.
Imágenes cloud verificadas: Debian y Ubuntu
Una imagen cloud es un disco con el sistema ya instalado, sin usuario ni contraseña útiles, con cloud-init dentro esperando datos. No lleva instalador. Se importa y arranca.
Debian 13 Trixie — https://cloud.debian.org/images/cloud/trixie/latest/
| Variante | Fichero | Cuándo usarla |
|---|---|---|
generic | debian-13-generic-amd64.qcow2 | La recomendada para Proxmox VE. Trae soporte para varios entornos cloud y cloud-init |
genericcloud | debian-13-genericcloud-amd64.qcow2 | Más pequeña. Asume virtualización, no sirve para bare metal |
nocloud | debian-13-nocloud-amd64.qcow2 | NO trae cloud-init. Login root sin contraseña, para pruebas locales |
Cada variante existe también en .raw y en arm64, y el directorio publica SHA512SUMS para verificación.
Gotcha: el nombre nocloud induce a error. No significa “cloud-init en modo NoCloud”, significa imagen sin cloud-init. Si la usas por descuido, la VM arranca y no aplica nada de lo que configures: ni usuario, ni claves, ni IP. Es la causa número uno de “cloud-init no hace nada” en foros.
Ubuntu — https://cloud-images.ubuntu.com/<codename>/current/. Para Noble 24.04 LTS: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img. El fichero termina en .img pero es un qcow2; qemu-img info lo confirma y import-from lo importa sin conversión manual.
Nota de la documentación: “Ubuntu Cloud-Init images require the
virtio-scsi-pcicontroller type for SCSI drives.”
Ese detalle es importante y contradice lo que la GUI hace por defecto en VMs Linux nuevas desde PVE 7.3, que es virtio-scsi-single. Para imágenes cloud de Ubuntu usa --scsihw virtio-scsi-pci. Si te preguntas qué pierdes: iothread en discos SCSI solo funciona con virtio-scsi-single, así que en Ubuntu cloud te quedas sin esa optimización salvo que cambies el controlador después de comprobar que la imagen arranca.
Construir la plantilla paso a paso con import-from
Esta es la secuencia verificada, comando a comando. Ejecútala en la shell del nodo.
# 1) Descargar la imagen cloud
wget https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2
# 2) Crear la VM sin disco todavía
qm create 9000 --name debian13-cloud \
--memory 2048 --cores 2 \
--net0 virtio,bridge=vmbr0 \
--scsihw virtio-scsi-pci \
--ostype l26 --agent enabled=1
# 3) Importar el disco y adjuntarlo como scsi0 en un solo paso
qm set 9000 --scsi0 local-lvm:0,import-from=/root/debian-13-generic-amd64.qcow2,discard=on,ssd=1
# 4) Añadir la unidad CD-ROM de cloud-init
qm set 9000 --ide2 local-lvm:cloudinit
# 5) Arrancar solo desde el disco
qm set 9000 --boot order=scsi0
# 6) Consola serie como display
qm set 9000 --serial0 socket --vga serial0
# 7) Convertir en plantilla
qm template 9000
Cinco detalles que explican por qué cada línea está donde está.
Uno. local-lvm:0 con import-from= es el método moderno: el 0 significa “el tamaño lo dicta la imagen origen”. El método clásico en dos pasos sigue funcionando y a veces es más claro cuando quieres inspeccionar el volumen antes de asignarlo:
qm importdisk 9000 debian-13-generic-amd64.qcow2 local-lvm
# deja el volumen como unused0; luego:
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
Con --target-disk te saltas el paso intermedio:
qm disk import 9000 debian-13-generic-amd64.qcow2 local-lvm --target-disk scsi0
La sintaxis completa es qm disk import <vmid> <source> <storage> [--format <qcow2|raw|vmdk>] [--target-disk <scsi0|...>], y el formato de origen tiene que estar soportado por qemu-img(1).
Dos. discard=on,ssd=1 en el disco importa aunque la plantilla sea pequeña: los clones heredan esas opciones y sin ellas el TRIM del huésped nunca llega al storage. Recuerda del capítulo 3 que ssd no está soportado en virtio[n], solo en IDE, SATA y SCSI.
Tres. --agent enabled=1 en la plantilla es gratis y ahorra un qm set por clon. Ojo: la clave agent solo declara que Proxmox debe hablar con el agente; el paquete qemu-guest-agent tiene que estar dentro del huésped. Las imágenes cloud de Debian y Ubuntu no lo traen instalado, así que lo añadirás por cloud-init.
Cuatro. --boot order=scsi0 no es cosmético: sin él el firmware busca un CD booteable primero y el arranque de cada clon se alarga sin motivo.
Cinco. El qm template va al final, cuando ya no queda nada por editar. Después ya no hay marcha atrás sin crear un clon.
Verifica el resultado leyendo la config:
qm config 9000
agent: enabled=1
boot: order=scsi0
cores: 2
ide2: local-lvm:vm-9000-cloudinit,media=cdrom
memory: 2048
meta: creation-qemu=11.0.0,ctime=1786000000
name: debian13-cloud
net0: virtio=BC:24:11:AA:BB:CC,bridge=vmbr0
ostype: l26
scsi0: local-lvm:base-9000-disk-0,discard=on,size=2G,ssd=1
scsihw: virtio-scsi-pci
serial0: socket
smbios1: uuid=6f2b1e0a-4d3c-4b2a-9c1f-8e7d6a5b4c3d
sockets: 1
template: 1
vga: serial0
vmgenid: 3a7f1c22-9b8e-4d5a-8f6c-2e1d0b9a8c7f
Fíjate en base-9000-disk-0: el prefijo base- confirma que la conversión a plantilla se completó.
El drive de cloud-init y la consola serie: por qué no son opcionales
Proxmox VE genera un ISO con los datos de cloud-init y se lo presenta a la VM como un CD-ROM. De ahí salen las dos exigencias que más veces se olvidan.
Toda VM con cloud-init necesita una unidad CD-ROM asignada, ide2 por convención. Sin ella no hay dónde montar el ISO y cloud-init dentro del huésped no encuentra su fuente de datos: arranca, busca, no ve nada y deja el sistema tal cual salió de la imagen.
Normalmente también hace falta una consola serie usada como display. Muchas imágenes cloud dependen de ello porque es un requisito heredado de OpenStack. Si una imagen concreta no funciona con display serial, se vuelve al display por defecto (--vga std), pero el orden correcto es probar primero con serie.
Recomendación oficial sobre credenciales: usa claves SSH, no contraseña, porque Proxmox tiene que almacenar una versión cifrada de la contraseña dentro de los datos de cloud-init, y esos datos viven en /etc/pve replicados por pmxcfs (revisa el capítulo 2 si quieres recordar dónde acaba cada cosa).
Desplegar desde la plantilla
Con la plantilla lista, cada máquina nueva son seis líneas. Este es el flujo canónico:
qm clone 9000 123 --name web-01
qm set 123 --sshkeys ~/.ssh/id_rsa.pub
qm set 123 --ipconfig0 ip=10.0.10.123/24,gw=10.0.10.1
qm set 123 --nameserver 10.0.10.1 --searchdomain lab.local
qm set 123 --ciuser admin
qm resize 123 scsi0 +30G
qm start 123
Gotcha de nomenclatura: el wiki oficial escribe qm set 123 --sshkey ~/.ssh/id_rsa.pub, en singular, pero la opción canónica de la CLI es --sshkeys, con s. La forma corta funciona por la abreviación automática de Getopt::Long de Perl, no porque exista. En scripts, en Ansible o en cualquier envoltorio que valide opciones contra el esquema del API, escribe siempre --sshkeys. En la config el valor se guarda url-encoded bajo la clave sshkeys.
Sobre qm resize: las imágenes cloud vienen con discos deliberadamente pequeños (2 GB en Debian generic). Ampliarlas es normal. La sintaxis acepta incremento o tamaño absoluto:
qm disk resize 123 scsi0 +30G # añade 30 GiB
qm disk resize 123 scsi0 100G # tamaño absoluto
Reducir el tamaño no está soportado. Y aunque cloud-init amplía la partición raíz por sí solo en las imágenes oficiales, si alguna vez tienes que hacerlo a mano dentro de un huésped Linux:
growpart /dev/sda 1 && resize2fs /dev/sda1 # ext4
growpart /dev/sda 1 && xfs_growfs / # xfs
pvresize /dev/sda3 && lvextend -r -l +100%FREE /dev/vg/lv # LVM
El ejemplo de la propia man page de qm es exactamente este patrón, con Ubuntu:
qm clone 9000 123 --name ubuntu2
qm set 123 --ipconfig0 ip=10.0.10.123/24,gw=10.0.10.1
Referencia completa de las opciones cloud-init
| Clave | Sintaxis / valores | Default | Qué hace |
|---|---|---|---|
ciuser | string | — | Usuario al que se aplican claves y contraseña, en lugar del usuario por defecto de la imagen |
cipassword | string | — | Contraseña. “Using this is generally not recommended. Use ssh keys instead”. Las versiones antiguas de cloud-init no soportan hashes |
sshkeys | una clave por línea, formato OpenSSH; en CLI se pasa un <filepath> | — | Claves públicas autorizadas |
ipconfig[n] | [gw=][,gw6=][,ip=][,ip6=] | ip=dhcp | Configuración IP por interfaz net[n] |
nameserver | string | el del host | Servidores DNS |
searchdomain | string | el del host | Dominios de búsqueda |
citype | configdrive2 | nocloud | opennebula | según ostype | nocloud en Linux, configdrive2 en Windows |
ciupgrade | boolean | 1 | ”do an automatic package upgrade after the first boot” |
cicustom | [meta=][,network=][,user=][,vendor=] | — | Ficheros propios que reemplazan a los generados |
Detalle de la herencia de DNS: “Create will automatically use the setting from the host if neither searchdomain nor nameserver are set”. Es decir, basta con definir uno de los dos para que el otro deje de heredarse del host. Si defines solo nameserver, el searchdomain desaparece.
Reglas de ipconfig[n] que conviene tener memorizadas:
- Las direcciones van en notación CIDR.
ip=dhcppara DHCP, y en ese caso no se pone gateway explícito.ip6=autopara autoconfiguración sin estado; requiere cloud-init >= 19.4 en el huésped.gwrequiereip;gw6requiereip6.- Si cloud-init está habilitado y no indicas ni IPv4 ni IPv6, por defecto usa DHCP en IPv4.
qm set 123 --ipconfig0 ip=dhcp
qm set 123 --ipconfig0 ip=192.168.1.50/24,gw=192.168.1.1,ip6=auto
qm set 123 --ipconfig0 ip=10.0.0.5/24,gw=10.0.0.1 --ipconfig1 ip=172.16.0.5/24
Sobre ciupgrade: 1: el default hace un apt upgrade en el primer arranque. Es cómodo en un laboratorio y discutible en producción, porque significa que dos clones creados con una semana de diferencia no tienen el mismo software. Si quieres reproducibilidad estricta, ponlo a 0 y controla las versiones desde tu pipeline de configuración.
El primer arranque, secuencia real
sequenceDiagram
participant OP as Operador
participant PVE as Proxmox VE
participant Q as QEMU
participant CI as cloud-init en el guest
OP->>PVE: qm start 123
PVE->>PVE: Genera el ISO de cloud-init
PVE->>Q: Arranca la VM con ide2 montado
Q->>CI: Boot del kernel y systemd
CI->>Q: Busca la fuente de datos NoCloud en el CD-ROM
CI->>CI: Lee user-data network-config y meta-data
CI->>CI: Aplica hostname usuario y claves SSH
CI->>CI: Aplica la IP y el DNS
CI->>CI: Ejecuta packages y runcmd
CI->>Q: power_state reboot si el YAML lo pide
Q-->>OP: VM accesible por SSH
Un matiz que ahorra confusión: cloud-init distingue entre módulos que corren una sola vez por instancia y módulos que corren en cada arranque. La identidad de instancia se deriva del UUID de SMBIOS, que Proxmox regenera al clonar. Por eso un clon nuevo ejecuta todo el ciclo, y un reinicio posterior de la misma VM no vuelve a crear usuarios.
Snippets: cicustom y sus trampas
Las opciones estándar cubren usuario, claves e IP. Para todo lo demás (paquetes, ficheros, servicios, comandos) necesitas snippets: ficheros YAML tuyos que Proxmox entrega tal cual al huésped.
Requisitos, los tres:
- El storage debe tener habilitado el content type
snippets. - El fichero debe estar disponible en todos los nodos a los que la VM pueda migrar, “otherwise the VM won’t be able to start”.
- La ruta típica del storage
locales/var/lib/vz/snippets/.
Habilitarlo en /etc/pve/storage.cfg:
dir: local
path /var/lib/vz
content iso,vztmpl,backup,snippets
O sin editar el fichero a mano:
pvesm set local --content iso,vztmpl,backup,snippets
Uso:
qm set 9000 --cicustom "user=local:snippets/userconfig.yaml"
qm set 9000 --cicustom "user=local:snippets/user.yaml,network=local:snippets/net.yaml"
Hay cuatro tipos de configuración: user, network, meta y vendor. Se combinan libremente y para los que no especifiques un fichero se usa el generado automáticamente por Proxmox.
Advertencia: un cicustom con user= sustituye por completo al user-data generado a partir de ciuser, cipassword y sshkeys. No se fusionan. Si pones un snippet de usuario y sigues esperando que --sshkeys funcione, la VM arrancará sin tus claves. Los usuarios y las claves tienen que estar dentro del YAML.
Un snippet realista en /var/lib/vz/snippets/userconfig.yaml:
#cloud-config
hostname: web-01
manage_etc_hosts: true
users:
- name: admin
groups: [sudo]
sudo: ['ALL=(ALL) NOPASSWD:ALL']
shell: /bin/bash
ssh_authorized_keys:
- ssh-ed25519 AAAA... admin@lab
package_update: true
packages:
- qemu-guest-agent
- htop
runcmd:
- systemctl enable --now qemu-guest-agent
Ese qemu-guest-agent en packages es la pieza que faltaba: la clave agent: enabled=1 de la plantilla queda por fin respaldada por un agente real dentro del huésped, y a partir del segundo arranque qm shutdown funciona limpio y el Summary de la GUI muestra las IPs.
Gotcha operativo: el snippet tiene el hostname escrito a fuego. Para un despliegue de varias VMs necesitas o un snippet por máquina, o generar el YAML desde una plantilla en tu pipeline. Ese es exactamente el punto donde Terraform y Ansible entran a jugar, y lo verás en el capítulo 14.
Inspección y depuración de cloud-init
Tres comandos que resuelven la mayoría de los “no aplica nada”.
qm cloudinit dump 9000 user # el YAML exacto de user-data que recibirá el guest
qm cloudinit dump 9000 network # la configuración de red generada
qm cloudinit dump 9000 meta # los metadatos de instancia
qm cloudinit pending 123 # valores actuales y pendientes
qm cloudinit update 123 # regenera el drive sin reiniciar la VM
qm cloudinit dump es además la mejor forma de escribir un snippet: vuelcas el user-data generado, lo guardas como base y lo editas, en lugar de escribir YAML desde cero.
qm cloudinit dump 9000 user > /var/lib/vz/snippets/userconfig.yaml
qm cloudinit update merece su párrafo. Cuando cambias --ipconfig0 en una VM ya creada, Proxmox no regenera el ISO automáticamente en todos los casos: el clásico “cambié la IP y sigue con la vieja” se arregla con este comando seguido de un reinicio del huésped. Y desde PVE 9.2 volcar contraseñas de cloud-init exige el privilegio VM.Config.Cloudinit, un detalle que importa si delegas la operación en usuarios no root (lo verás en el capítulo 10).
Limpieza obligatoria antes de plantillar
Si en lugar de partir de una imagen cloud construyes tu propia plantilla desde una VM instalada a mano (caso legítimo: quieres una base con tu agente de monitoreo, tus certificados y tu hardening), hay que limpiar la identidad de la máquina dentro del huésped antes de apagarla y plantillarla:
cloud-init clean --logs --seed
rm -f /etc/machine-id && touch /etc/machine-id
rm -f /var/lib/dbus/machine-id
rm -f /etc/ssh/ssh_host_*
Qué hace cada línea y qué rompe si la saltas:
Uno. cloud-init clean --logs --seed borra el estado de instancia y los logs. Sin esto, cloud-init cree que ya se ejecutó y no vuelve a aplicar nada en los clones.
Dos. Vaciar /etc/machine-id (borrar y crear vacío, no borrar a secas: systemd espera que el fichero exista) fuerza a systemd a generar uno nuevo en el primer arranque. Si no lo limpias, todos los clones piden la misma IP por DHCP, porque el cliente DHCP moderno de systemd deriva su identificador del machine-id. Es un fallo especialmente desconcertante: las VMs arrancan bien de una en una y se pisan cuando arrancan varias.
Tres. /var/lib/dbus/machine-id es habitualmente un enlace al anterior, pero en algunas distribuciones es un fichero independiente; borrarlo evita inconsistencias.
Cuatro. Borrar /etc/ssh/ssh_host_* obliga a regenerar las claves de host. Sin esto, veinte máquinas distintas presentan la misma huella SSH, lo que hace inútil la verificación de host y dispara avisos de known_hosts en cuanto reutilices una IP.
Después de eso, apaga el huésped, y ya en el nodo: qm template <vmid>.
Snapshots: con RAM y sin RAM
Un snapshot congela el estado de una VM para poder volver a él. En Proxmox VE se maneja con cuatro comandos y una opción que lo cambia todo.
qm snapshot 100 pre-update
qm snapshot 100 pre-update --description "antes de apt full-upgrade" --vmstate 1
qm listsnapshot 100
qm rollback 100 pre-update
qm rollback 100 pre-update --start 1
qm delsnapshot 100 pre-update
qm delsnapshot 100 pre-update --force 1 # quitarlo de la config aunque falle el borrado en disco
qm config 100 --snapshot pre-update # leer la config guardada dentro del snapshot
La opción que lo cambia todo es --vmstate:
Sin RAM (--vmstate 0) | Con RAM (--vmstate 1) | |
|---|---|---|
| Qué guarda | Estado de los discos y la config | Discos, config, memoria y estado de dispositivos |
| Al hacer rollback | La VM queda apagada, salvo --start 1 | La VM se reanuda exactamente donde estaba |
| Tiempo y espacio | Rápido, poco espacio | Proporcional a la RAM asignada |
| Freeze del filesystem | Sí, si el guest agent está activo | No hace falta |
| Fija la machine version | No | Sí, en runningmachine |
Nota de la documentación sobre el rollback: “VMs will be automatically started if the snapshot includes RAM.”
El detalle del freeze conecta con el capítulo 4: un snapshot sin RAM de una VM en ejecución emite guest-fsfreeze-freeze si tienes agent enabled=1 y freeze-fs=1. Eso es lo que hace que el snapshot sea consistente a nivel de sistema de archivos. También es lo que rompe la cadena de backups diferenciales en algunos SQL Server, con las dos soluciones que ya conoces.
Dónde se guarda el estado de RAM
Cuando pides --vmstate 1 y no indicas destino, Proxmox elige el storage con este orden exacto (el mismo algoritmo que usa la hibernación con qm suspend --todisk):
- El storage
vmstatestoragede la config de la VM. - El primer storage compartido de cualquier disco de la VM.
- El primer storage no compartido de cualquier disco.
- El storage
localcomo último recurso.
Gotcha: el paso 4 es el que llena /var/lib/vz sin avisar. Una VM con 64 GB de RAM y tres snapshots con estado consume 192 GB en el disco raíz del nodo. Si vas a usar snapshots con RAM de forma habitual, fija vmstatestorage explícitamente:
qm set 100 --vmstatestorage local-zfs
Qué storages soportan snapshots de verdad
Esta es la tabla oficial de tipos de storage. La columna que importa aquí es Snapshots.
| Descripción | Plugin | Nivel | Shared | Snapshots |
|---|---|---|---|---|
| ZFS local | zfspool | ambos | no | sí |
| Directory | dir | fichero | no | sí, solo qcow2 |
| BTRFS | btrfs | fichero | no | sí (technology preview) |
| NFS | nfs | fichero | sí | sí, solo qcow2 |
| CIFS | cifs | fichero | sí | sí, solo qcow2 |
| Proxmox Backup | pbs | ambos | sí | n/a |
| CephFS | cephfs | fichero | sí | sí |
| LVM | lvm | bloque | no | sí, vía volume chains desde PVE 9 |
| LVM-thin | lvmthin | bloque | no | sí |
| iSCSI kernel | iscsi | bloque | sí | sí, vía volume chains desde PVE 9 |
| iSCSI libiscsi | iscsidirect | bloque | sí | sí, vía volume chains desde PVE 9 |
| FC / SAS | nativo | bloque | sí | sí, vía volume chains desde PVE 9 |
| Ceph RBD | rbd | bloque | sí | sí |
| ZFS over iSCSI | zfs | bloque | sí | sí |
Y la regla transversal, literal de la documentación: “All storage types which have the ‘Snapshots’ feature also support thin provisioning.” Es un atajo mental útil: si un storage sabe hacer snapshots, sabe hacer thin provisioning, y por tanto discard=on en los discos tiene sentido en él.
El coste real de qcow2 sobre NFS
La nota al pie de esa tabla es de las más importantes de toda la documentación de Proxmox, y merece leerse entera:
“On file based storages, snapshots are possible with the qcow2 format, either using the internal snapshot function, or snapshots as volume chains. Creating and deleting internal qcow2 snapshots will block a running VM and is not an efficient operation. The performance is particularly bad with network storages like NFS. On some setups and for large disks (multiple hundred GiB or TiB sized), these operations may take several minutes, or in extreme cases, even hours. If your setup is affected, create and remove snapshots while the VM is shut down, expecting a long task duration.”
Traducido a operación: si tu storage es NFS y tus discos son de cientos de GiB, no hagas snapshots de VMs encendidas. La VM se queda bloqueada mientras dura la operación y no hay forma de acelerarla. Si necesitas puntos de recuperación frecuentes sobre NFS, la respuesta correcta no son los snapshots sino los backups incrementales a Proxmox Backup Server.
Snapshots como cadena de volúmenes en PVE 9
La novedad estructural de Proxmox VE 9 es snapshot-as-volume-chain, que lleva snapshots a LVM thick, iSCSI, FC y SAS. En lugar de un snapshot nativo del storage, cada snapshot persiste el estado actual bajo su nombre y arranca un volumen nuevo respaldado por él; el volumen nuevo referencia al anterior como backing file y solo registra las diferencias. Los volúmenes de snapshot son LVM logical volumes thick-provisioned.
# storage nuevo
pvesm add lvm san-lvm --vgname san-vg --shared 1 --content images \
--snapshot-as-volume-chain 1
# storage existente: solo afecta a volúmenes NUEVOS
pvesm set san-lvm --snapshot-as-volume-chain 1
Advertencia: la documentación lo etiqueta como technology preview. El disco tiene que estar en qcow2, la machine version debe ser >= 10, y “enabling or disabling this flag only affects newly created virtual disk volumes”. No lo pongas debajo de una carga de producción sin haberlo probado con tus datos. El detalle completo de backends y de cuándo elegir cada uno está en el capítulo 7, y el modelo de snapshots nativo de ZFS en el capítulo 8.
Árbol de decisión
flowchart TD
A["Quiero un snapshot de la VM"] --> B{"El storage tiene la feature Snapshots"}
B -->|No| C["Usa un backup vzdump o cambia de storage"]
B -->|Sí| D{"Storage de bloque o de fichero"}
D -->|Bloque ZFS LVM-thin RBD| E["Snapshot nativo rápido"]
D -->|Fichero dir NFS CIFS| F{"El disco está en qcow2"}
F -->|No| G["Convertir con qm disk move --format qcow2"]
F -->|Sí| H{"Disco grande o storage de red"}
H -->|Sí| I["Apagar la VM antes<br/>puede tardar minutos u horas"]
H -->|No| E
E --> J{"Necesitas volver al estado exacto en RAM"}
J -->|Sí| K["--vmstate 1<br/>revisa vmstatestorage"]
J -->|No| L["Snapshot de disco<br/>freeze del FS vía guest agent"]
Formato de los snapshots dentro de VMID.conf
Los snapshots no viven en un fichero aparte: se guardan como secciones dentro del mismo /etc/pve/qemu-server/<VMID>.conf. Cada sección [nombre] es una copia completa de la configuración de la VM en el momento del snapshot.
memory: 512
swap: 512
parent: testsnaphot
...
[testsnaphot]
memory: 512
swap: 512
snaptime: 1457170803
...
Las claves específicas de snapshot:
| Clave | Significado |
|---|---|
parent | Relación padre/hijo entre snapshots. Es lo que forma el árbol |
snaptime | Timestamp Unix de creación |
runningmachine | Machine version fijada. Solo aparece en snapshots con RAM |
vmstate | Volumen donde está el estado de memoria |
Ninguna de estas claves se escribe a mano: son claves runtime que gestiona qm. La clave parent en el nivel superior del fichero indica de qué snapshot desciende el estado actual, y por eso al hacer rollback cambia.
Gotcha: no se puede cambiar la machine version de un snapshot. Si tienes snapshots antiguos con runningmachine por debajo del baseline de PVE 9 (machine version 6.0), esos snapshots quedan atados a esa versión. Borrarlos es a veces la única salida limpia cuando actualizas el cluster.
Los snapshots también interactúan con los locks. Un corte de luz durante un qm snapshot deja la VM con lock: snapshot en la config y todo bloqueado:
qm unlock 100
“CAUTION: Only do that if you are sure the action which set the lock is no longer running.”
Valores posibles de lock: backup | clone | create | migrate | rollback | snapshot | snapshot-delete | suspended | suspending.
Errores comunes y diagnóstico
| Síntoma | Causa probable | Diagnóstico o solución |
|---|---|---|
| Cloud-init no aplica absolutamente nada | Falta el drive ide2:...:cloudinit, falta la consola serie, o la imagen es la variante nocloud | qm config <vmid> y comprobar ide2 y serial0; usar la imagen generic, no nocloud |
| Cloud-init aplica la configuración vieja | El ISO no se regeneró tras el cambio | qm cloudinit update <vmid> y reiniciar el huésped |
| Un clon de Ubuntu no arranca | scsihw incorrecto | Las imágenes cloud de Ubuntu exigen virtio-scsi-pci, no virtio-scsi-single |
cicustom hace que la VM no arranque | El snippet no está en todos los nodos, o el storage no tiene content type snippets | Añadir snippets en /etc/pve/storage.cfg; usar un storage compartido |
Puse un snippet user= y perdí mis claves SSH | cicustom user= sustituye al user-data generado | Meter usuarios y ssh_authorized_keys dentro del YAML del snippet |
| Todos los clones piden la misma IP por DHCP | No se limpió /etc/machine-id antes de plantillar | rm -f /etc/machine-id && touch /etc/machine-id en el guest y volver a plantillar |
| Todas las VMs presentan la misma huella SSH | No se borraron las claves de host | rm -f /etc/ssh/ssh_host_* antes de plantillar |
qm set --sshkey falla en un script | La opción canónica es --sshkeys | Usar --sshkeys <filepath> siempre |
| No puedo borrar la plantilla | Existen linked clones que dependen de ella | qm disk move de cada clon a otro storage para convertirlo en independiente, o borrar los clones |
| No puedo arrancar la plantilla | Es read-only por diseño | Crear un linked clone y arrancar ese |
| No aparece la opción Snapshot en la GUI | El storage no tiene la feature Snapshots | Revisar la tabla de storages; usar LVM-thin, ZFS, RBD o qcow2 |
| El snapshot tarda minutos u horas | qcow2 internal snapshot sobre NFS con discos grandes | Comportamiento documentado. Hacer snapshots con la VM apagada o cambiar de tipo de storage |
The current guest configuration does not support taking new snapshots | Falta snapshot-as-volume-chain 1, el disco no es qcow2, o la machine version es < 10 | pvesm set <storage> --snapshot-as-volume-chain 1; convertir el disco a qcow2 |
/var/lib/vz se llena tras varios snapshots | El estado de RAM cayó en el storage local por el paso 4 del algoritmo | qm set <vmid> --vmstatestorage <storage> |
VM bloqueada con lock: snapshot tras un corte de luz | Lock huérfano | qm unlock <vmid>, solo si la tarea realmente no está corriendo |
| El guest agent no responde en los clones | La clave agent está puesta pero el paquete no está dentro del huésped | Añadir qemu-guest-agent en packages del snippet; qm agent <vmid> ping para verificar |
| El disco del clon sigue con 2 GB | Las imágenes cloud vienen pequeñas | qm disk resize <vmid> scsi0 +30G; reducir no está soportado |
Comandos de diagnóstico transversales para esta parte:
qm config <vmid> # config con los cambios pendientes aplicados
qm config <vmid> --current 1 # config actual, sin pendientes
qm pending <vmid> # qué cambios esperan un reinicio
qm cloudinit dump <vmid> user # el YAML real que verá el guest
qm listsnapshot <vmid> # árbol de snapshots
pvesm list local --content snippets # snippets visibles en el storage
journalctl -u pvedaemon -u pvestatd -f
Y dentro del huésped, cuando cloud-init hizo algo raro:
cloud-init status --long
cat /var/log/cloud-init.log
cat /var/log/cloud-init-output.log
Lo que queda operativo
Tienes una plantilla Debian 13 en el VMID 9000 con disco importado, drive de cloud-init, consola serie y guest agent declarado; sabes desplegar una VM nueva con un qm clone seguido de tres qm set, y por qué cada uno de esos ajustes existe. Sabes distinguir un linked clone de un full clone y qué te ata cada uno. Tienes snippets propios para lo que las opciones estándar no cubren, y el procedimiento de limpieza que impide que veinte clones compartan identidad. Y tienes un criterio claro sobre snapshots: cuáles son baratos, cuáles bloquean la VM y dónde acaba el estado de RAM si no lo diriges tú.
Este mismo problema (no instalar a mano, partir de una base y personalizar en el arranque) existe también en el mundo de los contenedores, pero con piezas distintas: no hay imágenes cloud ni cloud-init, hay plantillas de pveam, un modelo de permisos con mapeo de UIDs y un conjunto de features que decides al crear. Eso es lo que verás en el capítulo 6.