Pourquoi vous en voudriez un

QEMU sait émuler un vrai contrôleur NVMe — pas un périphérique paravirtuel qui a besoin d’un pilote que vous fournissez, mais un contrôleur NVMe PCIe qu’un invité reconnaît comme un SSD normal et pilote avec la prise en charge NVMe qu’il a déjà.

C’est là tout l’attrait, et il vaut plus qu’il n’en a l’air.

Linux a un pilote nvme intégré depuis des années. Windows livre stornvme depuis Windows 8.1 et Server 2012 R2. Un invité démarre donc, énumère un contrôleur NVMe PCIe, charge son propre pilote et trouve un disque. Pas d’ISO VirtIO, pas d’injection de pilote au moment de l’installation, pas d’écran « aucun disque trouvé » à mi-chemin d’un installeur Windows.

Quiconque est resté à regarder cet écran, l’ISO VirtIO monté et l’installeur persistant à dire qu’il n’y a pas de disque, en verra l’attrait tout de suite.

La deuxième raison est qu’il se comporte comme du NVMe jusqu’en haut. nvme-cli fonctionne. Les namespaces sont réels. Les formats LBA, les octets de métadonnées et les informations de protection sont tous configurables. Ce qui en fait un très bon endroit pour s’exercer aux opérations que vous ne devriez pas exercer sur du matériel qui détient des données.

Comment en ajouter un

Proxmox n’a ni case à cocher ni clé de configuration pour cela dans l’interface. C’est un périphérique QEMU brut, il va donc dans args: dans /etc/pve/qemu-server/<vmid>.conf.

La documentation QEMU donne la paire minimale : un disque d’appui sans interface, et le contrôleur qui le consomme. Édité directement dans /etc/pve/qemu-server/<vmid>.conf, sans guillemets :

args: -drive file=/var/lib/vz/images/100/nvm.img,if=none,id=nvmidentifier -device nvme,serial=LAB-NVME-01,drive=nvmidentifier

if=none compte : il dit à QEMU de ne pas rattacher le disque à un contrôleur par défaut, parce que la ligne -device nvme va le réclamer. Le id= du disque et le drive= du périphérique doivent correspondre. C’est cet appariement qui joint les deux moitiés.

Le serial= est obligatoire ; QEMU refuse de démarrer la VM sans lui. Choisissez quelque chose que vous reconnaîtrez, car c’est exactement ce que l’invité rapporte dans nvme list et smartctl, et « lequel de ces quatre disques virtuels identiques est lequel » est une question que vous finirez par poser.

Les guillemets : la partie qui attrape tout le monde

Faut-il mettre cette chaîne entre guillemets ? Cela dépend de l’endroit où vous la tapez, et se tromper de sens est la cause la plus courante d’un échec au premier essai.

Proxmox stocke la valeur args: et la découpe plus tard avec Text::ParseWords::shellwords. Donc dans le fichier de configuration, les guillemets sont honorés et retirés. Une chaîne entièrement entre guillemets devient un seul argument :

# WRONG in the config file — collapses to one argv element QEMU cannot parse
args: "-drive file=…,if=none,id=nvmidentifier -device nvme,serial=…,drive=nvmidentifier"

Passée par shellwords, elle donne exactement un élément. Sans guillemets, la même ligne donne les quatre dont QEMU a réellement besoin : -drive, son bloc de paramètres, -device, son bloc de paramètres.

Sur la ligne de commande c’est l’inverse, parce que là vous mettez des guillemets pour votre shell, pas pour Proxmox. Ici les guillemets sont requis, et ce qui est stocké dans la configuration est la valeur sans guillemets :

qm set 100 --args "-drive file=/var/lib/vz/images/100/nvm.img,if=none,id=nvmidentifier -device nvme,serial=LAB-NVME-01,drive=nvmidentifier"

Les deux sont corrects. Ils ne sont simplement pas interchangeables. Si vous construisez la ligne avec qm set, vérifiez le résultat avec qm config 100 ensuite et vous la verrez stockée à nu. C’est la forme que veut le fichier de configuration.

Créez d’abord l’image d’appui si elle n’existe pas :

qemu-img create -f raw /var/lib/vz/images/100/nvm.img 32G

Plus d’un namespace

Pour tout ce qui va au-delà d’un seul disque, séparez le contrôleur de ses namespaces :

-device nvme,id=nvme-ctrl-0,serial=deadbeef
-drive file=nvm-1.img,if=none,id=nvm-1
-device nvme-ns,drive=nvm-1

Les identifiants de namespace sont attribués à partir de 1 automatiquement. C’est la configuration qui rend le périphérique vraiment utile pour apprendre, parce que la gestion des namespaces est la partie de NVMe que la plupart des gens ne touchent jamais.

