Incus mit ZFS in einer VM betreiben und Container auf neuen Storagepool verschieben
Seit vielen Jahren setze ich beim Hosting meiner Services auf das Virtualisierungs- und Containermanagement-Tool “Incus” (vorher “LXD”). Incus läuft dabei in einer virtuellen Maschine und hilft mir, eine Separierung auf Applikationsebene herzustellen. So gibt es beispielsweise einen Incus-Container für trashserver.net, einen weiteren für metalhead.club usw. Die Root-Dateisysteme der einzelnen Container befinden sich dabei in einem ZFS-Dateisystem. So kann ich platzsparende Snapshots von meinen Containern vor kritischen Wartungsaktionen anlegen, z.B. vor Updates oder Betriebssystemupgrades.
Da ich den zugrundeliegenden Speicher kürzlich erneuert habe, will ich hier mein Storage-Setup kurz vorstellen und für mich (aber auch euch ;-) ) dokumentieren, auf was ich geachtet habe und wie ich meine Container auf den neuen Storage umgezogen habe.
Auf Hypervisor-Level (also auf dem physischen Server) wird der Speicher für meine Container in Form von LVM Volumes bereitgestellt. Das erlaubt es mir, den großen Speicher in vielen einzelnen Volumes auch für andere VMs anzubieten und je nach Ressourcenbedarf zu dimensionieren.
Ein LVM Volume (Blockdevice) wird dann über libvirt/QEMU und den virtio-blk Treiber an die VM durchgereicht. Dabei nutze ich in libvirt die folgenden Parameter für den Speicher:
- Treiber: virt-blk
- Cache Modus: none (cache macht schon zfs)
- io = native (meistens besser als io=threads)
- discard = unmap (für Trim-Support)
- serial = megastor
Besonders hervorheben will ich an dieser Stelle die serial
Einstellung, die es erlaubt, für das durchgereichte Blockdevice eine Seriennummer / eine ID / einen eindeutigen Namen anzugeben. Bindet man mehrere Speichergeräte in seine VM an, lassen sich die einzelnen Speicher so wieder zweifelsfrei identifizieren. Ein megastor
benanntes Gerät erscheint in der VM dann z.B. als /dev/disk/by-id/virtio-megastor
. Das ist vor allem hilfreich, wenn …
- … man nicht mehr sicher ist, was eigentlich zwischen /dev/vdb und /dev/vdd steckt und welcher Storage gleich nochmal welcher ist?
- … oder man /dev/vdc entfernen will, aber man befürchten muss, dass dann beim nächsten Reboot /dev/vdd nachrückt und diesen Platz einnimmt. Dann wäre das Chaos perfekt.
Denn: Device-Namen für Storage-Devices sind unter Linux nicht “stabil”! Nur Partitions-UUIDs sind es (oder alternativ Disk-IDs!).
In der VM wiederum wird das Blockdevice genutzt, um damit einen einfachen ZFS-Pool zu erzeugen, z.B. via:
zpool create -o ashift=12 megastor /dev/disk/by-id/virtio-megastor
Der ZFS Pool wird auch in Incus registriert, damit er mit den Containern genutzt werden kann:
incus storage create megastor zfs source=megastor --description="megastor storage for containers"
Trim Support
Vor einiger Zeit unterstützte nur der virtio-scsi Treiber trim
, um freie Speicherbereiche auch auf der SSD passend zu markieren und freizugeben. Regelmäßiges “trimmen” führt zu einer wesentlich besseren Performance. Seit einigen QEMU Versionen beherrscht aber auch der schlankere “virtio-blk” Treiber Trimming.
ZFS führt über das Paket zfsutils-linux
übrigens regelmäßig selbst ein Trim-Cronjob (/etc/cron.d/zfsutils-linux
) aus - es ist also nicht nötig, selbst ein fstrim
auszuführen.
Allerdings ist darauf zu achten, dass - wie oben erwähnt - discard=unmap
für das QEMU Speicherberät definiert ist. Und auch auf Hypervisor-Level muss in meinem Fall wegen der LUKS-Verschlüsselung in /etc/crypttab
ein “discard” Parameter eingetragen sein. Sonst wird das Trimming nicht bis in die unseren Storage-Layer durchgereicht. Beispiel:
luks-d429b69b-1c1a-234a-8bcf-64232e83f156 UUID=d429b69b-1c1a-234a-8bcf-64232e83f156 /var/lib/megastor.key discard
Außerdem sollte auch LVM passend konfiguriert sein: vim /etc/lvm/lvm.conf
:
issue_discards = 1
Bestehende Incus VMs auf neuen Storage verschieben
Da ich - wie zu Beginn erwähnt - neue SSDs in dem System verbaut und dementsprechend neue ZFS Pools eingerichtet habe, sollten nun auch die Incus Container in den neuen “megastor” Pool verschoben werden. Dafür gibt es mehrere Methoden …
Der einfache Weg mit einigen Minuten Downtime
Das geht mit Incus so:
incus stop mycontainer
incus move mycontainer --storage megastor
Vor der Übertragung wird der Container gestoppt, was je nach Größe des Containers und Performance des Storage eine gewisse Weile dauert und somit Downtime verursacht. Dafür ist die Methode weniger komplex. Sie lohnt sich bei eher kleinen Containern mit etwa 50 - 100 GB Größe.
Mit inkrementeller Übertragung - aber kurzer Downtime
Wer Downtime möglichst vermeiden will, kann anders vorgehen:
- Snapshot vom Quellcontainer während des laufenden Betriebs erzeugen
- Snapshot im Hintergrund zum Ziel kopieren
- Quellcontainer stoppen (Beginn Downtime)
- Differenz seit letzter Übertragung übertragen (idR. nur wenige MG bis GB)
- Alten Container entfernen, neuen Container umbenennen
- Neuen Container starten (Ende Downtime)
Die Downtime reduziert sich auf ein Minimum. Das grundsätzliche Vorgehen habe ich schon einmal in meinem Beitrag “Move a Mastodon instance with less than 3 minutes downtime (LXD/ZFS-based)” beschrieben und habe gewissermaßen um LXD herum gearbeitet. Dasselbe funktioniert aber auch mit Incus-Bordmitteln und kann auf die Übertragung eines Containers von einem Storage auf den anderen angewendet werden (nicht nur auf die Übertragung zwischen zwei Hosts!).
# 1. Snapshot im laufenden Betrieb erstellen
incus snapshot create mycontainer
# 2. Snapshot auf neuen Storage "megastor" übertragen
incus copy mycontainer new-mycontainer --storage megastor
# 3. Container stoppen und weiteren Snapshot erzeugen
incus stop mycontainer
incus snapshot create mycontainer
# 4. Änderungen seit Beginn der ersten Übertragung nun auch übertragen
incus copy mycontainer new-mycontainer --storage megastor --refresh
# 5. Alten Container löschen, neuen Container umbenennen
incus delete mycontainer
incus move new-mycontainer mycontainer
# 6. Neuen Container starten
incus config unset mycontainer volatile.apply_template
incus start mycontainer
Das incus config unset...
sorgt dafür, dass der neue Container die alten MAC-Adressen, IPs etc behält und keine neuen Adressen zugeteilt bekommt.
Wer will, kann übrigens zwischen Schritt 2 und 3 auch weitere “snapshot - copy” Folgen zwischenschieben:
incus snapshot create mycontainer
incus copy mycontainer new-mycontainer --storage megastor --refresh
Das ist vor allem dann sinnvoll, wenn nach der ersten Übertragung viel Zeit vergangen ist. Hat sich während dieser Zeit viel im Container geändert, fällt auch der finale Sync bedeutend länger aus und sorgt für eine längere Downtime. Dem kann man entgegenwirken, indem man sich in mehreren “snapshot - copy” Schritten annähert. Wichtig ist nur, dass nach dem ersten copy
Befehl das --refresh
Flag nicht vergessen wird.