IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.

Schlagwort: ZFS (Seite 1 von 3)

Tiered Storage live: Wie ein ZFS special vdev den HDD-Flaschenhals an der Wurzel packt

Ein einzelner ZFS-Pool aus zwei 7200-rpm-Platten war durch Metadaten-Random-I/O ausgebremst. Lösung ganz ohne Neuaufbau: die vorhandenen SSDs zu einem gespiegelten special vdev für Metadaten plus gespiegeltem SLOG umgebaut, zwei zpool add-Befehle im laufenden Betrieb. Resultat: Metadaten-Leselatenz von rund 46 ms auf rund 455 µs, also etwa Faktor hundert, bei voll erhaltener Verschlüsselung und Redundanz.

Drehende Platten sind ein ehrliches Stück Technik. Sie speichern viele Terabyte für wenig Geld und liefern bei sequenziellem Zugriff ordentlichen Durchsatz. Ihre Achillesferse ist der zufällige Zugriff auf viele kleine Blöcke, denn jede Kopfbewegung kostet Latenz im zweistelligen Millisekundenbereich aus Seek und Rotationswartezeit. Und genau dieses ungünstigste Muster produziert ein Copy-on-Write-Dateisystem wie ZFS am laufenden Band: Metadaten. Verzeichnis-ZAPs, dnodes, indirekte Blöcke, also die Block-Pointer-Bäume, dazu Spacemaps. Jedes ls, jedes stat, jeder Snapshot-Vergleich, jeder Scrub und jede find-Traversierung wühlt sich durch viele kleine, über die ganze Platte verstreute Metadatenblöcke. Auf einer HDD ist das der teuerste Spaß, den man haben kann.

Symbolische Darstellung eines ZFS-HDD-Mirrors mit SSD-special-vdev: Metadaten-I/O wird von Festplatten auf schnelle SSDs ausgelagert.

Ich hatte genau diesen Schmerz auf einem dedizierten Server: ein bewusst simpel gehaltener ZFS-Pool, zwei Enterprise-SATA-Platten im Mirror als Kapazitätsspeicher, und ein nagender Verdacht, dass die Spindeln der Flaschenhals sind. Die spannende Frage war nicht, ob man das beheben kann, sondern wie elegant. Die Antwort heißt allocation classes, konkret ein special vdev. Und das Schöne daran: Der Umbau lief komplett im laufenden Betrieb, ohne den Pool neu aufzubauen, ohne Downtime, mit zwei Befehlen. Dieser Beitrag zeigt den ganzen Weg, inklusive der Baseline-Messung, die den Engpass erst beweist, eines Verschlüsselungs-Stolpersteins beim Umbau und der ehrlichen Frage, was so ein special vdev wirklich bringt.

Die Ausgangslage

Der Server läuft auf FreeBSD 15.1-RELEASE (amd64, 12 CPU-Threads, 64 GiB RAM). Ein einziger ZFS-Pool, 2023 ganz bewusst als schlichter Mirror angelegt:

zpool create -o altroot=/mnt -O compress=lz4 -O atime=off -m none -f zroot mirror ada0p3 ada1p3
  • Das Daten-vdev sind zwei 7200-rpm-Enterprise-SATA-Platten mit je 2 TB als Mirror (mirror-0, rund 1,8 TiB nutzbar), der eigentliche Kapazitätsspeicher.
  • Dazu zwei Datacenter-SATA-SSDs mit je 240 GB und Power-Loss-Protection. Die waren vorher suboptimal genutzt: eine als einzelner, nicht gespiegelter SLOG, die andere als L2ARC.
  • ARC-Limit anfangs 16 GiB, poolweit compression=lz4 und atime=off von Anfang an.
  • ashift=12 erzwungen über vfs.zfs.vdev.min_auto_ashift=12, also 4K-Sektor-Alignment, korrekt auch dann, wenn die Platten brav 512-Byte-Sektoren melden.

Die Power-Loss-Protection der SSDs ist kein Detail am Rande, sondern später für die SLOG-Sicherheit relevant: Eine SSD ohne Pufferschutz darf bei einem synchronen Write nicht behaupten, die Daten lägen sicher, solange sie noch im flüchtigen Cache stehen. Datacenter-SSDs mit Kondensator-gestütztem Cache dürfen das, und genau das braucht ein SLOG.

Erst messen, dann bauen

Bevor ich auch nur eine Partition angefasst habe, kam die wichtigste Phase: messen. Ohne Baseline kauft man Hardware nach Bauchgefühl und tunt am falschen Ende. Also lief ein eigener, delta-basierter Sampler über 30 Minuten, 90 Samples zu je 20 Sekunden. Er liest sysctl-Counter für CPU, ARC und Netz sowie iostat -x für die Platten-Busy und die Latenzen. Die wichtigste Spalte zur Einordnung der Last ist net-out, also der ausgehende Netzdurchsatz als Proxy dafür, was während des Laufs tatsächlich los war.

Das Ergebnis der Baseline (16 GiB ARC, alte SSD-Rollen) war eindeutig:

  • Der Flaschenhals ist der HDD-Mirror. Busy im Mittel 58 bis 62 %, Spitzen bis 100 bis 104 %, Latenz im Mittel rund 8 ms, unter Last bis 20 bis 24 ms. In 16 % der Samples war die HDD zu 95 % oder mehr ausgelastet, also gesättigt.
  • Die CPU war zu rund 95 % idle, RAM frei, der Netz-Peak lag bei rund 68 Mbit/s, also nur etwa 7 % des Gigabit-Links. Weder CPU noch RAM noch Netz waren das Limit.
  • Der ARC klebte an seinem 16-GiB-Limit (Mittel 15,7 GiB) bei einer Hit-Rate von rund 94,7 %. Der ARC war schlicht ausgehungert und hätte mehr RAM sofort genutzt.
  • Der einzelne SLOG lief bei rund 42 % Busy, war also nicht gesättigt. Die Spindeln waren das Limit, nicht der SLOG.

Das ist die didaktische Pointe, die ich jedem ans Herz lege: Ohne diese Messung wüsste ich nicht, ob CPU, RAM, Netz oder Platten klemmen, und ich wüsste nicht, ob das Problem auf der Lese- oder der Schreibseite liegt. Messen ist kein Nice-to-have, sondern die Voraussetzung dafür, das richtige Bauteil zu kaufen und am richtigen Hebel zu drehen.

Was ein special vdev ist, und warum nicht einfach All-SSD

Allocation classes sind ein OpenZFS-Feature (feature@allocation_classes), mit dem ein Pool mehrere Klassen von vdevs führen kann. Das special vdev ist die Klasse für Metadaten: ZFS legt dnodes, indirekte Blöcke und poolweite Metadaten bevorzugt dort ab statt auf dem normalen Daten-vdev. Über die Dataset-Property special_small_blocks kann man zusätzlich kleine Datenblöcke unterhalb einer einstellbaren Schwelle aufs special vdev ziehen. Im Kern verschiebt man also genau die Datenklasse, die eine HDD am schlechtesten beherrscht, auf ein Medium, das genau dafür gebaut ist.

