Almacenamiento: el modelo de plugins y todos los backends
Almacenamiento: el modelo de plugins y todos los backends
Hasta aquí has creado máquinas virtuales en el capítulo 3, plantillas cloud-init en el capítulo 5 y contenedores en el capítulo 6, y en todos los casos escribiste local-lvm o local en un desplegable sin pensarlo demasiado. Ese desplegable es la decisión con más consecuencias de todo Proxmox VE: determina si puedes hacer snapshots, si puedes hacer linked clones, si puedes migrar en vivo, si puedes sobreaprovisionar y qué pasa exactamente el día que el pool se llene.
El síntoma típico llega tarde y llega de golpe. Alguien crea veinte VMs de 100 GB sobre un thin pool de 500 GB, todo funciona durante meses porque cada VM usa 12 GB reales, y una noche un log crece sin rotar, el pool llega al 100 % y todos los guests que viven ahí empiezan a devolver errores de I/O a la vez. O más suave: alguien intenta hacer un snapshot de una VM cuyo disco es raw sobre un dir y la GUI responde con un escueto “the current guest configuration does not support taking new snapshots” sin explicar por qué.
Este capítulo cierra ese hueco. Vas a entender el modelo de plugins que hay debajo de pvesm, la sintaxis exacta de /etc/pve/storage.cfg, qué puede y qué no puede hacer cada backend de Proxmox VE 9.2, la novedad estructural de la rama 9 (snapshots como cadena de volúmenes sobre SAN) y las operaciones de disco que repetirás cada semana: mover, redimensionar, importar, desvincular y recuperar espacio con TRIM. La versión de referencia es Proxmox VE 9.2 sobre Debian 13 Trixie, con la documentación 9.2.4 como fuente.
El modelo de plugins de libpve-storage-perl
La documentación resume la filosofía en una frase:
“The Proxmox VE storage model is very flexible. Virtual machine images can either be stored on one or several local storages, or on shared storage like NFS or iSCSI (NAS, SAN). There are no limits, and you may configure as many storage pools as you like.”
Detrás de esa flexibilidad hay una biblioteca, libpve-storage-perl, que implementa un sistema de plugins. Cada tipo de almacenamiento es un plugin que expone la misma interfaz: reservar un volumen, liberarlo, listarlos, resolver su ruta, hacer un snapshot, clonar, exportar/importar y activar/desactivar. Nada más.
Esto importa por dos razones muy prácticas. Uno. Todo lo que hace la GUI lo hace llamando a la misma API que llama pvesm, qm y pct, tal como viste en el capítulo 2; no hay caminos privilegiados. Dos. Cuando un backend no soporta una operación, no es un capricho de la interfaz: es que el plugin no implementa ese método. Por eso “LVM no tiene linked clones” no se arregla con una opción, se arregla cambiando de backend.
flowchart TD
A["API REST / GUI / pvesm / qm / pct"] --> B["libpve-storage-perl"]
B --> C{"Plugin por tipo"}
C --> D["Nivel fichero: dir / nfs / cifs / btrfs / cephfs"]
C --> E["Nivel bloque: lvm / lvmthin / iscsi / iscsidirect / rbd / zfs"]
C --> F["Ambos niveles: zfspool"]
C --> G["Especiales: pbs / esxi"]
D --> H["Sistema de ficheros POSIX completo"]
E --> I["Dispositivos de bloque con imagenes raw"]
F --> J["zvol para VM y dataset para contenedor"]
G --> K["Backup deduplicado e importacion"]
/etc/pve/storage.cfg: sintaxis, replicación y dos avisos serios
La configuración de todos los storages vive en un único fichero: /etc/pve/storage.cfg. Como está dentro de /etc/pve, es decir dentro de pmxcfs, se replica automáticamente a todos los nodos del cluster. No hay configuración de storage “por nodo”: la hay para todo el cluster, y lo que restringe un storage a ciertos nodos es la propiedad nodes, no el hecho de editarlo en una máquina u otra.
La sintaxis es una lista de bloques:
<type>: <STORAGE_ID>
<property> <value>
<property> <value>
<property>
La primera línea abre la definición con el tipo de plugin y el ID; las propiedades van indentadas debajo. Algunas son booleanas con default razonable y se escriben sin valor, como sparse.
Esto es lo que tienes tras una instalación limpia, literal de la documentación:
dir: local
path /var/lib/vz
content iso,vztmpl,backup
# default image store on LVM based installation
lvmthin: local-lvm
thinpool data
vgname pve
content rootdir,images
# default image store on ZFS based installation
zfspool: local-zfs
pool rpool/data
sparse
content images,rootdir
Fíjate en la coherencia: local no tiene images en su content, porque el instalador reserva los discos de guest para local-lvm o local-zfs, según el filesystem que elegiste en el capítulo 1. Si alguna vez has visto una guía que te dice “añade images a local”, entiende lo que estás haciendo: guardar discos qcow2 dentro del filesystem raíz del nodo.
Dos avisos de la documentación que conviene leer literales, porque los dos se violan a diario:
Caution: “It is problematic to have multiple storage configurations pointing to the exact same underlying storage. Such an aliased storage configuration can lead to two different volume IDs (volid) pointing to the exact same disk image. Proxmox VE expects that the images’ volume IDs point to, are unique.”
Warning: “It is not advisable to use the same storage pool on different Proxmox VE clusters. Some storage operation need exclusive access to the storage, so proper locking is required. While this is implemented within a cluster, it does not work between different clusters.”
Advertencia: el segundo aviso es el que destruye datos. El locking de Proxmox VE es intracluster. Dos clusters distintos apuntando al mismo VG de una SAN no se ven las manos, y nada impide que ambos crean que un LV es suyo.
Content types y las dos clases de storage
Cada storage declara qué tipos de contenido acepta con la propiedad content. Hay siete valores válidos:
| Valor | Qué guarda |
|---|---|
images | Imágenes de disco de VMs QEMU/KVM |
rootdir | Datos de contenedores: rootfs y mount points |
vztmpl | Plantillas de contenedor |
backup | Ficheros de backup de vzdump |
iso | Imágenes ISO |
snippets | Snippets: hook scripts de guest, cloud-init personalizado |
import | OVAs e imágenes de disco importables desde ese storage |
La nota de pvesm add desambigua la confusión más habitual: “the value rootdir is used for Containers, and value images for VMs”. Un storage que solo declara images no puede alojar contenedores aunque técnicamente pudiese.
Y por debajo hay dos clases de storage, que explican casi todas las diferencias de la matriz de features:
File level storage. El plugin ve un sistema de ficheros POSIX completo. Puede guardar cualquier tipo de contenido: ISOs, backups, snippets y discos en raw, qcow2 o vmdk. Es el más flexible. La propia documentación describe ZFS como “probably the most advanced system, and it has full support for snapshots and clones”.
Block level storage. El plugin gestiona dispositivos de bloque y guarda imágenes raw grandes. Normalmente no puede guardar ISOs ni backups. La mayoría de implementaciones modernas soportan snapshots y clones nativos.
La tabla oficial de tipos y la matriz de features
Esta es la Table 1 del capítulo de storage, y es la tabla que deberías tener a mano al diseñar cualquier cluster:
| Descripción | Plugin | Nivel | Shared | Snapshots | Estable |
|---|---|---|---|---|---|
| ZFS local | zfspool | ambos | no | sí | sí |
| Directory | dir | fichero | no | sí, con qcow2 | sí |
| BTRFS | btrfs | fichero | no | sí | technology preview |
| NFS | nfs | fichero | sí | sí, con qcow2 | sí |
| CIFS | cifs | fichero | sí | sí, con qcow2 | sí |
| Proxmox Backup | pbs | ambos | sí | n/a | sí |
| CephFS | cephfs | fichero | sí | sí | sí |
| LVM | lvm | bloque | posible | sí, con volume chain | sí |
| LVM-thin | lvmthin | bloque | no | sí | sí |
| iSCSI/kernel | iscsi | bloque | sí | sí, con volume chain | sí |
| iSCSI/libiscsi | iscsidirect | bloque | sí | sí, con volume chain | sí |
| FC/SAS | nativo | bloque | sí | sí, con volume chain | sí |
| Ceph/RBD | rbd | bloque | sí | sí | sí |
| ZFS over iSCSI | zfs | bloque | sí | sí | sí |
Gotcha: esta tabla cambió en PVE 9. En PVE 8, LVM thick, iSCSI y FC/SAS aparecían con Snapshots: no. Ahora aparecen con yes solo porque existe snapshot-as-volume-chain, que es technology preview y está desactivado por defecto. Si copias la tabla sin la nota al pie, prometes una funcionalidad que el sistema no te va a dar tal cual sale de fábrica.
La segunda tabla, la de features por backend, consolida lo que puedes hacer en cada uno:
| Backend | Content types | Formatos | Shared | Snapshots | Clones |
|---|---|---|---|---|---|
dir | images rootdir vztmpl iso backup snippets | raw qcow2 vmdk subvol | no | solo qcow2 | solo qcow2 |
nfs | images rootdir vztmpl iso backup snippets | raw qcow2 vmdk | sí | solo qcow2 | solo qcow2 |
cifs | images rootdir vztmpl iso backup snippets | raw qcow2 vmdk | sí | solo qcow2 | solo qcow2 |
pbs | backup | n/a | sí | n/a | n/a |
zfspool | images rootdir | raw subvol | no | sí | sí |
lvm | images rootdir | raw, qcow2 desde PVE 9 | posible | sí, con volume chain | full sí, linked no |
lvmthin | images rootdir | raw | no | sí | sí, linked |
iscsi | images o none | raw | sí | no | no |
iscsidirect | images | raw | sí | no | no |
rbd | images rootdir | raw | sí | sí | sí |
cephfs | vztmpl iso backup snippets | ninguno | sí | sí | no |
zfs sobre iSCSI | images | raw | sí | sí | no |
Dos notas al pie que valen su peso en horas de soporte:
“On file based storages, snapshots are possible with the qcow2 format… 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.”
Sobre CephFS: “While no known bugs exist, snapshots are not yet guaranteed to be stable, as they lack sufficient testing.”
Los tipos válidos que acepta pvesm add en PVE 9 son exactamente estos: btrfs, cephfs, cifs, dir, esxi, iscsi, iscsidirect, lvm, lvmthin, nfs, pbs, rbd, zfs y zfspool. glusterfs ya no está: el roadmap de PVE 9.0 lo dice sin rodeos, “Drop support for GlusterFS starting with PVE 9, as it is unmaintained”. Si vienes de un PVE 8 con GlusterFS, ese storage hay que migrarlo antes de actualizar.
Thin provisioning y el peligro de sobreaprovisionar
El thin provisioning es la razón por la que una VM con disco de 32 GB ocupa 3 GB en el storage recién instalada:
“A number of storages, and the QEMU image format
qcow2, support thin provisioning. With thin provisioning activated, only the blocks that the guest system actually use will be written to the storage.”
Y hay una regla que simplifica mucho la elección de backend:
“All storage types which have the ‘Snapshots’ feature also support thin provisioning.”
El precio está en la letra pequeña, y es exactamente el escenario del principio del capítulo:
Caution: “If a storage runs full, all guests using volumes on that storage receive IO errors. This can cause file system inconsistencies and may corrupt your data. So it is advisable to avoid over-provisioning of your storage resources, or carefully observe free space to avoid such conditions.”
Advertencia: sobreaprovisionar no es un error en sí mismo; hacerlo sin monitorización sí. Si el Data% de un thin pool o el uso de un pool ZFS puede llegar al 100 %, necesitas una alerta antes del 80 %. En el capítulo 15 montarás esa alerta; hasta entonces, pvesm status y lvs a mano.
Propiedades comunes a casi todos los backends
Hay un conjunto de propiedades que casi cualquier plugin acepta. Estas son las que vas a usar:
| Propiedad | Descripción y default |
|---|---|
nodes | Lista de nodos del cluster donde el storage es usable. Restringe el acceso. |
content | Tipos de contenido permitidos. |
shared | Marca que es un solo storage con el mismo contenido en todos los nodos. No hace accesible un storage local desde otros nodos: solo declara que ya lo está. |
disable | Desactiva el storage por completo. |
prune-backups | Opciones de retención de backups. |
format | Formato de imagen por defecto: raw, qcow2 o vmdk. |
preallocation | off, metadata, falloc o full para raw y qcow2 en storages de fichero. Default metadata, que se trata como off en imágenes raw. |
bwlimit | clone, default, migration, move y restore, en KiB/s. |
create-base-path | Default yes. Crea el directorio base si no existe. |
create-subdirs | Default yes. Crea la estructura de subdirectorios. mkdir está deprecada en favor de estas dos. |
is_mountpoint | Default no. Asume que la ruta es un mountpoint gestionado por fuera; si no está montado, el storage se considera offline. |
max-protected-backups | Ilimitado para usuarios con Datastore.Allocate, 5 para el resto. -1 es ilimitado. |
content-dirs | Overrides del layout de directorios, formato vtype=path separado por comas. |
snapshot-as-volume-chain | Default 0. Snapshots agnósticos del proveedor mediante cadenas de volúmenes. |
skip-cert-verification | Default false. “Disable TLS certificate verification, only enable on fully trusted networks!” |
Gotcha de shared: marcar shared 1 en un dir local es el error clásico que rompe migraciones. Proxmox se creerá que el contenido de /mnt/datos del nodo A es el mismo que el de /mnt/datos del nodo B, migrará una VM sin copiar el disco y la VM arrancará sin disco al otro lado. shared describe la realidad, no la crea.
Gotcha de preallocation: con storages de red y qcow2 grandes conviene off explícito, porque preasignar metadatos sobre NFS puede hacer que la creación del disco supere el timeout de la tarea.
Volume IDs, propiedad de los volúmenes y pvesm path
Un volumen se identifica siempre como <STORAGE_ID>:<nombre-dependiente-del-tipo>. Ejemplos literales de la documentación:
local:230/example-image.raw
local:iso/debian-501-amd64-netinst.iso
local:vztmpl/debian-5.0-joomla_1.5.9-1_i386.tar.gz
iscsi-storage:0.0.2.scsi-14f504e46494c4500494b5042546d2d646744372d31616d61
Para traducir un volume ID a una ruta real del sistema de ficheros:
pvesm path local:100/vm-100-disk10.raw
/var/lib/vz/images/100/vm-100-disk10.raw
Volume ownership. Los volúmenes de tipo imagen pertenecen a una VM o a un contenedor concreto. local:230/example-image.raw pertenece a la VM 230, y la mayoría de backends codifican esa propiedad en el propio nombre del volumen. Al destruir la VM o el CT, sus volúmenes se borran con ella.
La convención de nombres es común a casi todos los backends:
vm-<VMID>-<NAME>.<FORMAT> disco normal de una VM
base-<VMID>-<NAME>.<FORMAT> imagen de plantilla en solo lectura
subvol-<VMID>-<NAME> subvolumen de contenedor en ZFS o BTRFS
<NAME> es ASCII sin espacios, por defecto disk-[N]. Cuando conviertes una VM en plantilla con qm template, el backend cambia el modo del fichero a 0444 y le aplica el flag inmutable con chattr +i si el storage lo soporta. Esa es la mecánica real detrás de “una plantilla no se puede arrancar” que viste en el capítulo 5.
Los comandos de inventario que vas a usar constantemente:
pvesm status --content images --enabled 1
pvesm list local-lvm --vmid 100
pvesm list local --content iso
pvesm alloc local 100 vm-100-disk10.raw 4G
pvesm free local:100/vm-100-disk10.raw
pvesm free destruye los datos, sin preguntar. Y pvesm remove <STORAGE_ID> hace lo contrario de lo que mucha gente espera: elimina la entrada de storage.cfg y no toca los datos. Para descubrir qué hay en un servidor remoto antes de darlo de alta están los subcomandos de escaneo:
pvesm scan nfs 10.0.0.10
pvesm scan cifs 10.0.0.11 --username anna --password
pvesm scan iscsi 10.10.10.1
pvesm scan lvmthin pve
pvesm scan zfs
Directory: el backend todoterreno
El plugin dir expone un directorio local como storage. Es el único backend que en un nodo standalone puede guardar ISOs, plantillas, backups, snippets y discos al mismo tiempo. Solo tiene una propiedad obligatoria, path, más content-dirs para mover subdirectorios.
El layout de directorios que usa es el mismo en todos los backends de fichero, y conviene memorizarlo porque también te sirve para NFS y CIFS:
images/<VMID>/
template/iso/
template/cache/
dump/
snippets/
import/
Un ejemplo de configuración literal, con content-dirs cambiando dónde caen los backups:
dir: backup
path /mnt/backup
content backup
prune-backups keep-last=7
max-protected-backups 3
content-dirs backup=custom/backup/dir
Con eso, los backups acaban en /mnt/backup/custom/backup/dir/. El equivalente en CLI:
pvesm add dir backup --path /mnt/backup --content backup \
--prune-backups keep-last=7 --max-protected-backups 3
Limitaciones que hay que tener claras: no hay snapshots a nivel de storage, solo el atajo de qcow2; no es shared; y hay un tip que aparece poco y muerde mucho:
“Some storage types do not support
O_DIRECT, so you can’t use cache modenonewith such storages. Simply use cache modewritebackinstead.”
Si montas un dir sobre un filesystem exótico y las VMs fallan al arrancar con errores de apertura del disco, el modo de caché es el primer sospechoso. Los modos de caché los recorriste en el capítulo 3.
NFS y CIFS: montaje gestionado y sus trampas
nfs está construido encima de dir: mismo layout, misma convención de nombres. Lo que añade es que Proxmox monta el share por ti, sin tocar /etc/fstab, comprueba si el servidor está online y puede listar los exports.
| Propiedad | Descripción |
|---|---|
server | IP o nombre DNS. Se recomienda IP para evitar retardos de resolución. |
export | Ruta del export NFS, la que lista pvesm scan nfs. |
path | Mountpoint local. Default /mnt/pve/<STORAGE_ID>/. |
options | Opciones de montaje NFS según man nfs. |
nfs: iso-templates
path /mnt/pve/iso-templates
server 10.0.0.10
export /space/iso-templates
options vers=3,soft
content iso,vztmpl
pvesm add nfs iso-templates --server 10.0.0.10 --export /space/iso-templates \
--content iso,vztmpl --options vers=3,soft
El flag shared está siempre puesto en NFS y no se puede quitar.
Gotcha de NFS: el tip oficial es corto y explica muchos cuelgues inexplicables del nodo.
“After an NFS request times out, NFS request are retried indefinitely by default. This can lead to unexpected hangs on the client side. For read-only content, it is worth to consider the NFS
softoption, which limits the number of retries to three.”
Traducido: si tu NAS se cae y tienes un NFS montado con las opciones por defecto, los procesos que toquen ese mountpoint se quedan en D state para siempre. Para contenido de solo lectura (ISOs, plantillas) usa soft. Para discos de VM, soft cambia colgarse por corromper, así que ahí la respuesta correcta es que el NAS no se caiga.
cifs es el equivalente para NAS Windows o Samba:
| Propiedad | Descripción y default |
|---|---|
server | IP o DNS. Requerida. |
share | Nombre del share, lo lista pvesm scan cifs. Requerida. |
username | Default guest. |
password | Se guarda en /etc/pve/priv/storage/<STORAGE-ID>.pw, legible solo por root. |
smbversion | 2.0, 2.1, 3, 3.0, 3.11 o default. SMB1 no está soportado por seguridad. |
subdir | Subdirectorio del share a montar. Default: la raíz. |
options | Opciones extra de mount.cifs. PVE siempre añade soft. |
cifs: backup
path /mnt/pve/backup
server 10.0.0.11
share VMData
content backup
options noserverino,echo_interval=30
username anna
smbversion 3
subdir /data
noserverino es habitual para evitar colisiones de inode con algunos servidores. Y PVE 9.2 corrigió el chequeo de conexión con Kerberos: si pones sec=krb5 en options, la comprobación usa smbclient --use-kerberos.
LVM thick: la LUN compartida y por qué no hay linked clones
El plugin lvm pone un Volume Group LVM encima de un dispositivo de bloque. Su caso de uso canónico es la SAN: una LUN grande de iSCSI, Fibre Channel o SAS, visible desde todos los nodos, partida en logical volumes. La razón la da la propia documentación: “the iSCSI specification does not define a management interface for space allocation”. Alguien tiene que repartir ese espacio, y ese alguien es LVM.
| Propiedad | Descripción y default |
|---|---|
vgname | Nombre del VG existente. Requerida. |
base | Volumen base que se activa antes de acceder al storage. Útil si el VG vive en un servidor iSCSI. |
saferemove | ”Wipe Removed Volumes” en la GUI: pone a cero los datos al borrar LVs. |
saferemove-stepsize | Paso del wipe en MiB para blkdiscard -p. Valores `1 |
saferemove_throughput | Throughput del wipe con cstream, hasta 10 MiB/s por defecto. |
snapshot-as-volume-chain | Default 0. |
tagged_only | Listar solo LVs etiquetados pve-vm-ID. |
saferemove usa blkdiscard cuando el dispositivo soporta “write zeroes” y solo cae a cstream si no puede. Ese fallback es el que va a 10 MiB/s: borrar un disco de 500 GB con saferemove sobre un dispositivo sin write-zeroes deja una tarea corriendo durante horas.
Crear el VG desde cero y darlo de alta:
sgdisk -N 1 /dev/sdb
pvcreate --metadatasize 250k -y -ff /dev/sdb1
vgcreate vmdata /dev/sdb1
pvesm add lvm myspace --vgname vmdata --content rootdir,images --shared 1
La explicación de por qué LVM thick nunca tuvo linked clones merece leerse literal, porque también explica de dónde sale la novedad de PVE 9:
“Unfortunately, regular LVM snapshots are inefficient because they interfere with all write operations within the entire volume group while the snapshot is active, which causes significant I/O degradation. This is why LVM does not support linked clones, and why Proxmox VE added support for snapshots as volume chains.”
Dos detalles operativos más. Uno. El tip oficial: “You can use the LVM-thin backend for non-shared local storage. It supports snapshots and linked clones”. Si no necesitas compartir, LVM thick no es la elección. Dos. PVE 9.0 desactiva la autoactivación de LVs nuevos, lo que arregla los problemas de creación y migración en clusters con LVM compartido (issue 4997). Para los LVs que ya existían, pve8to9 sugiere ejecutar el script de migración; hazlo antes de dar por cerrada una actualización desde PVE 8.
LVM-thin: el default del instalador y el pool de metadatos
lvmthin es lo que el instalador te deja como local-lvm cuando eliges ext4 o xfs. Es local, tiene thin provisioning real, snapshots y linked clones eficientes. Solo dos propiedades: vgname y thinpool.
Crear un thin pool en dos pasos, o en uno solo con -T, y darlo de alta:
lvcreate -L 100G -n data pve
lvconvert --type thin-pool pve/data
lvcreate -L 80G -T -n vmstore vmdata
pvesm add lvmthin vmstore --vgname vmdata --thinpool vmstore --content rootdir,images
El layout que deja el instalador, literal:
# lvs
LV VG Attr LSize Pool Origin Data% Meta%
data pve twi-a-tz-- 4.38g 0.00 0.63
root pve -wi-ao---- 1.75g
swap pve -wi-ao---- 896.00m
Esas dos últimas columnas son el panel de instrumentos del thin pool. Data% es cuánto del pool está realmente escrito; Meta% es cuánto del pool de metadatos está usado. Advertencia: el pool de metadatos se puede llenar antes que el de datos, y el efecto es el mismo: errores de I/O en todos los guests que viven ahí. Ocurre con muchos volúmenes pequeños y muchos snapshots.
Ampliar el pool:
lvresize --size +100G --poolmetadatasize +128M pve/data
Note: “When extending the data pool, the metadata pool must also be extended.”
Limitaciones a memorizar: no se puede compartir entre nodos, solo formato raw, y “New volumes are automatically initialized with zero”.
iSCSI, iSCSI directo y Fibre Channel
El plugin iscsi usa el initiator open-iSCSI del kernel. Necesita un paquete que no viene instalado:
apt-get install open-iscsi
Solo tiene dos propiedades, portal y target, y el tip oficial sobre content es el que define el patrón de uso real:
“If you want to use LVM on top of iSCSI, it make sense to set
content none. That way it is not possible to create VMs using iSCSI LUNs directly.”
iscsi: mynas
portal 10.10.10.1
target iqn.2006-01.openfiler.com:tsn.dcb5aaaddd
content none
Es decir: el storage iscsi existe para que Proxmox se conecte al target y presente la LUN al kernel; quien reparte el espacio es el storage lvm que pones encima. Esa es la pila SAN clásica:
flowchart LR
SAN["LUN iSCSI / FC / SAS"] --> ISCSI["storage iscsi<br/>content none"]
ISCSI --> PV["PV LVM sobre /dev/disk/by-id"]
PV --> VG["Volume Group compartido"]
VG --> LVM["storage lvm<br/>shared 1<br/>snapshot-as-volume-chain 1"]
LVM --> D1["vm-100-disk-0"]
LVM --> D2["vm-101-disk-0"]
Fibre Channel y SAS no tienen plugin propio. La nota al pie 6 de la tabla oficial lo dice: “Fibre Channel (FC) and SAS block storage is handled directly by the host without a dedicated storage plugin”. El host ve las LUNs como dispositivos de bloque y tú pones lvm con shared 1 encima, exactamente igual que con iSCSI. En ambos casos multipath es requisito práctico, no una mejora opcional; la guía oficial es la wiki de Multipath de Proxmox.
iscsidirect es la variante en espacio de usuario. Requiere el paquete libiscsi-bin y tiene las mismas opciones de configuración, pero sus limitaciones son literales y determinantes:
“there are no kernel drivers involved, so this can be viewed as performance optimization. But this comes with the drawback that you cannot use LVM on top of such iSCSI LUN. So you need to manage all space allocations at the storage server side.”
“This backend works with VMs only. Containers cannot use this driver.”
Sin LVM encima, cada disco de VM tiene que ser una LUN creada a mano en la cabina. Es una opción para escenarios muy concretos, no para uso general.
Ceph RBD y CephFS como storages
rbd es la opción recomendada para clusters HA de tres o más nodos: distribuido, sin single point of failure, con thin provisioning, snapshots y clones nativos, redimensionable y self-healing. El despliegue hiperconvergido completo es el capítulo 12; aquí solo interesa cómo se declara como storage.
| Propiedad | Descripción |
|---|---|
monhost | IPs de los MON. Opcional: solo si Ceph no corre en el propio cluster PVE. |
pool | Nombre del pool Ceph. |
data-pool | Pool de datos, solo para erasure coding. |
username | RBD user ID para clusters externos, sin el prefijo client.. |
krbd | Fuerza el módulo de kernel. Default 0. |
namespace | Namespace RBD. |
rbd: ceph-external
monhost 10.1.1.20 10.1.1.21 10.1.1.22
pool ceph-external
content images
username admin
Con un cluster externo, el keyring hay que copiarlo a mano y el nombre del fichero importa:
mkdir -p /etc/pve/priv/ceph
scp cephserver:/etc/ceph/ceph.client.admin.keyring \
/etc/pve/priv/ceph/ceph-external.keyring
La ruta final tiene que ser /etc/pve/priv/ceph/<STORAGE_ID>.keyring, donde <STORAGE_ID> es exactamente lo que aparece tras rbd: en storage.cfg. Un pool con erasure coding se declara con pvesm add rbd <id> --pool <replicated-pool> --data-pool <ec-pool>.
Nota sobre krbd: “Containers will use krbd independent of the option value”. El flag solo afecta a las VMs.
cephfs es el complemento: RBD solo acepta images y rootdir, así que las ISOs, las plantillas de contenedor, los backups y los snippets van en CephFS. Sus content types son exactamente vztmpl iso backup snippets y no admite formatos de imagen.
cephfs: cephfs-external
monhost 10.1.1.20 10.1.1.21 10.1.1.22
path /mnt/pve/cephfs-external
content backup
username admin
fs-name cephfs
Gotcha: el secret de CephFS y el keyring de RBD no tienen el mismo formato ni la misma ruta. CephFS usa /etc/pve/priv/ceph/<STORAGE_ID>.secret y ese fichero, que se genera con ceph auth get-key client.userid > cephfs.secret, contiene solo la clave, mientras que el de RBD contiene también la sección [client.userid]. Copiar uno donde va el otro produce un fallo de autenticación que no dice nada útil.
BTRFS y ZFS over iSCSI
btrfs es technology preview en Proxmox VE. Se configura igual que dir, y la diferencia central es que los discos en raw se colocan dentro de un subvolumen “in order to allow taking snapshots and supporting offline storage migration with snapshots being preserved”. Los snapshots aparecen como un subvolumen de solo lectura con el mismo path seguido de @ y el nombre, por ejemplo .../vm-100-disk-0@snap1.
btrfs: data2
path /mnt/data2/pve-storage
content rootdir,images
is_mountpoint /mnt/data2
Advertencia: hay una nota que rompe VMs en silencio.
“BTRFS will honor the
O_DIRECTflag when opening files, meaning VMs should not use cache modenone, otherwise there will be checksum errors.”
Como none es el default de caché en Proxmox, toda VM que crees sobre BTRFS sin tocar nada está en la configuración incorrecta. Ponles writeback. La opción nocow (default 0) permite direct I/O a cambio de desactivar los checksums de datos y volver irrecuperables los errores; la documentación la limita a datos que no necesiten más seguridad que un ext4 en un disco suelto.
zfs (ZFS over iSCSI) es otra cosa distinta a zfspool. Aquí hay un appliance remoto con ZFS (TrueNAS, Solaris/illumos, FreeBSD, Linux con LIO o IET) y Proxmox crea un ZVOL por disco de guest y lo exporta como LUN iSCSI. La nota oficial acota el caso: “This plugin needs a ZFS capable remote storage appliance, you cannot use it to create a ZFS Pool on a regular Storage Appliance/SAN”.
El requisito de autenticación es una clave SSH sin passphrase en una ruta muy concreta:
mkdir -p /etc/pve/priv/zfs
ssh-keygen -f /etc/pve/priv/zfs/192.0.2.1_id_rsa
ssh-copy-id -i /etc/pve/priv/zfs/192.0.2.1_id_rsa.pub [email protected]
zfs: lio
blocksize 4k
iscsiprovider LIO
pool tank
portal 192.0.2.111
target iqn.2003-01.org.linux-iscsi.lio.x8664:sn.xxxxxxxxxxxx
content images
lio_tpg tpg1
sparse 1
Los cuatro proveedores soportados en iscsiprovider son LIO, iet, istgt y comstar. Limitaciones: solo VMs, solo formato raw, snapshots sí, clones no, y la advertencia obvia de que el appliance no debe convertirse en un single point of failure. El plugin zfspool local, que es otra historia completamente distinta, lo verás en el capítulo 8.
Un último tipo que conviene conocer aunque no sea un storage de producción: esxi. Es el plugin de importación desde VMware, expone las VMs del host ESXi como contenido de tipo import y requiere el paquete pve-esxi-import-tools. Lo usarás en el capítulo 16.
Snapshots como cadena de volúmenes: la novedad de PVE 9
Esta es la funcionalidad que cambió la tabla de features en la rama 9, y la que más malentendidos genera. La documentación la describe así:
“Set this flag to enable snapshot support for virtual machines on LVM with a volume backing chain. With this setting, taking a snapshot persists the current state under the snapshot’s name and starts a new volume backed by the snapshot. A volume based on a snapshot references its parent snapshot volume as its backing volume and records only the differences to that backing volume. Snapshot volumes are currently thick-provisioned LVM logical volumes. This design avoids issues with native LVM snapshots, such as significant input/output (I/O) penalties and unexpected, dangerous behavior when running out of pre-allocated space. Note that, although this feature relies on qcow2, it only uses qcow2’s ability to layer multiple volumes in a backing chain, not qcow2’s snapshot functionality. Enabling or disabling this flag only affects newly created virtual disk volumes.”
flowchart LR
A["LV0 activo<br/>qcow2 base"] -->|primer snapshot| B["LV0 congelado como snap1"]
B --> C["LV1 activo<br/>backing file LV0"]
C -->|segundo snapshot| D["LV1 congelado como snap2"]
D --> E["LV2 activo<br/>backing file LV1"]
E --> F["Escrituras siempre en la capa superior"]
E --> G["Lecturas recorren la cadena hacia abajo"]
Lo que tienes que retener antes de activarlo:
| Aspecto | Detalle verificado |
|---|---|
| Estado | Technology preview |
| Formato de disco | qcow2 obligatorio |
| QEMU machine version | >= 10; PVE 9.1 falla temprano si es menor |
| Alcance del flag | Solo afecta a volúmenes nuevos; PVE 9.1 prohíbe desactivarlo si ya hay una imagen qcow2 |
| TPM | PVE 9.2 permite snapshots en vivo de VMs con estado TPM sobre volume chains (issue 7066), con la limitación de que el snapshot más alto no se puede borrar con la VM encendida |
| Migración | Las VMs con snapshots en storage local que permita volume chains no se pueden migrar |
| Requisito del storage | Thin provisioning y discard, para que cada snapshot no ocupe de verdad el tamaño completo |
| Storages soportados | LVM thick, incluido sobre iSCSI/FC/SAS, y Directory/NFS/CIFS |
Activarlo en un storage nuevo o en uno existente:
pvesm add lvm san-lvm --vgname san-vg --shared 1 --content images \
--snapshot-as-volume-chain 1
pvesm set san-lvm --snapshot-as-volume-chain 1
Advertencia sobre el dimensionado. Cada snapshot asigna un LV nuevo del tamaño completo del disco. Del lado de PVE cada snapshot parece ocupar el volumen entero; el consumo real solo será menor si la cabina hace thin provisioning y honra el discard. El análisis de Blockbridge sobre esta funcionalidad pone números: para un disco de 1 TiB con 24 snapshots horarios hacen falta al menos ((1 + 24) * 1TiB) * 110% = 27.5 TiB de LUN, midieron entre un 30 % y un 90 % de degradación de rendimiento frente a raw, y observaron que en SAN thin el merge al borrar un snapshot “allocates additional storage in the new logical volume but does not release any storage from the old one”.
Con eso en la mano: es una funcionalidad excelente para no tener que renunciar a snapshots en una SAN existente, y una mala idea como configuración por defecto de un cluster nuevo. Si puedes elegir el backend, ZFS o Ceph te dan snapshots baratos sin ninguna de estas notas al pie.
Mover, redimensionar, importar y desvincular discos
Estas son las operaciones que vas a repetir cada semana. El comando canónico para mover un disco entre storages es qm disk move, con qm move-disk como alias:
qm disk move 300 scsi0 other-storage
qm move-disk 300 scsi1 --target-vmid 400 --target-disk scsi3
El segundo ejemplo es útil y poco conocido: reasigna scsi1 de la VM 300 a la VM 400 como scsi3, renombrando la imagen por debajo.
| Opción | Default | Descripción |
|---|---|---|
--bwlimit | límite move del datacenter o del storage | Límite de I/O en KiB/s |
--delete | 0 | Borra el disco original tras la copia |
--format | — | Formato destino. Si el storage destino no lo soporta, se usa su formato por defecto |
--target-vmid | — | VM destino para reasignación |
--target-disk | misma clave que el origen | Clave de config en la VM destino |
Gotcha número uno de todo el capítulo: --delete es 0 por defecto. Después de mover un disco, el original sigue existiendo y aparece en la config como unusedN. Es la causa número uno de “moví todas las VMs a la SAN y el storage local sigue lleno”.
qm config 300 | grep unused
qm disk unlink 300 --idlist unused0
Para contenedores el comando equivalente es pct move-volume, con el mismo default de --delete 0, y acepta rootfs y mp0..mpN:
pct move-volume 100 mp0 other-storage
pct move-volume 100 mp0 --target-vmid 200 --target-volume mp1
Redimensionar:
qm disk resize 100 scsi0 +32G
qm disk resize 100 scsi0 200G
pct resize 200 rootfs +10G
La sintaxis exacta del tamaño es \+?\d+(\.\d+)?[KMGT]?. Con el signo + el valor se suma al tamaño actual; sin él es absoluto. Y una regla dura:
“Shrinking disk size is not supported.”
Tras ampliar un disco de VM hay que ampliar la partición y el filesystem dentro del guest con growpart más resize2fs en ext4, xfs_growfs en xfs, o pvresize y lvextend -r si hay LVM dentro. Con pct resize no hace falta: el contenedor comparte kernel con el host y Proxmox se ocupa del filesystem en la mayoría de casos.
Cuando el tamaño real de un volumen cambia por fuera de Proxmox (redimensionaste un LV o un zvol a mano, o recuperaste imágenes huérfanas), hay que resincronizar con qm disk rescan o pct rescan, que aceptan --vmid y --dryrun 1. Importar y desvincular:
qm disk import 200 /root/disco.vmdk local-lvm --format raw --target-disk scsi1
qm disk unlink 200 --idlist scsi1
qm disk unlink 200 --idlist scsi1 --force 1
qm disk import deja la imagen como disco unused, no la engancha. Sobre --force: “Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called unused[n], which contains the volume ID. Unlink of unused[n] always cause physical removal.”
Y la variante que usaste al construir plantillas cloud-init, que importa y engancha en un solo paso:
qm set 9000 --scsi0 local-lvm:0,import-from=/root/debian-13-generic-amd64.qcow2
Por último, qm destroy 300 --purge: “You have to activate --purge, if you want to additionally remove the VM from replication jobs, backup jobs and HA resource configurations.” Sin --purge, la VM desaparece pero el job de backup que la referenciaba sigue ahí.
TRIM y discard de extremo a extremo
Que un storage haga thin provisioning no significa que recupere espacio cuando el guest borra ficheros. Para que eso ocurra tienen que alinearse cuatro capas, y basta con que una falle para que el disco solo crezca.
flowchart LR
A["El guest borra ficheros"] --> B{"discard=on en el disco?"}
B -->|no| Z["El storage no libera nada"]
B -->|si| C{"El guest emite TRIM?"}
C -->|"Linux: fstrim o mount -o discard"| D["Controlador virtual"]
C -->|"Windows: SSD emulation mas optimize"| D
D --> E{"El storage soporta thin?"}
E -->|"lvmthin / zfs sparse / rbd / qcow2"| F["Bloques liberados"]
E -->|"lvm thick o raw preasignado"| Z
Del lado de Proxmox son dos sub-opciones del disco:
discard: <ignore | on>
ssd: <0 | 1>
qm set 100 --scsi0 local-lvm:vm-100-disk-0,discard=on,ssd=1
Tres reglas exactas que hay que respetar:
Uno. discard en VirtIO Block solo funciona con kernel Linux >= 5.0 en el guest.
Dos. ssd no está soportado en virtio[n]; solo en ide, sata y scsi. No hace falta que el medio físico sea realmente un SSD: es lo que el guest ve.
Tres. Algunos sistemas operativos guest solo emiten TRIM si el disco se presenta como SSD, así que discard=on sin ssd=1 puede quedarse a medias.
Del lado del guest Linux, la verificación y la activación. En lsblk --discard, las columnas DISC-GRAN y DISC-MAX distintas de cero confirman que el disco acepta descartes:
lsblk --discard
fstrim -av
systemctl enable --now fstrim.timer
Proxmox también puede lanzar el TRIM por ti a través del QEMU Guest Agent, con la opción fstrim_cloned_disks (default 0), que ejecuta fstrim “after moving a disk or migrating the VM”:
qm set 100 --agent enabled=1,fstrim_cloned_disks=1
Gotcha con ext4: la documentación avisa de un caveat concreto, “because it uses an in-memory optimization to avoid issuing duplicate TRIM requests”. Como el guest no sabe que el storage subyacente cambió tras un move o un clone, puede saltarse el descarte por creer que ya lo hizo. Si tras mover discos el espacio no baja, entra al guest y fuerza fstrim -av.
En contenedores el comando es propio, pct fstrim <vmid> [--ignore-mountpoints 1], y tiene una exclusión importante: “Run fstrim on a chosen CT and its mountpoints, except bind or read-only mountpoints”. Con --ignore-mountpoints se limita al rootfs del contenedor. Mientras corre, el contenedor queda con el lock fstrim.
Rutas y ficheros que conviene tener memorizados
| Ruta | Contenido |
|---|---|
/etc/pve/storage.cfg | Configuración de todos los storages, replicada por pmxcfs |
/etc/pve/priv/storage/<STORAGE-ID>.pw | Password de CIFS y de PBS |
/etc/pve/priv/storage/<STORAGE-ID>.enc | Clave AES-256 GCM de cifrado de PBS |
/etc/pve/priv/storage/<STORAGE-ID>.master.pem | Master public key RSA de PBS |
/etc/pve/priv/ceph/<STORAGE_ID>.keyring | Keyring RBD para cluster Ceph externo |
/etc/pve/priv/ceph/<STORAGE_ID>.secret | Secret de CephFS, solo la clave |
/etc/pve/priv/ceph/<STORAGE_ID>.conf | Config de cliente Ceph por storage, opcional |
/etc/pve/priv/zfs/<target_ip>_id_rsa | Clave SSH para ZFS over iSCSI |
/var/lib/vz | Directorio del storage local por defecto |
/mnt/pve/<STORAGE_ID>/ | Mountpoint por defecto de NFS, CIFS y CephFS |
/etc/fstab | Montajes manuales para storages de tipo dir |
Todo lo que hay bajo /etc/pve/priv/ es legible solo por root y contiene contraseñas y claves en texto plano. Ten eso presente antes de compartir un pvereport, tal como se advirtió en el capítulo 2.
Errores típicos de almacenamiento y cómo se diagnostican
| Síntoma | Causa probable | Diagnóstico y solución |
|---|---|---|
| Un ISO o backup nuevo no aparece en la GUI | El content del storage no incluye ese tipo, o el fichero no está en el subdirectorio correcto | pvesm list <ID>; revisar content en /etc/pve/storage.cfg; comprobar el layout template/iso/, dump/ |
storage 'X' is not online | El share no está montado, el servidor está caído o is_mountpoint apunta mal | pvesm status; findmnt /mnt/pve/X; journalctl -u pvestatd |
| Todos los guests devuelven errores de I/O a la vez | El storage se llenó: thin pool, pool ZFS o Ceph al 100 % | pvesm status; lvs mirando Data% y Meta%; zpool list; ceph -s |
The current guest configuration does not support taking new snapshots | LVM thick sin snapshot-as-volume-chain, disco raw sobre dir/nfs, iSCSI directo, machine version < 10, o TPM en PVE anterior a 9.2 | Revisar la matriz de features; pasar el disco a qcow2; activar snapshot-as-volume-chain 1; subir la machine version |
| Un snapshot sobre NFS tarda minutos u horas | Snapshot interno qcow2 sobre red con un disco muy grande | Es el comportamiento documentado: hacerlo con la VM apagada o usar volume chains |
Tras qm disk move el storage origen sigue lleno | --delete 0 por defecto dejó un unusedN | qm config <vmid> | grep unused; qm disk unlink <vmid> --idlist unused0 |
| Migración fallida en un cluster con LVM compartido | Autoactivación de LVs heredada de PVE 8 | PVE 9 la desactiva para LVs nuevos; ejecutar el script de migración que sugiere pve8to9 |
| Checksum errors en VMs sobre BTRFS | cache=none con O_DIRECT | Cambiar el modo de caché del disco a writeback |
| RBD externo no autentica | Nombre de fichero del keyring incorrecto | Tiene que ser /etc/pve/priv/ceph/<STORAGE_ID>.keyring, con el ID exacto de storage.cfg |
| CephFS externo no monta | Se copió un keyring donde iba un secret | /etc/pve/priv/ceph/<STORAGE_ID>.secret debe contener solo la clave, sin [client.userid] |
| El disco crece y nunca baja pese a borrar ficheros | Falta discard=on, falta ssd=1, el guest no emite TRIM o el storage es thick | lsblk --discard en el guest; fstrim -av; revisar la config del disco |
| El nodo se cuelga cuando el NAS se cae | NFS con reintentos infinitos por defecto | Opción soft para contenido de solo lectura; para discos de VM, redundancia en el NAS |
| Migración de una VM a otro nodo sin copiar el disco y arranque sin disco | Un dir local marcado con shared 1 | Quitar shared; shared declara una realidad, no la crea |
| El wipe de un LV borrado tarda horas | saferemove cayendo a cstream a 10 MiB/s | Comprobar si el dispositivo soporta write-zeroes; valorar desactivar saferemove |
Lo que queda operativo
A partir de aquí sabes leer /etc/pve/storage.cfg como se lee un plano: qué plugin hay detrás de cada bloque, qué content types acepta, si es shared de verdad, qué formatos admite y por tanto si podrás hacer snapshots, linked clones y migración en vivo. Sabes dar de alta cualquiera de los catorce tipos que acepta pvesm add en PVE 9.2, montar la pila SAN clásica con iscsi en content none y lvm con shared 1 encima, y decidir con criterio si activar snapshot-as-volume-chain sabiendo lo que cuesta. Y tienes las operaciones del día a día con sus trampas: el --delete 0 de qm disk move, la imposibilidad de reducir un disco y la cadena completa que hace falta para que un TRIM llegue hasta el storage.
Falta el backend que la documentación describe como el más avanzado y que el instalador te ofrece de entrada. En el capítulo 8 entras en ZFS a fondo: vdevs y niveles RAID, ashift, recordsize y volblocksize, la amplificación de escritura de los ZVOL sobre RAIDZ, SLOG y L2ARC, el límite del ARC, scrub y sustitución de discos, datasets cifrados y la replicación asíncrona con pvesr que convierte un ZFS local en la base de un cluster con failover.