Warum du eines wollen würdest

QEMU kann einen echten NVMe-Controller emulieren — kein paravirtuelles Gerät, das einen Treiber braucht, den du mitbringst, sondern einen PCIe-NVMe-Controller, den ein Gast als normale SSD erkennt und mit der NVMe-Unterstützung ansteuert, die er schon hat.

Das ist der ganze Reiz, und er ist mehr wert, als er klingt.

Linux hat seit Jahren einen eingebauten nvme-Treiber. Windows liefert stornvme seit Windows 8.1 und Server 2012 R2. Ein Gast startet also, zählt einen PCIe-NVMe-Controller auf, lädt seinen eigenen Treiber und findet eine Platte. Kein VirtIO-ISO, keine Treiber-Einspeisung bei der Installation, und kein Bildschirm „keine Laufwerke gefunden“ mitten im Windows-Installationsprogramm.

Wer je vor diesem Bildschirm gesessen hat, mit eingebundenem VirtIO-ISO und einem Installationsprogramm, das trotzdem darauf beharrt, es gäbe keine Platten, wird den Reiz sofort sehen.

Der zweite Grund ist, dass es sich bis nach oben wie NVMe verhält. nvme-cli funktioniert. Namespaces sind echt. LBA-Formate, Metadaten-Byte und Schutzinformationen sind alle einstellbar. Das macht es zu einem sehr guten Ort, um die Handgriffe zu üben, die du auf Hardware mit Daten darauf nicht üben solltest.

Wie man eines hinzufügt

Proxmox hat dafür kein Häkchen in der GUI und keinen Konfigurationsschlüssel. Es ist ein rohes QEMU-Gerät, es kommt also in args: in /etc/pve/qemu-server/<vmid>.conf.

Die QEMU-Dokumentation gibt das minimale Paar: ein unterlegtes Laufwerk ohne Schnittstelle und den Controller, der es verbraucht. Direkt in /etc/pve/qemu-server/<vmid>.conf geschrieben, ohne Anführungszeichen:

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

if=none zählt: es sagt QEMU, das Laufwerk nicht an einen voreingestellten Controller zu hängen, denn die Zeile -device nvme wird es beanspruchen. Das id= am Laufwerk und das drive= am Gerät müssen übereinstimmen. Diese Paarung ist es, die die zwei Hälften verbindet.

Das serial= ist Pflicht; QEMU weigert sich, die VM ohne eines zu starten. Wähl etwas, das du wiedererkennst, denn es ist genau das, was der Gast in nvme list und smartctl zurückmeldet, und „welches dieser vier gleichen virtuellen Laufwerke ist welches“ ist eine Frage, die du irgendwann stellen wirst.

Anführungszeichen: der Teil, der alle erwischt

Ob du diese Zeichenkette in Anführungszeichen setzt, hängt davon ab, wo du sie tippst, und es verdreht zu bekommen ist der häufigste Grund, warum eines davon beim ersten Versuch nicht geht.

Proxmox speichert den Wert von args: und teilt ihn später mit Text::ParseWords::shellwords. In der Konfigurationsdatei werden Anführungszeichen also beachtet und entfernt. Eine vollständig eingefasste Zeichenkette wird zu einem einzigen 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"

Durch shellwords gelaufen gibt das genau ein Element. Ohne Anführungszeichen gibt dieselbe Zeile die vier, die QEMU wirklich braucht: -drive, ihren Parameterklumpen, -device, seinen Parameterklumpen.

Auf der Kommandozeile ist es umgekehrt, denn dort setzt du für deine Shell in Anführungszeichen und nicht für Proxmox. Hier sind die Anführungszeichen nötig, und was in der Konfiguration landet, ist der Wert ohne sie:

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"

Beides ist richtig. Sie sind bloß nicht austauschbar. Baust du die Zeile mit qm set, prüf das Ergebnis danach mit qm config 100, und du wirst sie nackt gespeichert sehen. Das ist die Form, die die Konfigurationsdatei will.

Leg das unterlegte Abbild zuerst an, wenn es nicht existiert:

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

Mehr als ein Namespace

Für alles jenseits einer einzelnen Platte trenn den Controller von seinen 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

Namespace-Kennungen werden von 1 aufwärts automatisch vergeben. Das ist die Aufstellung, die das Gerät zum Lernen wirklich nützlich macht, denn Namespace-Verwaltung ist der Teil von NVMe, an den die meisten Leute nie kommen.

Ein 4Kn-Namespace, virtuell

Das Namespace nimmt die üblichen Eigenschaften zur Blockgröße, und QEMU leitet die LBA-Datengröße direkt daraus ab. hw/nvme/ns.c berechnet den Formatexponenten als ds = 31 - clz32(ns->blkconf.logical_block_size). Das gibt dir also ein richtiges 4-K-natives Namespace:

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