Dass das hier der richtige Hebel ist, ist nicht geraten, sondern messbar: Der ARC dieses Servers besteht zu rund 85 % aus Metadaten, konkret 17,4 GB Metadaten gegenüber 3,0 GB Daten im ARC. Der Workload ist also metadaten-dominiert. Metadaten auf SSD zu verlagern trifft den Engpass damit an der Wurzel, denn das ist exakt der Random-I/O, an dem die Platten am meisten leiden. Bevor ich mich für das special vdev entschieden habe, standen aber andere Optionen auf dem Tisch:

  • Kompletter All-SSD-Pool aus zwei großen SSDs: der sauberste Komplettfix, aber teuer und ein großer Umbau mit Pool-Neuaufbau und vollständiger Datenmigration. Overkill, wenn der Großteil der Kapazität aus kalten, überwiegend sequenziell gelesenen Daten besteht.
  • Mehr RAM und ARC: hilft nur der Leseseite und nur, solange der Working Set in den ARC passt. Schreib-Metadaten müssen trotzdem auf stabilen Speicher, daran ändert RAM nichts.
  • L2ARC behalten: abgeschafft. Bei 24 GiB ARC lag die Lese-Hit-Rate schon bei rund 98,5 %. Der L2ARC brachte nur rund 1,3 % zusätzliche Reads, ist flüchtig (nach einem Reboot leer) und kostet sogar ARC-RAM für seine Header. Das Kosten-Nutzen-Verhältnis war negativ.
  • special vdev: die gewählte Lösung. Nutzt die vorhandenen SSDs, kein Pool-Neuaufbau, adressiert exakt den gemessenen Metadaten-Schmerz, inkrementell und live im Betrieb machbar.

Der Umbau Schritt für Schritt

Aus dem alten Zustand mit einem einzelnen SLOG und einem L2ARC sollte ein SLOG-Mirror plus ein special-vdev-Mirror werden. Beide SSDs werden also jeweils zur Hälfte für beide Zwecke genutzt, jeweils gespiegelt. Zuerst die alten Single-Rollen entfernen:

zpool remove zroot ada3p1     # alter L2ARC
zpool remove zroot ada2p1     # alter (einzelner) SLOG

Und hier kam der erste Stolperstein, der so lehrreich ist, dass er einen eigenen Absatz verdient. Das SLOG-Remove schlug zunächst fehl:

cannot remove ada2p1: Mount encrypted datasets to replay logs

Die Ursache: Es existierten verschlüsselte Datasets, deren Keys in diesem Boot nie geladen waren. Der SLOG lässt sich nicht entfernen, solange potenziell noch nicht abgespielte ZIL-Einträge für gesperrte Datasets vorliegen, denn ZFS müsste diese Einträge zum Replay erst entsperren. Erst nach dem Aufräumen und Entsperren ließ sich der SLOG sauber entfernen. Das ist gleichzeitig die perfekte Überleitung zum Verschlüsselungskapitel weiter unten, denn es zeigt, wie tief native ZFS-Encryption in den ZIL-Pfad eingreift.

Danach die SSDs neu partitionieren, sauber 1-MiB-aligned. Pro SSD wird p1 16 GiB groß (SLOG) und p2 rund 208 GiB (special). Das Ergebnis von gpart show ada2 ada3:

=>       40  468862048  ada2  GPT  (224G)
         40       2008        - free -  (1004K)
       2048   33554432     1  freebsd-zfs  (16G)     # p1 -> SLOG
   33556480  435304448     2  freebsd-zfs  (208G)    # p2 -> special
  468860928       1160        - free -  (580K)

Jetzt der eigentliche Akt: gespiegelter SLOG und gespiegeltes special vdev werden hinzugefügt.

zpool add zroot log     mirror ada2p1 ada3p1
zpool add zroot special mirror ada2p2 ada3p2

Beide Befehle bewusst ohne -f. So bleibt der Redundanz-Schutz von ZFS als Sicherheitsnetz aktiv: ZFS verweigert ein nicht-redundantes special oder log neben einem Mirror, solange man es nicht ausdrücklich erzwingt. Und genau dieses Verweigern ist hier gewollt.

Die wichtigste Warnung dieses Beitrags: Ein special vdev ist nicht optional für die Pool-Integrität. Verliert man ein nicht gespiegeltes special vdev, ist der gesamte Pool verloren, denn die Metadaten liegen dort, und ohne sie ist der Rest unlesbar. Das special vdev muss mindestens so redundant sein wie das Daten-vdev, hier also als Mirror. Für den SLOG gilt das in dieser Schärfe nicht, ein verlorener SLOG kostet nur die letzten Sekunden async-bestätigter sync-Writes, aber ein SLOG-Mirror verhindert, dass ein einzelner SSD-Ausfall den ZIL-Schutz aushebelt.

Das fertige Layout sieht in zpool status und zpool list -v dann so aus:

zroot       mirror-0   ada0p3 + ada1p3   1.80T  (Daten, HDD-Mirror)
            special    mirror-3: ada2p2 + ada3p2   206G  (Metadaten, SSD-Mirror)  NEU
            logs       mirror-2: ada2p1 + ada3p1   15.5G (ZIL/SLOG, jetzt gespiegelt)

Zum SLOG-Sizing noch ein Wort, weil es oft falsch gemacht wird. Der SLOG puffert nur die dirty data eines, maximal zweier txg-Flush-Intervalle. Bei vfs.zfs.dirty_data_max = 4 GiB reichen 16 GiB SLOG mit großzügigem Polster, mehr bringt schlicht nichts. Genauso wichtig: Der SLOG beschleunigt nichts direkt. Er ist nur ein schnelles, stromausfallsicheres Zwischenlager für den ZIL, greift ausschließlich bei synchronen Writes (fsync oder O_SYNC) und wird im Normalbetrieb nie gelesen, sondern erst nach einem Crash zum Replay. Wer das verwechselt, sollte sich die Trennung einprägen: Der ZIL ist immer da, das ist das Konzept. Der SLOG ist nur ein optionales separates Gerät dafür.

Die unbequeme Wahrheit: nur neue Metadaten wandern

Hier muss ich ehrlich sein, denn es ist der am häufigsten missverstandene Punkt. Ein special vdev migriert keine bestehenden Metadaten. Es nimmt nur auf, was nach dem Hinzufügen geschrieben wird. Alte Metadaten bleiben auf der HDD liegen, bis sie durch Copy-on-Write ohnehin neu geschrieben werden. Der volle Effekt entsteht also erst über die Zeit oder durch einen optionalen zfs send | zfs recv-Rebuild der großen Datasets. Kein Sofort-magisch-alles-schneller, sondern ein Mechanismus, der sich befüllt. Dass er sich befüllt, sieht man an der Belegung, die mit jedem neuen Metadaten-Write wächst:

special   mirror-3   206G   alloc 5.38G   free 201G   FRAG 27%   CAP 2.60%

Die Messung danach, und wie man sie ehrlich liest

Jetzt kommt der Teil, an dem viele Tuning-Berichte unsauber werden, weil sie einen Vorher-Nachher-Durchsatz behaupten, der unter unterschiedlicher Last gemessen wurde und damit nichts beweist. Ich gehe einen anderen Weg und zeige die Wirkung über drei Argumente, von denen zwei komplett lastunabhängig sind.

Erstens der Latenz-Split pro vdev, das stärkste und lastunabhängige Argument. zpool iostat -lv zeigt die Latenzen getrennt pro vdev. Die folgende Tabelle sind seit-Boot-kumulierte Mittelwerte, also langzeit-repräsentativ und kein zufälliger Augenblick:

                  capacity     operations     bandwidth    total_wait
vdev            alloc   free   read  write   read  write   read   write
mirror-0        1.22T   595G     45      7   434K   601K   46ms   34ms    # HDD (Daten)
  ada0p3                         22      3   217K   300K   56ms   39ms
  ada1p3                         23      3   217K   300K   36ms   29ms
special/mirror-3 5.38G  201G      0     67  5.28K  3.24M  455us    6ms    # SSD (Metadaten)
  ada2p2                          0     33  2.67K  1.62M  447us    5ms
  ada3p2                          0     33  2.61K  1.62M  464us    6ms