Un namespace virtuel en 4Kn

Le namespace prend les propriétés habituelles de taille de bloc, et QEMU en dérive directement la taille de donnée LBA. hw/nvme/ns.c calcule l’exposant de format comme ds = 31 - clz32(ns->blkconf.logical_block_size). Cela vous donne donc un namespace 4K natif en bonne et due forme :

-device nvme-ns,drive=nvm-1,logical_block_size=4096,physical_block_size=4096

Le périphérique de namespace accepte aussi ms pour les octets de métadonnées par LBA, mset pour les LBA étendus, et pi et pif pour le type d’informations de protection et le format de garde.

C’est un laboratoire complet pour tout ce dont parle l’article sur le 4Kn et le 512e — blocs logiques de 512 octets contre 4096, formats porteurs de métadonnées, T10-PI — sur un périphérique que vous pouvez détruire aussi souvent que vous voulez.

Vérifier que c’est bien arrivé

Depuis l’intérieur de l’invité :

lsblk -o NAME,MODEL,SIZE,LOG-SEC,PHY-SEC
nvme list
nvme id-ns -H /dev/nvme0n1 | grep -i "lbaf\|data size"

Vous devriez voir un vrai namespace NVMe, avec les tailles de bloc que vous avez demandées.

Ce que vous abandonnez

Trois choses, et les deux premières ne sont pas des compromis de performance. Ce sont des retraits de capacité. Connaissez-les avant de mettre quoi que ce soit sur le périphérique.

Ce que Proxmox gère, et ce hors de quoi un périphérique rattaché par args se trouveDans la configuration de la VM, comme disquescsi0: local-zfs:vm-100-disk-0,iothread=1Proxmox possède le volume et sait qu'il existeInclus dans les sauvegardes vzdump / PBSouiInstantanés PVEouiMigration à chaudouiRedimensionner et Déplacer dans l'interfaceouiCompté dans la vue de stockageouiRattaché par args :-device nvme,drive=nvm1,serial=…un périphérique QEMU brut — PVE n'en sait rienInclus dans les sauvegardesnonInstantanés PVEnonMigration à chaudnonRedimensionner et DéplacernonCompté dans la vue de stockagenonUn seul de ces « non » s'annonce. La migration à chaud échoue avec une erreur, parce que QEMU marquele périphérique non migrable. La sauvegarde réussit simplement sans le disque dedans — ce qui est pourquoi celaa sa place dans votre runbook, pas seulement dans votre mémoire.
Proxmox gère ce qui figure dans la configuration de la VM comme un disque. Un contrôleur rattaché par args est en dehors de cela, donc chaque fonction bâtie sur la couche de stockage ne s’y applique tout simplement pas.

1. La migration à chaud est coupée

Ce n’est pas une limite de Proxmox ni un oubli. QEMU déclare le périphérique non migrable dans le modèle de périphérique lui-même. Extrait de hw/nvme/ctrl.c dans QEMU 10.2 :

static const VMStateDescription nvme_vmstate = {
    .name = "nvme",
    .unmigratable = 1,
};

Trois lignes, et celle du milieu est toute l’histoire. Le contrôleur n’a pas d’état de migration, donc QEMU refuse la migration plutôt que de la tenter. C’est le bon échec. Vous obtenez une erreur, pas un invité qui reprend sur un autre nœud avec un disque désorienté.

Il y a une seconde raison, indépendante, pour laquelle cela ne peut pas marcher : Proxmox ne sait pas que le disque existe. Même si QEMU pouvait déplacer l’état du périphérique, rien dans la logique de migration de PVE n’organiserait la disponibilité du volume d’appui sur la cible.

À surveiller toutefois : la branche de développement de QEMU a remplacé le drapeau global par une fonction nvme_set_migration_blockers() qui autorise la migration et ne la bloque que pour certaines fonctions. Plus d’un namespace, par exemple, où le commentaire note « we don’t handle this in migration code yet ». Cela n’est apparu dans aucune version jusqu’à 10.2 comprise, donc cela ne vous aide pas aujourd’hui, mais cette restriction paraît en passe de s’assouplir. Vérifiez votre propre version de QEMU plutôt que de vous fier à un article.

2. Les sauvegardes Proxmox ne le verront pas

vzdump et Proxmox Backup Server sauvegardent les volumes qui apparaissent dans la configuration de la VM comme disques — scsi0, virtio0, et ainsi de suite. Un disque rattaché par args: n’en est pas un. C’est un périphérique QEMU brut dont PVE ne sait rien.

La sauvegarde tourne donc, rapporte un succès, et ne contient pas le périphérique.

