KVM-Images platzsparend speichern und sichern

Mit dem Problem, Image-Dateien virtueller Maschinen platzsparend zu speichern, habe ich mich schon vor einigen Jahren in diesem Blog beschäftigt. Aber man lernt nie aus, weswegen ich Ihnen hier eine Variante der damals präsentierten Techniken sowie ein neues Verfahren vorstellen möchte :-)

Variante 1: Möglichst kleine Backups von Images im laufenden Betrieb erstellen

Ausgangspunkt ist eine laufende virtuelle Maschine, deren Image-Datei sich in einem Dateisystem in einem Logical Volume befindet. Damit Sie die Image-Datei überhaupt im laufenden Betrieb sichern können, brauchen Sie einen LVM-Snapshot. Damit frieren Sie den Zustand der Image-Datei während des Backups ein. (Diese Vorgehensweise ist gut, aber nicht perfekt. Zum Zeitpunkt des Backups offene Dateioperationen können dadurch verloren gehen. Genaugenommen müssten Sie das Image zusammen mit dem Zustand der virtuellen Maschine speichern, was ich der Einfachheit halber aber nicht mache. In der Praxis funktioniert das Image-Backup in der Regel gut genug. Wenn im Rahmen des Backups zwei, drei offene Datenbanktransaktionen oder die letzten Einträge in einer Logging-Datei verloren gehen, kann ich damit leben.)

Die folgenden Zeilen zeigen ein bash-Script, das auf dem KVM-Host ausgeführt wird. Das Script richtet einen Snapshot ein, erzeugt eine lokale, komprimierte Image-Backup-Datei und löst den Snapshot wieder auf. Gegebenenfalls können Sie das Image jetzt noch verschlüsseln und per FTP/scp/aws/etc. irgendwohin hochladen.

#!/bin/bash -e
# Beispiel für Backup-Script am KVM-Host
img=myimage.qcow2
bak=/local-backup-directory
# bisheriges Backup löschen
rm -f $bak/$img

# evt. noch vorhandenen alten Snapshot auflösen
if [ "$(mount | grep /lvmsnap)"  ]; then
  umount /lvmsnap
fi
if [ "$(lvscan | grep snap)"  ]; then
  lvremove -f /dev/vg1/snap
fi

# neuen Snapshot erzeugen und mounten
lvcreate -s -L 2G -n snap /dev/vg1/vms
mount /dev/vg1/snap /lvmsnap

# lokale Backup-Datei erzeugen und mit lz4 komprimieren,
# ohne die ganze IO-Kapazität zu blockieren
ionice -c 3 cat /lvmsnap/$img  | lz4 - -c > $bak/$img.lz4

# Snapshot wieder auflösen
umount /lvmsnap
lvremove -f /dev/vg1/snap

Angenommen, die Image-Datei sieht eine maximale Größe von 50 GB vor, tatsächlich werden aber aktuell nur rund 25 GB genutzt: In diesem Fall wird die Backup-Datei des Images größer ausfallen als notwendig, weil das Dateisystem mit der Zeit voller ungenutzter Blöcke ist, die aktuell keinen Dateien zugeordnet sind. Abhilfe schafft in solchen Fällen, in der virtuellen Maschine (nicht am KVM-Host) mit dd vorübergehend eine möglichst große Datei voller Nullen zu erzeugen und diese dann wieder zu löschen. Das Ergebnis: Die bisher ungenützten Blöcke sind jetzt mit Nullen vollgeschrieben und lassen sich durch das obige Backup-Script sehr effizient komprimieren.

Nun fördert das vorübergehende Vollschreiben des ganzen Dateisystems zwar die Komprimierbarkeit der Image-Datei, führt aber zu einem zersplitterten Dateisystem. ext4-Entwickler Theodore Ts’o ist deswegen kein Freund dieser Technik. Noch ein Nachteil: Wenn die Image-Datei vorher sparse war, ist sie es danach nicht mehr. Gerade bei lange laufenden virtuellen Maschinen ist die Image-Datei früher oder später aber sowieso nicht mehr sparse.

Ich habe mich diesbezüglich zu einem Kompromiss entschieden. Das folgende Script erzeugt eine Datei aus lauter Nullen, die 90% des freien Speicherplatz füllt. Dieses Script wird in meiner virtuellen Maschine einmal monatlich aufgerufen (Link in /etc/cron.monthly), also nur recht selten.

#!/bin/bash
# 90% der ungenutzten Blöcke des Dateisystems mit Nullen überschreiben
# läuft in der virtuellen Maschine
#
# df / liefert freie 1k-Blöcke
# grep filtert die richtige Zeile heraus
# awk -F bildet Spalten, $4 extrahiert die vierte Spalte
# 1k-Blöcke * 0.0009 ergibt 90% 1M-Blöcke
cnt=$(df / | grep ' /' | awk  -F' ' '{print int($4*0.0009)}')
dd if=/dev/zero of=/zero bs=1M count=$cnt
sync
rm /zero

Variante 2: Image-Dateien schrumpfen (virt-sparsify)

Ganz anders sieht die Lage aus, wenn Sie zum Verkleiner des Images bzw. zur Durchführung des Backups die virtuelle Maschine herunterfahren können. In diesem Fall können Sie das großartige, mir bis vor kurzem aber unbekannte Kommando virt-sparsify ausführen. Unter Ubuntu befindet sich das Kommando im Paket libguestfs-tools.

Standardmäßig liest virt-sparsify eine Image-Datei, löscht den Inhalt von dort enthaltene Swap-Partitionen, berücksichtigt bei Dateisystemen nur die tatsächlich aktiven Blöcke und erzeugt schließlich eine neue Image-Datei, die so klein wie möglich und außerdem sparse ist. Grandios!

# setzt voraus, dass die virtuelle Maschine heruntergefahren wurde!
virt-sparsify image.qcow2 sparse-image.qcow2

Wenn Sie virt-sparsify vertrauen, können Sie die Datei auch direkt überschreiben:

virt-sparsify --in-place image.qcow2

Beachten Sie, dass virt-sparsify relativ viel Platz im temporären Verzeichnis erfordert (im ungünstigsten Fall 2x die Maximalgröße der Image-Datei).

Quellen

Werfen Sie insbesondere einen Blick auf den StackExchange-Beitrag, wo einige weitere Techniken mit diversen Vor- und Nachteilen beschrieben werden. Ich habe auch mit fstrim experimentiert, aber leider ohne Erfolg.