logs/mirror-2   31.6M  15.5G      0     45      3   947K    2ms    1ms    # SSD (SLOG/ZIL)

Die Kernaussage steht in zwei Zahlen: Metadaten-Leselatenz 455 µs auf der special-SSD gegen 46 ms auf der HDD, das ist etwa Faktor hundert. Jeder Metadaten-Zugriff, der nicht ohnehin aus dem RAM bedient wird, ist seitdem rund hundertmal schneller. Zu den -l-Spalten kurz: total_wait ist die Gesamtwartezeit inklusive Queue, disk_wait die reine Gerätelatenz, syncq_wait und asyncq_wait die Zeit in den ZFS-internen Queues. Wer ein echtes Zeitfenster statt des Boot-Mittels sehen will, nimmt zpool iostat -lv zroot 10 2 und liest das zweite Sample, denn das erste ist immer der Seit-Boot-Durchschnitt.

Zweitens die ARC-Metadaten-Aufteilung, also die Struktur des Workloads. Sie erklärt, warum es gerade hier so viel bringt:

arcstats.metadata_size          = 17.4 GB     # rund 85 % des ARC sind Metadaten
arcstats.data_size              =  3.0 GB
arcstats.demand_metadata_hits   = 1,133,349,218
arcstats.demand_metadata_misses =    11,594,376   # müssen auf Platte ... jetzt SSD
arcstats.demand_data_hits       =   220,864,693
arcstats.demand_data_misses     =       672,908

Der Workload ist metadaten-dominiert. Die Lifetime-ARC-Hit-Rate liegt bei rund 98,9 %, aber die über 11,5 Millionen Metadaten-Misses müssen zwangsläufig auf Platte, und sie landen jetzt auf SSD statt auf HDD. Hier multipliziert sich der Faktor-hundert-Latenzvorteil mit der schieren Menge an Metadaten-Operationen. Das ist die quantitative Begründung dafür, warum ausgerechnet ein special vdev der wirksamste Hebel war und nicht etwa nur mehr ARC. Begleitend habe ich das ARC-Limit von 16 auf 24 GiB angehoben, weil RAM frei war. Die Folge war eine Hit-Rate von rund 95 % auf rund 99 %. Zwei Hebel, die zusammenwirken: weniger Misses überhaupt, und die verbliebenen sind jetzt SSD-schnell.

Das Herzstück: die 8,5-MB/s-Rechnung

Drittens, und das ist der eigentliche Aha-Moment, eine logische Schlussfolgerung statt eines Durchsatz-Vergleichs. Die Ausgangsmessung lief unter einer ganz konkreten Last: Ein Client lud zeitgleich größere Dateien herunter, ein klassischer Datei-Download. Der Netzdurchsatz dabei lag bei rund 68 Mbit/s, also etwa 8,5 MB/s. Und genau hier wird es interessant.

Eine einzelne 7200-rpm-HDD liefert sequenziell 150 bis 200 MB/s. Ein Download mit 8,5 MB/s ist also kaum 5 % dessen, was eine Platte im Schlaf kann, und hier zogen sogar zwei davon im Mirror mit. Trotzdem zeigte die Messung, dass der HDD-Mirror im Mittel rund 60 % ausgelastet war und in 16 % der Messintervalle voll gesättigt (95 % Busy oder mehr), mit Latenzen bis 20 bis 24 ms.

Das ist ein Widerspruch, und der Widerspruch ist der Beweis. Für sequenzielle 8,5 MB/s darf eine HDD niemals an die Sättigung kommen. Wenn sie es doch tut, dann waren diese Zugriffe nicht sequenziell, sondern seek-gebunden. Die Köpfe wurden permanent quer über die Platte gerissen. Wofür? Für das, was dieses System zu rund 85 % beschäftigt: Metadaten-Random-I/O, also dnodes, indirekte Blöcke und Verzeichnis-Lookups, die sich auf denselben zwei Spindeln mit dem Download um die Köpfe prügelten, verschärft durch die damals hohe Fragmentierung. Ein eigentlich harmloser Download zerfiel so in ein Seek-Gewitter.

Genau diese Konkurrenz wurde mit dem special vdev eliminiert. Die Metadaten-Zugriffe laufen jetzt auf den SSDs mit rund 455 µs statt zig Millisekunden. Die HDD-Köpfe können auf dem Datenstrom bleiben, statt ständig für Metadaten wegzuspringen. Derselbe Download belastet die Spindeln damit nur noch einen Bruchteil. Nicht, weil die Dateidaten schneller kämen, die liegen weiter auf HDD, sondern weil der Lärm daneben weg ist. Diese Schlussfolgerung steht ohne erfundenen Vergleich, sie ist wasserdicht: 8,5 MB/s sättigt physikalisch keine HDD, also waren es Seeks, also Metadaten-Kontention, und genau die habe ich verlagert.

Wie sich der Pool im ruhigen Normalbetrieb anfühlt, zeigt eine zweite, entspannte Momentaufnahme. Sie ist ausdrücklich kein Vorher-Nachher-Vergleich, sondern nur ein Blick auf den Alltag:

CPU idle 96.9 %   ARC hit 99.7 %   ARC 23.3 GiB
HDD busy ~2 %     HDD-Sättigung 0 %   HDD-Latenz ~1.5 ms
SSD busy 4.3 % / 4.5 % (gleichmäßig über beide Mirror-Member)
net-out-Peak 2.0 Mbit/s

Im ruhigen Normalbetrieb langweilt sich der HDD-Mirror, fast alle Reads kommen aus ARC oder SSD. Das illustriert den Alltag. Die eigentliche Wirkung des Umbaus zeigen aber die 8,5-MB/s-Rechnung oben sowie der Latenz-Split und die ARC-Aufteilung, und die gelten unabhängig von der Last.

Sicherheit und Verschlüsselung, die entscheidende Nuance

Die wichtigen Datasets dieses Systems sind nativ mit ZFS verschlüsselt (encryption = aes-256-gcm), die System- und Boot-Datasets nicht. Sobald man ein special vdev einführt, stellt sich sofort die sicherheitskritische Frage: Landet jetzt unverschlüsselter Klartext auf den SSDs, nur weil dort die Metadaten liegen? Die Antwort ist ein klares Nein, und die Begründung ist wichtig genug, um sie sauber auszuführen.

  • ZFS native encryption verschlüsselt Dateiinhalte und die sensiblen Objekt-Metadaten, also Dateinamen, Verzeichnisstruktur, dnodes, Attribute und ACLs. Diese Blöcke sind bereits Ciphertext, bevor der Allocator überhaupt entscheidet, auf welches vdev sie wandern. Ein special vdev ist nur ein anderer Ablageort und ändert an der Verschlüsselung nichts. Verschlüsselte Metadaten bleiben auf der special-SSD verschlüsselt.
  • Was ZFS-Encryption ohnehin nicht verbirgt, special vdev hin oder her, sind die Metadaten auf Pool- und Dataset-Ebene: Dataset-Namen, Pool-Struktur, Anzahl und Größe von Snapshots, die Blockpointer-Struktur. Das ist eine Eigenschaft von ZFS-Encryption und keine neue Schwäche durch das special vdev.
  • aes-256-gcm ist authenticated encryption (AEAD), liefert also Vertraulichkeit und gleichzeitig Integritäts- und Authentizitätsschutz der verschlüsselten Blöcke.