Ce mode d’échec est pire qu’une erreur, parce que rien ne vous prévient. La même chose vaut partout : pas d’instantanés PVE, pas de redimensionnement de disque depuis l’interface, pas de Move Disk, pas de comptabilité dans la vue de stockage. Si vous avez créé le volume par PVE puis l’avez détaché, PVE peut ne pas le nettoyer non plus. Un orphelin qui attend de désorienter quelqu’un plus tard.

Si des données vont vivre sur l’un de ces périphériques, sauvegardez-les depuis l’intérieur de l’invité, et notez quelque part que l’hyperviseur ne les couvre pas.

3. Il n’est pas plus rapide que VirtIO SCSI

Celui-là surprend, parce que « NVMe » se lit comme une fonction de performance. Ici, il n’en est pas une.

VirtIO SCSI et VirtIO block sont paravirtuels : le pilote de l’invité et l’hyperviseur partagent un tampon en anneau conçu exactement pour ce travail, et l’invité sait qu’il parle à un hyperviseur.

Le contrôleur NVMe émulé est l’inverse par conception. Il présente de vrais registres NVMe, donc l’invité le programme comme si c’était du matériel. Chaque écriture de sonnette (doorbell) est un accès MMIO qui piège dans l’hyperviseur. Correct, et plus coûteux par IO que de poser un descripteur sur un anneau.

Un anneau partagé contre des registres émulésinvité │ hyperviseurVirtIO SCSI — paravirtuelpilote invitésait que c'est une VManneau partagéles deux bouts le comprennentcouche bloc hôte1 notif.Un descripteur va sur l'anneau et l'hôte est notifié. L'anneau chevauche la frontière exprès.NVMe émulé — vrais registrespilote invitése croit du matérielregistres NVMesonnettes, files, MMIOcouche bloc hôtechaque écriture de sonnette piègepiège + émule, par IOL'invité fait exactement ce qu'il ferait à un contrôleur physique, ce qui est l'intérêt — son proprepilote marche sans modif. C'est aussi pourquoi c'est une fonction de compatibilité, pas de performance.Le regroupement d'interruptions est non pris en charge et coupé par défaut : comportement matériel exact, au prix d'émuler le matériel.
Le chemin paravirtuel est un anneau que l’invité et l’hôte comprennent tous deux. Le chemin émulé fait piloter des registres à l’invité, et chaque écriture de sonnette est un piège — comportement matériel exact, au coût de l’émulation de matériel.

La documentation de QEMU elle-même est franche sur les aspérités du périphérique aussi : le regroupement d’interruptions « is not supported and is disabled by default », et les chiffres de comptabilité dans la page de journal SMART/Health « are reset when the device is power cycled ».

Rien de cela ne le rend lent en termes absolus. Il est parfaitement utilisable. Cela veut juste dire que vous ne devriez jamais le choisir en espérant plus de débit que ce que VirtIO SCSI vous donne. Choisissez-le pour le pilote, ou pour la sémantique NVMe.

Où il gagne vraiment sa place

  • Installer un invité sans support VirtIO. Un installeur Windows qui ne voit pas de disque VirtIO SCSI en verra un NVMe, parce que le pilote est déjà dans l’image. Installez dessus, puis décidez de basculer ou non vers VirtIO ensuite.
  • Appareils et images que vous ne contrôlez pas. Tout ce qui est livré comme image figée dépourvue de pilotes VirtIO, et que vous préféreriez ne pas reconstruire.
  • Apprentissage et travail de laboratoire. nvme format --lbaf, création et rattachement de namespaces, métadonnées et informations de protection — les opérations destructives et dépendantes du fabricant sur du vrai matériel sont gratuites ici. C’est la façon la plus sûre d’acquérir les automatismes avant de toucher un disque qui compte.
  • Reproduire la topologie de quelqu’un d’autre. Si vous déboguez la disposition NVMe d’un client, un contrôleur émulé avec des namespaces et des tailles de bloc assortis est une boucle bien plus rapide que d’emprunter son matériel.

Ce qu’il faut utiliser à la place en production

Pour une VM qui a besoin de performance, des fonctions PVE et d’une vie tranquille : VirtIO SCSI single, avec iothread=1, discard=on et ssd=1, sur cache=none. C’est l’arrangement qui garde la migration à chaud, les sauvegardes, les instantanés et la vue de stockage tous en état de marche.

Pour une VM qui a besoin des derniers pourcents et peut abandonner ces fonctions exprès, la réponse n’est pas un périphérique NVMe émulé. C’est du vrai passthrough, avec ses propres compromis durs, traité dans l’article sur la taxe IOMMU.

Le périphérique NVMe émulé n’est dans aucun des deux camps. À ce titre, c’est un outil de compatibilité et de laboratoire, et il excelle à cela.

Servez-vous-en pour le travail où il est bon et il ne vous lâchera pas. Demandez-lui d’être une fonction de performance et il vous lâchera très vite.

Références