Das Namespace-Gerät nimmt außerdem ms für Metadaten-Byte pro LBA, mset für erweiterte LBAs und pi und pif für Art und Wächterformat der Schutzinformationen.

Das ist ein vollständiges Labor für alles in dem Beitrag über 4Kn und 512e — logische Blöcke von 512 gegen 4096 Byte, Formate mit Metadaten, T10-PI — auf einem Gerät, das du so oft zerstören kannst, wie du willst.

Prüfen, dass es gelandet ist

Im Gast:

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

Du solltest ein echtes NVMe-Namespace sehen, mit den Blockgrößen, die du verlangt hast.

Was du aufgibst

Drei Dinge, und die ersten zwei sind keine Abwägungen bei der Leistung. Es sind entfernte Fähigkeiten. Kenne sie, bevor du irgendetwas auf das Gerät legst.

Was Proxmox verwaltet, und wovon ein über args angehängtes Gerät außen stehtIn der VM-Konfiguration, als Plattescsi0: local-zfs:vm-100-disk-0,iothread=1Proxmox besitzt das Volume und weiß, dass es da istIn vzdump-/PBS-Sicherungen enthaltenjaPVE-SnapshotsjaLive-MigrationjaGröße ändern und Platte verschieben in der GUIjaIn der Speicheransicht gezähltjaÜber args: angehängt-device nvme,drive=nvm1,serial=…ein rohes QEMU-Gerät — PVE weiß nichts davonIn Sicherungen enthaltenneinPVE-SnapshotsneinLive-MigrationneinGröße ändern und verschiebenneinIn der Speicheransicht gezähltneinNur eines dieser Neins meldet sich. Die Live-Migration scheitert mit einem Fehler, weil QEMU dasGerät als nicht migrierbar markiert. Die Sicherung gelingt einfach ohne die Platte darin — unddeshalb gehört das in dein Runbook und nicht bloß in dein Gedächtnis.
Proxmox verwaltet, was in der VM-Konfiguration als Platte steht. Ein über args angehängter Controller liegt außerhalb davon, jede Funktion, die auf der Speicherschicht aufbaut, gilt für ihn also einfach nicht.

1. Live-Migration ist aus

Das ist keine Grenze von Proxmox und kein Versehen. QEMU erklärt das Gerät im Gerätemodell selbst für nicht migrierbar. Aus hw/nvme/ctrl.c in QEMU 10.2:

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

Drei Zeilen, und die mittlere ist die ganze Geschichte. Der Controller hat keinen Migrationszustand, QEMU weist die Migration also ab, statt sie zu versuchen. Das ist das richtige Scheitern. Du bekommst einen Fehler, keinen Gast, der auf einem anderen Knoten mit einer verwirrten Platte weiterläuft.

Es gibt einen zweiten, unabhängigen Grund, warum es nicht gehen kann: Proxmox weiß nicht, dass die Platte existiert. Selbst wenn QEMU den Gerätezustand bewegen könnte, würde nichts in der Migrationslogik von PVE dafür sorgen, dass das unterlegte Volume auf dem Ziel verfügbar ist.

Es lohnt sich aber, das zu beobachten: der Entwicklungszweig von QEMU hat das pauschale Flag durch eine Funktion nvme_set_migration_blockers() ersetzt, die Migration erlaubt und sie nur für bestimmte Funktionen sperrt. Mehr als ein Namespace zum Beispiel, wo der Kommentar festhält „we don’t handle this in migration code yet“, das sei im Migrationscode noch nicht behandelt. Das ist in keiner Ausgabe bis einschließlich 10.2 erschienen, es hilft dir also heute nicht, aber diese Einschränkung sieht danach aus, als würde sie weicher. Prüf deine eigene QEMU-Version, statt einem Beitrag zu trauen.

2. Proxmox-Sicherungen werden es nicht sehen

vzdump und der Proxmox Backup Server sichern die Volumes, die in der VM-Konfiguration als Platten erscheinen — scsi0, virtio0 und so weiter. Eine über args: angehängte Platte ist keine davon. Es ist ein rohes QEMU-Gerät, von dem PVE nichts weiß.

Die Sicherung läuft also, meldet Erfolg und enthält das Gerät nicht.

Dieser Fehlerfall ist schlimmer als ein Fehler, denn nichts sagt es dir. Dasselbe gilt auf ganzer Linie: keine PVE-Snapshots, keine Größenänderung der Platte aus der GUI, kein Move Disk, keine Buchung in der Speicheransicht. Hast du das Volume über PVE angelegt und dann abgehängt, räumt PVE es vielleicht auch nicht auf. Ein Waisenkind, das darauf wartet, später jemanden zu verwirren.

Sollen Daten auf einem davon leben, sichere sie im Gast, und schreib irgendwo auf, dass der Hypervisor sie nicht abdeckt.

3. Es ist nicht schneller als VirtIO SCSI

Das überrascht Leute, denn „NVMe“ liest sich wie eine Leistungsfunktion. Hier ist es keine.