Ein schöner Praxisbezug schließt sich hier zum Umbau-Kapitel: Genau weil verschlüsselte Datasets im Spiel sind, blockierte das zpool remove mit der Meldung über das Mounten verschlüsselter Datasets zum Replay. Das zeigt anschaulich, wie tief Encryption in den ZIL- und SLOG-Pfad eingreift, denn der ZIL kann Einträge für verschlüsselte Datasets enthalten, die sich nur nach dem Entsperren abspielen lassen. Das Fazit zur Sicherheit ist damit eindeutig: Ein special vdev ist verschlüsselungs-neutral. Wer verschlüsselte Datasets nutzt, bekommt verschlüsselte Metadaten auf der special-SSD, kein Klartext-Leak.

Abwägung: Vorteile, Nachteile, Risiken

Was unterm Strich für das special vdev spricht:

  • Es adressiert den gemessenen Engpass, Metadaten-Random-I/O, direkt an der Wurzel.
  • Es nutzt vorhandene SSDs, also keine Neuanschaffung, kein Pool-Neuaufbau, live im laufenden Betrieb hinzugefügt.
  • Rund hundertfach niedrigere Metadaten-Leselatenz (455 µs gegen 46 ms), spürbar bei ls, stat, find, Snapshots, Scrub und allen Workloads mit vielen kleinen Dateien.
  • Über special_small_blocks später fein justierbar, um kleine Datenblöcke nachzuziehen, ohne Downtime und nur für neue Writes.
  • Verschlüsselungs-neutral.
  • Der I/O verteilt sich jetzt gleichmäßig über beide Mirror-Member. Vorher lag eine SSD als einzelner SLOG bei rund 42 % Busy, die andere als L2ARC quasi brach.

Und ehrlich auch die andere Seite, denn ein special vdev ist kein Selbstläufer:

  • Redundanz ist Pflicht, nicht Kür. Ein nicht-redundantes special vdev bedeutet Totalverlust des Pools bei SSD-Ausfall. Mirror ist zwingend.
  • Keine Migration bestehender Metadaten. Nur neue Writes wandern, der volle Effekt kommt erst per send und recv-Rebuild.
  • Das special vdev kann volllaufen. Ist es voll, fallen neue Metadaten auf das langsame Daten-vdev zurück. Das ist kein Fehler, aber der Effekt lässt nach, also Füllgrad mit zpool list -v überwachen.
  • special_small_blocks zu hoch gesetzt verstopft das special vdev mit Datenblöcken und lässt es schneller volllaufen. Vorsichtig hochtasten (von 0 über 4K und 8K bis vielleicht 32K) und dabei den Füllgrad beobachten.
  • Mehr vdevs bedeuten mehr Komplexität und mehr Teile, die ausfallen können. Den SSD-Wear im Blick behalten, hier bewusst Datacenter-SSDs mit Power-Loss-Protection gewählt, weil sie Dauerlast und sync-Writes aushalten.
  • Der zpool remove-Stolperstein mit verschlüsselten Datasets gehört dokumentiert, damit man im Ernstfall nicht in Panik gerät.

Die eigentliche Botschaft

ZFS erlaubt es, die Storage-Architektur inkrementell und im laufenden Betrieb an einen gemessenen Engpass anzupassen, ohne Pool-Neuaufbau, ohne Downtime, ohne Datenmigration als Vorbedingung. Aus einem simplen HDD-Mirror wurde durch zwei zpool add-Befehle ein hybrider, mehrstufiger Pool: kalte Massendaten auf günstigen Spindeln, heiße Metadaten und optional kleine Blöcke auf schnellen SSDs, synchrone Writes über einen gespiegelten SLOG. Diese Flexibilität, tiered storage als Live-Operation, kombiniert mit Checksumming, Compression, Snapshots und nativer Verschlüsselung im selben Dateisystem, ist der eigentliche Kern. Man kauft sich SSD-Speed genau dort, wo die Messung den Schmerz zeigt, und lässt den Rest günstig auf HDD. Kein anderes verbreitetes Dateisystem macht das so geradlinig.

Ausblick

  • special_small_blocks schrittweise anheben, um kleine Dateien und nicht nur Metadaten auf SSD zu ziehen, live und nur für neue Writes.
  • Ein optionaler send und recv-Rebuild der großen Datasets, um bestehende Metadaten auf das special vdev zu migrieren und so den vollen Effekt zu heben.
  • Eine lastgleiche Wiederholungsmessung in einem Hochlast-Fenster für eine saubere Zahl auf der Schreibseite.

Spickzettel

Die Befehle, mit denen man Layout, Latenzen und ARC-Komposition selbst nachsieht:

# Pool-Layout und Auslastung pro vdev
zpool status zroot
zpool list -v zroot

# Latenzen pro vdev (das Geld-Kommando), 2. Sample lesen für ein echtes Zeitfenster:
zpool iostat -lv zroot 10 2

# ARC: Größe und Metadaten/Daten-Split plus Demand-Hits und -Misses
sysctl kstat.zfs.misc.arcstats.size kstat.zfs.misc.arcstats.metadata_size kstat.zfs.misc.arcstats.data_size kstat.zfs.misc.arcstats.demand_metadata_hits kstat.zfs.misc.arcstats.demand_metadata_misses

# allocation_classes-Feature und special_small_blocks
zpool get feature@allocation_classes zroot
zfs get special_small_blocks zroot

# Verschlüsselungs-Status der Datasets
zfs get encryption,keystatus DATASET

# SSD-Partitionierung
gpart show ada2 ada3

# SLOG-Sizing-Kontext
sysctl vfs.zfs.dirty_data_max

# Pool-Historie (zeigt die echten add/remove-Befehle)
zpool history zroot

Siehe auch:

Selbst einen HDD-Pool mit einem special vdev entschärft, oder noch am Abwägen, ob sich der Umbau lohnt? Erzähl mir gern von deinem Layout, oder stell deine fragen.

FreeBSD: Unverschlüsseltes ZFS-Dataset nachträglich verschlüsseln

Bild der Bücher FreeBSD Mastery ZFS und FreeBSD Mastery Advanced ZFS

ZFS Encryption lässt sich nicht nachträglich auf ein bestehendes Dataset aktivieren. Die Daten müssen per zfs send | zfs receive in ein neues, verschlüsseltes Dataset geschrieben werden. Typischer Anwendungsfall: Jails, die in eigenen Datasets liegen und nach einem FreeBSD-Upgrade auf 13+ verschlüsselt werden sollen.

Wer die Grundlagen zu ZFS Encryption noch braucht (Dataset anlegen, Passphrase, Mount nach Reboot), findet sie im Beitrag Native ZFS Encryption einrichten.

Ausgangslage

Ein Pool zroot mit dem unverschlüsselten Dataset varta:

zfs list zroot/varta
NAME          USED  AVAIL     REFER  MOUNTPOINT
zroot/varta   100M  12.0G      100M  /zroot/varta

zfs get encryption zroot/varta
NAME         PROPERTY    VALUE   SOURCE
zroot/varta  encryption  off     default

Migration

Bei zfs send | zfs receive kann man kein Passphrase interaktiv eingeben, weil stdin durch die Pipe belegt ist. Deshalb legt man den Schlüssel temporär in einer Datei ab:

echo 'MeinGeheimesPassphrase' > /tmp/keyfile.txt
chmod 600 /tmp/keyfile.txt

Snapshot erstellen und in ein neues verschlüsseltes Dataset senden:

zfs snapshot zroot/varta@migration

zfs send zroot/varta@migration | \
  zfs receive -F \
  -o encryption=on \
  -o keyformat=passphrase \
  -o keylocation=file:///tmp/keyfile.txt \
  zroot/en-varta

Das neue Dataset zroot/en-varta enthält jetzt dieselben Daten, verschlüsselt mit AES-256-GCM:

zfs list zroot/varta zroot/en-varta
NAME             USED  AVAIL     REFER  MOUNTPOINT
zroot/en-varta   100M  11.8G      100M  /zroot/en-varta
zroot/varta      100M  11.8G      100M  /zroot/varta

Aufräumen

Die Keyfile-Referenz auf Passphrase-Abfrage umstellen und die temporäre Datei löschen:

zfs set keylocation=prompt zroot/en-varta

zfs get keylocation zroot/en-varta
NAME            PROPERTY     VALUE   SOURCE
zroot/en-varta  keylocation  prompt  local

rm /tmp/keyfile.txt

Dann das alte Dataset entfernen und das neue umbenennen:

zfs destroy -r zroot/varta
zfs rename zroot/en-varta zroot/varta

Fertig. Das Dataset liegt jetzt verschlüsselt am selben Mountpoint wie vorher:

zfs get encryption,keylocation,keyformat zroot/varta
NAME         PROPERTY     VALUE        SOURCE
zroot/varta  encryption   aes-256-gcm  -
zroot/varta  keylocation  prompt       local
zroot/varta  keyformat    passphrase   -

Stolperfallen

Keyfile in /tmp ist ein Kompromiss. Auf FreeBSD-Default ist /tmp zwar ein tmpfs im RAM, aber bei Speicherdruck kann das System swappen. chmod 600 schützt gegen andere Benutzer, nicht gegen root, Backup-Prozesse oder einen Crash-Dump. Sauberer ist eine keylocation auf einem schon verschlüsselten Dataset oder ein FIFO statt einer regulären Datei. In beiden Fällen existiert die Passphrase nie persistent auf der Platte.

Properties gehen beim Send verloren. Ohne -R (Replication Stream) oder explizite -o-Optionen werden compression, recordsize, quota, reservation, atime und eigene Properties nicht mitübertragen. Wer compression=zstd oder recordsize=1M gesetzt hatte, muss die beim receive wieder setzen oder direkt einen Replication Stream nutzen.

Mountpoint nach dem Rename prüfen. Lag das alte Dataset auf /var/jails/varta, muss der Mountpoint nach dem Rename wieder explizit gesetzt werden, sonst landet das Dataset am Default-Pfad des Pools:

zfs set mountpoint=/var/jails/varta zroot/varta

Jail vorher stoppen. service jail stop <name> bzw. iocage stop oder bastille stop. Sonst sind Dateien offen, zfs destroy -r schlägt fehl und der Rename macht keinen Spaß.

Inkrementelle Migration für große Datasets

Bei Jails mit Datenbank, Mailspool oder einfach mehreren hundert GB ist Downtime gleich Send-Laufzeit nicht akzeptabel. Besser: initial senden während der Dienst läuft, dann nur kurz stoppen und den inkrementellen Rest nachziehen.

# Phase 1: initialer Send im laufenden Betrieb
zfs snapshot zroot/varta@migration-1
zfs send zroot/varta@migration-1 | \
  zfs receive -F \
  -o encryption=on \
  -o keyformat=passphrase \
  -o keylocation=file:///tmp/keyfile.txt \
  zroot/en-varta

# Phase 2: Dienst stoppen, inkrementelles Delta senden
service jail stop varta
zfs snapshot zroot/varta@migration-2
zfs send -i @migration-1 zroot/varta@migration-2 | \
  zfs receive zroot/en-varta

# Phase 3: Rename und Neustart
zfs destroy -r zroot/varta
zfs rename zroot/en-varta zroot/varta
zfs set mountpoint=/var/jails/varta zroot/varta
service jail start varta

Die Downtime reduziert sich so auf den inkrementellen Send plus Rename, bei einer typischen Mailbox sind das Sekunden statt Minuten. Bei sehr aktiven Datasets kann man mehrere inkrementelle Snapshots nacheinander schieben, bis das Delta klein genug für die gewünschte Zielzeit ist.

Update 2026: was heute dazugekommen ist

OpenZFS 2.x ist auf FreeBSD 14 und 15 stabil. Der eigentliche Migrationspfad hat sich nicht geändert, drumherum ist einiges dazugekommen:

Raw encrypted send/receive. Mit zfs send -w wird ein verschlüsseltes Dataset ohne Entschlüsselung übertragen. Der Remote-Host lagert den Stream, kennt den Schlüssel nicht und sieht keinen Klartext. Für Offsite-Backups der saubere Weg, sobald die Migration durch ist:

zfs snapshot zroot/varta@backup
zfs send -w -R zroot/varta@backup | \
  ssh backup@remote zfs receive backuppool/varta

Andere Keyformate. Statt keyformat=passphrase gehen auch keyformat=raw (32-Byte-Keyfile, z.B. aus HSM oder TPM) oder keyformat=hex (64 Zeichen Hex). keylocation=https://... holt den Schlüssel beim Pool-Import von einer URL, praktisch für headless Systeme, bei denen die Passphrase-Abfrage beim Boot stört.

Kompression vor Verschlüsselung. ZFS komprimiert zuerst, verschlüsselt danach. compression=zstd zusammen mit encryption=on wirkt also wie erwartet, und der komprimierte Zustand bleibt auch im Raw Stream erhalten.

Nur für Datenpools und Non-Root-Datasets. Diese Anleitung deckt Jails, Home-Verzeichnisse, Mailspools und ähnliches ab. Verschlüsseltes Root-on-ZFS ist ein eigenes Thema und braucht zusätzlich Boot-Environment-Handling (bectl) sowie ein geli-verschlüsseltes Swap.

Siehe auch

Native ZFS Encryption einrichten und nutzen für die Grundlagen zu Dataset, Passphrase und Mount nach Reboot. Verschlüsseltes ZFS-Backup auf USB-Platte mit geli für die Offline-Sicherung. Linux Mint mit verschlüsseltem ZFS-Root-Pool installieren für das Linux-Pendant mit Root-Pool.

Eine Übersicht über alle ZFS-Funktionen gibt es im ZFS-Überblick. Fragen? Einfach melden.

TRIM für SSDs im ZFS-Pool unter Linux aktivieren

Speicherzellen einer SSD sind nicht unendlich beschreibbar. Damit sie möglichst lange halten, verteilt die interne Logik der SSD Schreibzugriffe gleichmäßig über alle Bereiche — Wear Leveling. Dafür muss die SSD aber wissen, welche Blöcke wirklich frei sind. Diese Information kann sie nur vom Dateisystem bekommen. Genau dafür gibt es TRIM.

Wer sich für TRIM allgemein interessiert — im älteren Beitrag zu TRIM für SSDs und Flash-Speicher steht mehr zu den Grundlagen. Hier geht es nur um ZFS.

autotrim — kontinuierliches TRIM

ZFS kennt die Pool-Eigenschaft autotrim. Wenn aktiviert, schickt ZFS nach jedem Freigeben von Blöcken automatisch TRIM-Befehle an die darunterliegenden Geräte. Prüfen, ob es aktiv ist:

$ zpool get autotrim bpool rpool
NAME   PROPERTY  VALUE  SOURCE
bpool  autotrim  off    local
rpool  autotrim  off    local

Aktivieren:

$ zpool set autotrim=on bpool
$ zpool set autotrim=on rpool

Kontrolle — jetzt sollte on stehen:

$ zpool get autotrim bpool rpool
NAME   PROPERTY  VALUE  SOURCE
bpool  autotrim  on     local
rpool  autotrim  on     local

zpool trim — manuelles TRIM

Neben dem kontinuierlichen autotrim gibt es auch manuelles TRIM. Das ist sinnvoll, wenn man autotrim bisher nicht aktiviert hatte und den Pool einmalig aufräumen will:

$ zpool trim rpool

Der Fortschritt lässt sich mit zpool status -t rpool beobachten. Bei großen Pools kann das eine Weile dauern.