VirtIO SCSI und VirtIO Block sind paravirtuell: der Treiber im Gast und der Hypervisor teilen einen Ringpuffer, der für genau diese Aufgabe entworfen ist, und der Gast weiß, dass er mit einem Hypervisor redet.

Der emulierte NVMe-Controller ist von Entwurf her das Gegenteil. Er zeigt echte NVMe-Register, der Gast programmiert ihn also, als wäre er Hardware. Jeder Schreibvorgang auf eine Türklingel ist ein MMIO-Zugriff, der in den Hypervisor fällt. Korrekt, und pro IO teurer, als einen Deskriptor auf einen Ring zu legen.

Ein gemeinsamer Ring gegen emulierte RegisterGast │ HypervisorVirtIO SCSI — paravirtuellTreiber im Gastweiß, dass es eine VM istgemeinsamer Ringbeide Enden verstehen ihnBlock-Schicht des Hosts1 HinweisEin Deskriptor kommt auf den Ring, und der Host wird benachrichtigt. Der Ring liegt mit Absicht über der Grenze.Emuliertes NVMe — echte RegisterTreiber im Gasthält sich für HardwareNVMe-RegisterTürklingeln, Queues, MMIOBlock-Schicht des Hostsjede Türklingel fällt hineinAbfangen + Emulieren, pro IODer Gast tut genau, was er mit einem physischen Controller täte, und das ist der Punkt — sein eigenerTreiber läuft unverändert. Deshalb ist es auch eine Funktion für Kompatibilität und nicht für Leistung.Interrupt-Zusammenfassung ist nicht unterstützt und aus: korrektes Verhalten von Hardware, zum Preis,Hardware zu emulieren.
Der paravirtuelle Weg ist ein Ring, den Gast und Host beide verstehen. Der emulierte Weg lässt den Gast Register ansteuern, und jeder Schreibvorgang auf eine Türklingel fällt hinein — genaues Verhalten von Hardware, zum Preis der Hardware-Emulation.

QEMUs eigene Dokumentation ist über die rauen Kanten des Geräts auch offen: die Zusammenfassung von Interrupts „is not supported and is disabled by default“, sie ist nicht unterstützt und aus, und die Zählwerte in der SMART-/Health-Log-Seite „are reset when the device is power cycled“, sie werden beim Aus- und Einschalten des Geräts zurückgesetzt.

Nichts davon macht es absolut gesehen langsam. Es ist völlig brauchbar. Es heißt bloß, dass du es nie in der Hoffnung auf mehr Durchsatz als VirtIO SCSI wählen solltest. Wähl es wegen des Treibers, oder wegen der NVMe-Semantik.

Wo es seinen Platz wirklich verdient

  • Einen Gast ohne VirtIO-Medium installieren. Ein Windows-Installationsprogramm, das keine VirtIO-SCSI-Platte sieht, sieht eine NVMe-Platte, denn der Treiber ist schon im Abbild. Installiere darauf, und entscheide dann, ob du danach auf VirtIO wechselst.
  • Appliances und Abbilder, die dir nicht gehören. Alles, was als festes Abbild ausgeliefert wird, dem VirtIO-Treiber fehlen und das du lieber nicht neu bauen willst.
  • Lernen und Laborarbeit. nvme format --lbaf, Namespaces anlegen und anhängen, Metadaten und Schutzinformationen — die Handgriffe, die auf echter Hardware zerstörend und herstellerabhängig sind, sind hier kostenlos. Das ist der sicherste Weg, das Muskelgedächtnis aufzubauen, bevor du ein Laufwerk anfasst, auf das es ankommt.
  • Die Topologie eines anderen nachbauen. Suchst du einen Fehler im NVMe-Aufbau eines Kunden, ist ein emulierter Controller mit passenden Namespaces und Blockgrößen eine viel schnellere Schleife, als sich dessen Hardware zu leihen.

Was man produktiv stattdessen nimmt

Für eine VM, die Leistung, PVE-Funktionen und ein ruhiges Leben braucht: VirtIO SCSI single, mit iothread=1, discard=on und ssd=1, auf cache=none. Das ist die Anordnung, die Live-Migration, Sicherungen, Snapshots und die Speicheransicht alle am Laufen hält.

Für eine VM, die die letzten Prozent braucht und diese Funktionen bewusst aufgeben kann, ist die Antwort kein emuliertes NVMe-Gerät. Es ist echtes Passthrough, mit seinen eigenen harten Abwägungen, behandelt in dem Beitrag über die IOMMU-Steuer.

Das emulierte NVMe-Gerät sitzt in keinem der beiden Lager. Damit ist es ein Werkzeug für Kompatibilität und fürs Labor, und darin ist es sehr gut.

Nimm es für die Aufgabe, in der es gut ist, und es wird dich nicht enttäuschen. Verlang von ihm eine Leistungsfunktion, und es enttäuscht dich sehr schnell.

Quellen