Tipp: SSD-Wartungsmodus

Wenn eine SSD nur am Strom hängt, ohne dass ein Rechner darauf zugreift, fallen viele SSDs in einen internen Wartungs- und Reparaturmodus. Die Firmware sortiert dann selbstständig Speicherzellen um, führt Diagnosen durch und versucht schwache Zellen zu retten. Einfach 2–3 Stunden laufen lassen. Über diesen Weg lassen sich manchmal sogar „tote“ SSDs wiederbeleben. Mit aktivem TRIM funktioniert das alles natürlich deutlich besser.

Hinweis für FreeBSD: autotrim und zpool trim funktionieren dort genauso — FreeBSD nutzt seit langem OpenZFS.

Fragen? Einfach melden.

Linux Mint mit verschlüsseltem ZFS-Root-Pool installieren

ZFS als Root-Dateisystem unter Linux — will man haben. Verschlüsselung auf der Festplatte — will man auch haben. Die Kombination aus beidem war unter Linux lange ein manueller Kraftakt. Seit Linux Mint 20 bringt der Installer aber fast alles mit. Man muss ihm nur an zwei Stellen unter die Arme greifen.

Linux Mint Boot-Bildschirm: ZFS-Pool-Passwortabfrage nach erfolgreicher Verschluesselung.

Hinweis: Diese Anleitung wurde mit Linux Mint 20.1 geschrieben, funktioniert aber genauso mit Mint 21.x und 22.x — der Installer nutzt das gleiche zsys-setup-Skript.

Pakete im Live-System nachinstallieren

Die gewünschte Linux Mint ISO herunterladen und davon booten. Im Live-System ein Terminal öffnen und die ZFS-Pakete installieren:

sudo aptitude -y install libzfs2linux zfs-initramfs zfsutils-linux zfs-zed

Wer nur ZFS ohne Verschlüsselung will, ist damit schon fertig. Einfach den Installer starten, bei der Festplattenauswahl auf Advanced features… klicken und EXPERIMENTAL: Erase disk and use ZFS wählen. Für Verschlüsselung weiter lesen.

Installer-Skript für Verschlüsselung anpassen

Der Installer fragt kein Passwort für die ZFS-Verschlüsselung ab — das muss man ihm vorher ins Skript schreiben. Im Terminal:

sudo vi /usr/share/ubiquity/zsys-setup

Nach zpool create suchen — es taucht zweimal auf. Beim rpool (nicht beim bpool) zwei Änderungen vornehmen:

1. Vor die zpool create-Zeile des rpool ein echo mit dem gewünschten Passwort setzen:

echo 'EUER-PASSWORT' | zpool create -f \

2. Vor die Zeile -O mountpoint=/ -R "${target}" rpool "${partrpool}" die Encryption-Optionen einfügen:

-O encryption=aes-256-gcm \
-O keylocation=prompt \
-O keyformat=passphrase \

Das fertige Ergebnis sieht dann so aus:

echo 'Kennwort!' | zpool create -f \
        -o ashift=12 \
        -O compression=lz4 \
        -O acltype=posixacl \
        -O xattr=sa \
        -O relatime=on \
        -O normalization=formD \
        -O mountpoint=/ \
        -O canmount=off \
        -O dnodesize=auto \
        -O sync=disabled \
        -O encryption=aes-256-gcm \
        -O keylocation=prompt \
        -O keyformat=passphrase \
        -O mountpoint=/ -R "${target}" rpool "${partrpool}"

Sicherheitshinweis: Das Passwort steht kurzzeitig im Klartext in der Datei und in der Bash-History. Im Live-System ist das unkritisch — nach dem Reboot ins installierte System ist das Live-System weg. Trotzdem nicht das Produktivpasswort in Screenshots posten.

Jetzt den Installer starten, ZFS als Dateisystem wählen — fertig. Funktioniert mit EFI und Legacy-Boot.

Verschlüsselung prüfen

Nach dem ersten Boot ins neue System — Terminal öffnen und prüfen, ob die ZFS-Verschlüsselung aktiv ist und sich durch den Pool bis zu den Benutzerdaten vererbt hat:

$ zpool get feature@encryption bpool rpool
NAME   PROPERTY            VALUE     SOURCE
bpool  feature@encryption  disabled  local
rpool  feature@encryption  active    local

$ zfs get encryption rpool/USERDATA/test_9d9i92
NAME                        PROPERTY    VALUE        SOURCE
rpool/USERDATA/test_9d9i92  encryption  aes-256-gcm  -

feature@encryption: active beim rpool und aes-256-gcm auf den Benutzerdaten — alles korrekt. Der bpool (Boot-Pool) bleibt unverschlüsselt — GRUB muss den Kernel lesen können, bevor das Passwort eingegeben wird.

Wer seine SSDs im ZFS-Pool auch gleich mit TRIM versorgen will — das ist in einem separaten Beitrag beschrieben.


Bilder von der Installation zum Durchklicken:

Siehe auch: ZFS Encryption

Fragen? Einfach melden.

FreeBSD: Native ZFS Encryption einrichten und nutzen

Seit FreeBSD 13 steht native ZFS Encryption zur Verfügung. Datasets lassen sich mit AES-256-GCM verschlüsseln, ohne dass der gesamte Pool verschlüsselt sein muss. Die Verschlüsselung greift pro Dataset und vererbt sich auf Kind-Datasets.

Verschlüsseltes Dataset anlegen

Ein neues Dataset mit Passphrase-Abfrage:

zfs create -o encryption=aes-256-gcm -o keyformat=passphrase usbpool/test01
Enter passphrase:
Re-enter passphrase:

Das Dataset wird sofort gemountet und ist einsatzbereit. Alles was hineingeschrieben wird, liegt verschlüsselt auf der Platte:

zfs list usbpool/test01
NAME             USED  AVAIL     REFER  MOUNTPOINT
usbpool/test01    99K   899G       99K  /usbpool/test01

zfs get encryption usbpool/test01
NAME            PROPERTY    VALUE        SOURCE
usbpool/test01  encryption  aes-256-gcm  -

Nach einem Reboot

Bei einem Passphrase-geschützten Dataset hat ZFS nach einem Reboot den Schlüssel nicht mehr. Das Dataset existiert, ist aber nicht gemountet:

zfs get mounted usbpool/test01
NAME            PROPERTY  VALUE    SOURCE
usbpool/test01  mounted   no       -

Mit zfs mount -l wird der Schlüssel geladen und das Dataset eingehängt:

zfs mount -l usbpool/test01
Enter passphrase for 'usbpool/test01':

zfs get mounted usbpool/test01
NAME            PROPERTY  VALUE    SOURCE
usbpool/test01  mounted   yes      -

Keyfile statt Passphrase

Statt einer Passphrase-Abfrage kann der Schlüssel auch in einer Datei liegen. Praktisch für Server die ohne Interaktion booten sollen:

zfs create -o encryption=aes-256-gcm \
  -o keyformat=passphrase \
  -o keylocation=file:///root/keys/pool.key \
  zroot/encrypted-data

Die Key-Datei enthält das Passphrase als Text. Wichtig: Die Datei muss beim Boot erreichbar sein, also auf einem unverschlüsselten Dataset liegen. Berechtigungen auf 0400 setzen.

Bestehende Datasets verschlüsseln

Verschlüsselung lässt sich nicht nachträglich auf ein bestehendes Dataset aktivieren. Man muss die Daten per zfs send | zfs receive in ein neues, verschlüsseltes Dataset migrieren. Die komplette Anleitung dafür steht im Beitrag ZFS-Dataset nachträglich verschlüsseln.

Eine Übersicht über alle ZFS-Funktionen gibt es im ZFS-Überblick. Wer sich für ZFS Encryption unter Solaris/OpenIndiana interessiert, findet die Anleitung unter ZFS Encryption unter Solaris. Fragen? Einfach melden.

FreeBSD Jail Upgrade: Wenn freebsd-update die Version nicht erkennt

FreeBSD-Jails lassen sich mit freebsd-update genauso upgraden wie das Host-System. Der Parameter -b gibt den Pfad zur Jail an:

# Normales Jail-Upgrade
freebsd-update -r 14.2-RELEASE upgrade -b /zroot/jails/myjail
freebsd-update install -b /zroot/jails/myjail
service jail restart myjail
freebsd-update install -b /zroot/jails/myjail
# Pakete aktualisieren
jexec myjail pkg upgrade
freebsd-update install -b /zroot/jails/myjail

Das Problem: Falsche Versionserkennung

Manchmal ist freebsd-update davon überzeugt, dass die Jail bereits auf der Zielversion läuft, obwohl sie es nicht ist. Prüft man manuell, steht da noch die alte Version:

jexec myjail freebsd-version
13.2-RELEASE-p9

Das passiert typischerweise wenn die Jail schon Patches bekommen hat oder wenn der Host auf einer anderen Version läuft als die Jail. freebsd-update liest die Version aus Dateien im Jail-Dateisystem und kommt durcheinander.

Die Lösung: –currently-running

Mit --currently-running gibt man freebsd-update die aktuelle Version explizit vor:

freebsd-update -b /zroot/jails/myjail --currently-running 13.2-RELEASE-p9 -r 14.2-RELEASE upgrade

Danach läuft das Upgrade normal durch. Die Version, die man bei --currently-running angibt, muss exakt der Ausgabe von freebsd-version in der Jail entsprechen, inklusive Patchlevel.

Tipp: Vor dem Upgrade einen ZFS-Snapshot der Jail anlegen. Falls etwas schiefgeht, ist ein Rollback in Sekunden erledigt.

Fragen? Einfach melden.

bhyve und vm-bhyve: Windows-VM auf FreeBSD einrichten

bhyve und vm-bhyve auf FreeBSD mit Windows-VM, ZFS-Storage, virtuellem Netzwerk, ISO-Import, VNC-Zugriff, RDP-Nutzung und VM-Verwaltung.

FreeBSD bringt seit Version 10.0 einen eigenen Typ-2-Hypervisor mit: bhyve. Für den täglichen Umgang empfiehlt sich vm-bhyve als Verwaltungstool, damit lässt sich eine Windows-VM in wenigen Minuten einrichten, ohne sich mit den bhyve-Basistools herumschlagen zu müssen.

vm-bhyve installieren und einrichten

# Installation
pkg install vm-bhyve grub2-bhyve uefi-edk2-bhyve

# ZFS-Dataset für VMs anlegen
zfs create pool/vm

# Autostart aktivieren
sysrc vm_enable="YES"
sysrc vm_dir="zfs:pool/vm"

# Initialisieren und Templates kopieren
vm init
cp /usr/local/share/examples/vm-bhyve/* /pool/vm/.templates/

# Netzwerk-Switch erstellen und physisches Interface anhängen
vm switch create public
vm switch add public em0

Windows-VM erstellen

ISO-Dateien importieren, die Windows-ISO und die virtio-Treiber für die Netzwerkkarte:

# Windows-ISO importieren
vm iso /home/kernel/Download/win10.iso

# virtio-net Treiber (für die Netzwerkkarte in der VM)
fetch https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
vm iso /home/kernel/Download/virtio-win.iso

VM aus dem mitgelieferten Windows-Template erstellen:

vm create -t windows -s 200G win10

VM-Konfiguration anpassen

Das Windows-Template kommt mit 2 CPUs und 2 GB RAM. Für eine brauchbare Windows-VM besser anpassen:

vm configure win10
uefi="yes"
cpu=4
memory=8G
graphics="yes"
graphics_port="5999"
graphics_listen="127.0.0.1"
graphics_res="1280x1024"
graphics_wait="auto"
xhci_mouse="yes"
network0_type="virtio-net"
network0_switch="public"
disk0_type="ahci-hd"
disk0_name="disk0.img"

Die wichtigsten Optionen: graphics="yes" aktiviert einen VNC-Server für die Grafikausgabe, xhci_mouse="yes" sorgt für eine brauchbare Maus in der VM, network0_type="virtio-net" nutzt den schnelleren paravirtualisierten Netzwerktreiber statt einer emulierten Karte.

Installation und Zugriff

# VM starten und ISO einlegen
vm install win10 win10.iso

Dann mit einem VNC-Viewer auf 127.0.0.1:5999 verbinden und Windows installieren. Nach der Installation die virtio-Treiber-ISO einlegen (vm install win10 virtio-win.iso) und Windows die Netzwerktreiber dort suchen lassen.

Für den täglichen Zugriff RDP in der VM aktivieren, dann braucht man den VNC-Viewer nur noch für die Ersteinrichtung.

VM verwalten

# Laufende VMs anzeigen
vm list
NAME   DATASTORE  LOADER  CPU  MEMORY  VNC  AUTOSTART  STATE
win10  default    uefi    4    8G     ,    No         Running (10638)

# VM stoppen / starten
vm stop win10
vm start win10

# Snapshot erstellen (ZFS-Snapshot der VM-Disk)
vm snapshot win10

Details und weitere Optionen im vm-bhyve Wiki. Fragen? Einfach melden.

ioping: Read- und Write-Latency schnell messen

Für ausführliche Storage-Benchmarks gibt es Tools wie bonnie++ oder fio. Wenn man nur schnell die Read- oder Write-Latency eines Dateisystems prüfen will, reicht ioping — ein einzelner Befehl, Ergebnis in Sekunden.

Installation

# FreeBSD
pkg install ioping

# Debian/Ubuntu
apt install ioping

Read-Latency messen

ioping -s 256k -T 120 -D -c 20 ./
256 KiB <<< ./ (zfs tanksmeer/usr/home): request=1 time=16.0 us (warmup)
256 KiB <<< ./ (zfs tanksmeer/usr/home): request=2 time=35.7 us
256 KiB <<< ./ (zfs tanksmeer/usr/home): request=3 time=45.8 us
...

--- ./ (zfs tanksmeer/usr/home) ioping statistics ---
19 requests completed in 853.7 us, 4.75 MiB read, 22.3 k iops, 5.43 GiB/s
generated 20 requests in 19.0 s, 5 MiB, 1 iops, 269.2 KiB/s
min/avg/max/mdev = 35.7 us / 44.9 us / 52.8 us / 3.85 us

Die Parameter im Detail:

  • -s 256k — Blockgröße pro Request (hier 256 KiB)
  • -T 120 — Timeout in Sekunden, Requests die länger brauchen werden ignoriert
  • -D — Direct I/O, umgeht den Kernel-Cache (misst die echte Disk-Latency)
  • -c 20 — Anzahl der Requests
  • ./ — Pfad zum Dateisystem das gemessen werden soll

Die Summary am Ende zeigt min/avg/max/mdev — genau wie bei ping. Hier: durchschnittlich 44,9 µs Read-Latency auf einem ZFS-Dataset.

Write-Latency messen

Für die Write-Latency kommt ein einziger Parameter dazu — -W:

ioping -s 256k -T 120 -D -W -c 20 ./
256 KiB >>> ./ (zfs tanksmeer/usr/home): request=1 time=27.0 us (warmup)
256 KiB >>> ./ (zfs tanksmeer/usr/home): request=2 time=54.4 us
256 KiB >>> ./ (zfs tanksmeer/usr/home): request=3 time=60.6 us
...

--- ./ (zfs tanksmeer/usr/home) ioping statistics ---
19 requests completed in 3.86 ms, 4.75 MiB written, 4.93 k iops, 1.20 GiB/s
generated 20 requests in 19.0 s, 5 MiB, 1 iops, 269.5 KiB/s
min/avg/max/mdev = 51.6 us / 202.9 us / 2.65 ms / 577.9 us

Write ist hier erwartungsgemäß langsamer — 202,9 µs im Schnitt gegenüber 44,9 µs beim Lesen. Die höhere Standardabweichung (577,9 µs vs. 3,85 µs) zeigt, dass einzelne Writes deutlich länger dauern können (hier ein Ausreißer mit 2,65 ms — vermutlich ein ZFS Transaction Group Commit).

Weitere nützliche Optionen

# Fortlaufend messen (wie ping ohne -c)
ioping -D ./

# Nur die Summary nach 10 Requests
ioping -D -c 10 -q ./

# Bestimmte Blockgröße (4k für Random I/O)
ioping -s 4k -D -c 20 ./

# Netzlaufwerk / NFS-Mount testen
ioping -D -c 20 /mnt/nfs-share/

Praktisch für einen schnellen Vergleich: Lokale SSD, NFS-Share und USB-Platte mit dem gleichen Befehl messen — die Unterschiede werden sofort sichtbar. Fragen? Einfach melden.

ZFS send/recv: „Cannot receive incremental stream“ beheben

Inkrementelle Replikation mit ZFS

Mit zfs send und zfs recv lassen sich Datasets über SSH auf ein anderes System replizieren. Das Schöne daran: Nach einem initialen Vollabgleich muss man nur noch die Differenz zwischen zwei Snapshots übertragen. Bei großen Datasets spart das enorm Bandbreite und Zeit.

Beispiel: Eine FreeBSD Jail mit 100 GB Mediadaten. Wöchentliche Snapshots werden automatisch erstellt. Der initiale Transfer:

zfs send zroot/jails/subsonic@weekly-2017-03-12 | ssh zfsrecv@system02 zfs recv zroot/backup/subsonic

Ab jetzt nur noch das Delta zum nächsten Snapshot:

zfs send -i zroot/jails/subsonic@weekly-2017-03-12 zroot/jails/subsonic@weekly-2017-03-19 \
  | ssh zfsrecv@system02 zfs recv zroot/backup/subsonic

Der Fehler

Das funktioniert nur, solange das Ziel-Dataset seit dem letzten Snapshot nicht verändert wurde. Jede Änderung — und sei es nur ein Update der Access-Time (atime) durch einen Lesezugriff — reicht aus, damit ZFS den inkrementellen Stream ablehnt:

cannot receive incremental stream: destination zroot/backup/subsonic has been modified
since most recent snapshot
warning: cannot send 'zroot/jails/subsonic@weekly-2017-03-19': signal received

ZFS sagt damit: Zwischen dem letzten gemeinsamen Snapshot und jetzt hat sich am Ziel etwas geändert. Das Delta passt nicht mehr.

Lösung und Prävention

Man muss nicht alles neu übertragen. Mit -F weist man zfs recv an, das Ziel-Dataset auf den letzten gemeinsamen Snapshot zurückzurollen und dann das Delta anzuwenden:

zfs send -i zroot/jails/subsonic@weekly-2017-03-12 zroot/jails/subsonic@weekly-2017-03-19 \
  | ssh zfsrecv@system02 zfs recv -F zroot/backup/subsonic

Damit das Problem gar nicht erst auftritt, setzt man das Ziel-Dataset auf readonly:

zfs set readonly=on zroot/backup/subsonic

So kann nichts am Ziel verändert werden — kein atime-Update, kein versehentliches Schreiben. Das -F im Skript schadet trotzdem nicht als Sicherheitsnetz. Und nicht vergessen: Auf dem Zielsystem regelmäßig alte Snapshots aufräumen, sonst wächst der Plattenverbrauch stetig.

Mehr Details in der OpenZFS-Dokumentation zu zfs send. Mehr zu ZFS: ZFS Compression und Deduplication und TRIM im ZFS-Pool aktivieren. Fragen? Einfach melden.

FreeBSD: Automatische ZFS-Snapshots mit zfs-auto-snapshot

Automatische Snapshots einrichten

Auf FreeBSD gibt es mehrere Wege, ZFS-Snapshots zu automatisieren. Ein bewährtes Tool ist zfs-auto-snapshot aus den Ports (sysutils/zfstools). Es orientiert sich an der Snapshot-Automatisierung von Solaris/OpenSolaris und kann auch konsistente Snapshots einer MySQL- oder PostgreSQL-Datenbank erstellen.

Hinweis: Seit FreeBSD 13 gibt es mit periodic(8) und dem ZFS-Periodic-Script eine eingebaute Alternative, die ohne zusätzliche Pakete auskommt. Für ältere Systeme oder mehr Kontrolle über die Retention bleibt zfstools eine gute Wahl.

Installation:

pkg install zfstools

Danach die Cronjobs in /etc/crontab eintragen:

# ZFS snapshots
15,30,45 * * * * root /usr/local/sbin/zfs-auto-snapshot frequent  4
0        * * * * root /usr/local/sbin/zfs-auto-snapshot hourly   24
7        0 * * * root /usr/local/sbin/zfs-auto-snapshot daily     7
14       0 * * 7 root /usr/local/sbin/zfs-auto-snapshot weekly    4
28       0 1 * * root /usr/local/sbin/zfs-auto-snapshot monthly  12

Falls zfs im Cronjob nicht gefunden wird, die PATH-Variable in der Crontab erweitern:

PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin

Jetzt noch die Snapshots für das gewünschte Dataset aktivieren:

zfs set com.sun:auto-snapshot=true zroot/usr/home

Retention-Schema

Mit der Crontab oben werden folgende Snapshots vorgehalten:

  • Alle 15 Minuten — 4 Snapshots vorgehalten
  • Stündlich — 24 Snapshots vorgehalten
  • Täglich — 7 Snapshots vorgehalten
  • Wöchentlich — 4 Snapshots vorgehalten
  • Monatlich — 12 Snapshots vorgehalten

Snapshots prüfen

zfs list -r -H -t snapshot zroot/usr/home
zroot/usr/home@zfs-auto-snap_hourly-2016-05-19-11h00     21,5M  -  82,5G  -
zroot/usr/home@zfs-auto-snap_frequent-2016-05-19-11h30   14,6M  -  82,6G  -
zroot/usr/home@zfs-auto-snap_frequent-2016-05-19-11h45   13,3M  -  82,6G  -
zroot/usr/home@zfs-auto-snap_hourly-2016-05-19-12h00     14,0M  -  82,7G  -

Datenbank-Snapshots

Für konsistente Datenbank-Snapshots muss das Property auf dem Datenbank-Dataset speziell gesetzt werden — zfs-auto-snapshot friert die Datenbank dann vor dem Snapshot kurz ein:

# PostgreSQL
zfs set com.sun:auto-snapshot=postgresql zroot/data/postgres

# MySQL/MariaDB
zfs set com.sun:auto-snapshot=mysql zroot/data/mysql

Wichtig: Snapshots auf demselben System sind kein Backup-Ersatz. Geht die Platte kaputt, sind die Snapshots mit weg. Für eine echte Sicherung die Snapshots per zfs send/recv auf ein zweites System replizieren.

Mehr zu ZFS: ZFS Compression und Deduplication. Details zu allen Snapshot-Optionen in der OpenZFS-Dokumentation. Fragen? Einfach melden.

« Ältere Beiträge

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