Springe zum Inhalt

Backup der VMs im laufenden Betrieb mit libvirt

Mit libvirt bzw. deren Kommandozeilen-Interface virsh kann ein Backup der VMs im laufenden Betrieb durchgeführt werden. Das nachfolgende Script stellt ein Grundgerüst, angeregt durch einen Artikel von Christian Rößner, für ein Backup nach der folgenden Methode zur Verfügung:

Es wird zuerst ein Snapshot aller Volumes einer VM angelegt. Dadurch besteht jedes Volume aus (mindestens) zwei Dateien: eine Datei mit den originalen Daten und eine zweite Datei, welche alle Änderungen gegenüber dem Originalvolume ab dem Erstellen des Snapshots enthält. Das Originalvolume wird bei aktivem Snapshot nicht mehr beschrieben, es kann nun problemlos gesichert werden. Zum Kopieren kann "cp -f --sparse=always ..." oder, sofern verfügbar, "rsync --sparse ..." zum Einsatz kommen. Beide Kommandos behandeln freien Platz in den Volumes optimal, rsync ist jedoch um einiges schneller, wenn im Backup-Verzeichnis bereits vom vorherigen Backup schon die gleiche Datei vorhanden ist, da dann nur die Änderungen kopiert werden. Anschließend werden mit einem sogenannten Blockcommit die im Snapshot als Änderungen geschriebene Blöcke in das Originalvolume wieder eingearbeitet. Das alles kann im laufenden Betrieb vom Host der VMs aus durchgeführt werden.

Update vom 06.09.2021: Der Server mit dem Git-Repository existiert nicht mehr. Interessierte können sich ein Archiv des letzten Stand des Projekts herunterladen.

Update: mittlerweile handelt es sich hier um die 3. Version des Scriptes, welches nun auch korrekt mit mehreren Volumes einer VM umgehen kann. Fälschlicherweise hatte ich angenommen, dass "snapshot-create-as" mit entsprechender "--diskspec" nur einen Snapshot des angegebenen Volumes erzeugt, was aber nicht der Fall ist. "snapshot-create-as" erzeugt in einem Schritt ein Snapshot aller Volumes der VM. Obiges Script berücksichtigt das nun.

Update 2: Skript Version 4. Das Skript wurde erweitert, um auch VM-Festplatten im qcow2-Format verarbeiten zu können. Ebenfalls werden die Images jeder VM in einem eigenen Verzeichnis gesichert. VM-Images mit gleichem Namen aus unterschiedlichen Speicherpools überschreiben sich somit nicht mehr. Vielen Dank an Sylvia für die Hinweise und Jan für die Lösung!

Am Anfang des Skripts wird festgelegt, wo die Volumes und die XML-Definition der VMs zu finden sind. Das in Zeile 5 angegebene Verzeichnis ist das Ziel der Backups.

Ebenfalls am Anfang des Skripts ist eine Sicherung eingebaut, die das mehrfache gleichzeitige Ausführen des Skripts verhindert.

Für alle laufenden VMs wird ausgeführt:

  • virsh snapshot-create-as ...: erstellen eines Snapshots aller Volumes der VM
  • rsync --sparse ...: kopieren der originalen Volumes ins Backupverzeichnis
  • virsh blockcommit ...: einarbeiten der Änderungen ins originale Volume
  • löschen der Overlay-Images

Nach dem Abarbeiten aller Volumes wird das XML-Definitionsfile der jeweiligen VM ebenfalls ins Backup-Verzeichnis kopiert.

Fallstricke

virsh blockcommit ... --delete

"virsh blockcommit --help" zeigt eine Option "--delete" an, welche das eingearbeitete Änderungsfile praktischerweise gleich löschen würde. Leider ist diese Option bisher nicht im Sourcecode eingearbeitet und erzeugt nur die Fehlermeldung: "error: unsupported flags (0x2) in function qemuDomainBlockCommit". Ein manuelles Löschen ist notwendig.

virsh snapshot-create-as ... --quiesce

Für die Option "--quiesce" ist es notwenig, dass auf dem Gast der sogenannte "qemu-guest-agent" installiert ist. Dieser kann Befehle vom Host-System entgegen nehmen und wie in diesem Fall, das Dateisystem in einen konsistenten Zustand versetzen.

qemu-guest-agent erforderlich

Der für die Option "--quiesce" erforderliche Dienst "qemu-guest-agent" benötigt einen Channel in der VM-Definition, welcher mit dem von mir eingesetzten virt-manager in der Version 0.9.5 leider nicht angelegt werden kann. Man muss vielmehr auf dem VM-Host mit dem Kommando "virsh edit <VMname>" die XML-Definition der VM direkt bearbeiten und innerhalb von "<devices> ... </devices>" folgenden Schnipsel einfügen:

<channel type='unix'>
  <source mode='bind' path='/var/lib/libvirt/qemu/channel/target/<vmname>.qemu.guest_agent.0'/>
  <target type='virtio' name='org.qemu.guest_agent.0'/>
  <address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>

<vmname> ist entsprechend zu ersetzen. Anschließend muss die VM heruntergefahren und wieder gestartet werden. Ein einfacher Reboot reicht nicht aus! Danach sollte der Dienst "qemu-guest-agent" erfolgreich innerhalb der VM gestartet werden können.

Cron

Sollte obiges Script per Cron eingesetzt werden, so muss es nach bisherigen Tests unter dem Benutzer root laufen. Der Benutzer libvirt-quemu, wie er unter Debian-Betriebssystemben beim Installieren von libvirt eingerichtet wird, hat keine Berechtigung, mit den KVM/QEMU-Systemdiensten zu kommunizieren.

Fazit

Ich selbst setzte seit kurzem mehrere kleine VMs für eigene Dienste ein und benutze ein Script nach obigem Muster für das Backup der VMs. Es kann einfach an die eigenen Bedürfnisse angepasst werden. Wesentlich sind die Befehle zum Auflisten der VMs und die Befehle zum Auflisten der Volumes der jeweiligen VM. Für jede VM werden die Volumes aufgelistet, ein Snapshot aller Volumes angelegt und anschließend jedes Volume gesichert. Danach werden die Snapshots wieder in die Original-Volumes eingearbeitet und der Admin kann dann sicher eins: ruhiger schlafen.

36 Gedanken zu „Backup der VMs im laufenden Betrieb mit libvirt

  1. Axel Birndt

    Hallo Jens,
    danke für das Script und den tollen Artikel. Unter welchem Betriebssystem läuft das Script bei Dir? Gibt es noch andere Fallstricke, als die beschriebenen? Wie sind die Langzeit-Erfahrungen?

    Danke und Gruß Axel

    1. Jens Tautenhahn

      Danke! Das Script läuft hier unter Debian 8 nun seit mehreren Wochen unverändert. Probleme konnte ich keine mehr feststellen. Vielleicht gibt es hier ja irgendwann Erfahrungsberichte von Usern, die es auch einsetzen.

  2. Kevin Krüger

    Vielen Dank für das Script.
    Scheint mir genau das, was ich seit längerem Suche. Ein Problem habe ich aber (oder viele kleine!?): Läuft bei mir nicht!

    Offenbar können mehrere der virsh Befehle nicht so ausgeführt werden, wie das Script es möchte, z.B. werden die jeweiligen Flags bei
    virsh list --name
    virsh domblklist --details
    nicht unterstützt.

    Ausprobiert auf Ubuntu Server 12.04 und 14.04, bei beiden ohne Erfolg. Hast du da noch einen heißen Tipp für mich?

    1. Jens Tautenhahn

      Ich habe das ganze hier so unter Debian am laufen. Hat Ubuntu da evtl. eine andere Version in den Repositories?

      # virsh --version
      1.2.9
      # cat /etc/debian_version 
      8.5
      1. Kevin Krüger

        Ubuntu 12.04 setzt noch auf 0.8.9

        Die Maschine die eigentlich schon mit 14.04 laufen sollte war doch noch auf dem alten LTS Relase. Hab die jetzt mal aktualisiert und siehe da, es läuft. Lag dann also wirklich an der alten virsh Version.

        Danke

  3. Sylvia Ge

    Vielen Dank für das Skript.
    Ich habe folgendes Problem.

    Die .backup Datei wird z.B. als xenia.backup angelegt, aber das Skript versucht später xenia.qcow2.backup zu löschen, was natürlich nicht existiert. Leider finde ich die Stelle nicht wo ich den Namen ändern könnte.
    Verwendest du raw Platten? Diese haben wahrscheinlich keine Endung.
    Ob es noch an anderer Stelle zu Problemen dadurch kommt, kann ich nicht sagen.
    Für einen Tipp wäre ich sehr dankbar.

    1. Jens Tautenhahn

      Das ist in der Tat ein Problem. Das Skript in der obigen Form kann nur mit *.img umgehen, welches die Default-Endung für RAW-Files ist. Ich selbst habe auf einem anderen Server nur qcow2 im Einsatz und habe das Skript dort (angepasst) auch im Einsatz.

      Für qcow2 musst du alle Stellen mit %.img durch %.qcow2 ersetzen. Statt ${img%.img} muss dann überall ${img%.qcow2} stehen.

      Ein zweites Problem ist, dass das Skript in der bisherigen Form nur ein Format für alle Images unterstützt. Vielleicht mach ich demnächst mal Version 4 ;)

      1. Jan

        Ich habe das so gelöst, dass ich einfach nach dem erstellen der snapshots das zusätzliche array `$imgsbackup` einführe. Das enthält dann den Pfad zu allen Snapshots-Disks.

        Die Dateien kann ich dann beim Löschen 1:1 übernehmen.

          virsh snapshot-create-as --no-metadata --domain $vm backup --disk-only --atomic > /dev/null
          for img in ${imgs[@]}; do
            rsync --sparse ${img} ${DUMPDIR}/${vm}/
          done
          declare -A imgsbackup
          eval $(virsh domblklist ${vm} --details | awk '/disk/ {print "imgsbackup["$3"]="$4}')
          [...]
          for img in ${imgsbackup[@]}; do
            fuser -s ${img} || rm ${img}
          done
        
        1. Jens Tautenhahn

          Bei mir läuft das Script nur wöchentlich und so habe ich erst jetzt doch noch einen Fehler gefunden: declare löscht das Array nicht. Bei der nächsten VM wird dann versucht, die Backup-Images der vorhergehenden VM zu löschen, welche natürlich nicht mehr da sind. Es fehlt noch ein unset imgsbackup, wie jetzt oben in Zeile 64.

          1. Jan

            Stimmt, das hatte ich bei mir auch noch eingebaut.

            Und ich hätte noch vier Dinge:
            1. Der Schalter --delete beim blockcommit kann zusammen mit --metadata aufgerufen werden, dann wird die erzeugte XML-Datei weggeräumt. Dafür ist das momentan also nutzbar.

            2. Ich bevorzuge `virsh dumpxml` statt dem Kopieren der XML-Datei. Dies scheint mir ein besserer Weg zu.

            Und zwei Dinge habe ich ebenfalls noch angepasst:
            3. Es wird so immer das komplette File kopiert. Daher habe ich eine Prüfung eingebaut, ob es schon eine Sicherung des Images gibt - diese wird dann nur noch mit --inplace aktualisiert. Die Prüfung ist erforderlich da --inplace mit --sparse kollidiert. Damit erreicht man eine bessere Laufzeit:
            `
            # ursprüngliche Images der VM wegkopieren
            for img in ${imgs[@]}; do
            mkdir -p ${DST}/${vm}/
            if [ -f ${DST}/${vm}/${img} ]; then
            rsync --inplace ${img} ${DST}/${vm}/
            else
            rsync --sparse ${img} ${DST}/${vm}/
            fi
            done
            `
            4. Es werden nur laufende VMs gesichert. Ich habe noch eine weitere Schleife für die inaktiven VMs eingebaut:
            `
            # Inaktive VMs sichern
            for vm in $(virsh list --name --inactive); do
            unset imgs
            echo "-----------------------------------------------------------------------------"
            echo "Backup KVM guest '${vm}'"

            # Liste der Plattennamen und Imagepfade holen
            declare -A imgs
            eval $(virsh domblklist ${vm} --details | awk '/disk/ {print "imgs["$3"]="$4}')

            # ursprüngliche Images der VM wegkopieren
            for img in ${imgs[@]}; do
            mkdir -p ${DST}/${vm}/
            if [ -f ${DST}/${vm}/${img} ]; then
            rsync --inplace ${img} ${DST}/${vm}/
            else
            rsync --sparse ${img} ${DST}/${vm}/
            fi
            done
            # VM-Definition ebenfalls sichern
            virsh dumpxml ${vm} > ${DST}/${vm}/domain.xml
            done
            `

            1. Jan

              Bei Punkt 3 fehlte noch ein $(basename):
              `
              for img in ${imgs[@]}; do
              mkdir -p ${DST}/${vm}/
              if [ -f ${DST}/${vm}/$(basename ${img}) ]; then
              echo rsync --inplace ${img} ${DST}/${vm}/
              else
              echo rsync --sparse ${img} ${DST}/${vm}/
              fi
              done
              `

              1. Jens Tautenhahn

                Vielen Dank für die sehr nützlichen Ergänzungen! Punkt 1 verstehe ich allerdings nicht. Mein virsh kennt bei blockcommit kein --metadata.

                Da die Änderungen hier langsam zu unübersichtlich werden, habe ich das Skript mal in ein hier gehostetes Git geschoben. Vielleicht magst Du Dich ja dort registrieren und die Änderungen direkt einarbeiten? Dann würde ich den Artikel ändern, so dass das Skript gleich aus dem Git kommt.

                1. Jan

                  Ach ja, das --metadata war für `virsh snapshot-delete`, aber das wird ja eh nicht verwendet...

                2. Jan

                  Ich würde gerne dein gogs verwenden, aber bekomme beim pull-request `template: repo/issue/list:11:148: executing "repo/issue/list" at : nil pointer evaluating *models.Repository.Link`

                  Vielleicht solltest Du dir mal gitlab ansehen, das ist zwar etwas ressourcenhungriger aber wesentlich stabiler und vor allem wartungsärmer.

  4. Axel Birndt

    Hallo Jens, @All,
    ich habe auch (derzeit noch) Ubuntu 14.04. LTS.

    Hier habe ich auch noch:
    ------------------------------------------
    ~$ cat /etc/*release /etc/*deb*version*
    DISTRIB_ID=Ubuntu
    DISTRIB_RELEASE=14.04
    DISTRIB_CODENAME=trusty
    DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"
    NAME="Ubuntu"
    VERSION="14.04.5 LTS, Trusty Tahr"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 14.04.5 LTS"
    VERSION_ID="14.04"
    HOME_URL="http://www.ubuntu.com/"
    SUPPORT_URL="http://help.ubuntu.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
    jessie/sid
    ------------------------

    Ich hatte auch schon mal geguckt, es muss mindestens die virsh Version 1.2.9 vorhanden sein. Siehe auch hier: https://www.gonzalomarcote.com/2014/kvm-live-backups-with-qcow2/

    Zitat:
    For this blockcommit backup option you need a quite recent libvirt and qemu versions. Debian wheezy versions are quite old but you can install them from wheezy-backports:
    ii libvirt-bin 1.2.9-3~bpo70+
    ii qemu 2.1+dfsg-5~bpo

    Allerdings habe ich noch keine Quelle gefunden, wo ich ein neueres Libvirt als 1.2.2 für Trusty finden kann... Naja aus den Sourcen kompilieren, wollte ich es nicht...

    1. Jens Tautenhahn

      Vielleicht helfen dir Backports weiter. Hier steht beschrieben, wie du die Backports in Ubuntu aktivierst. Weiter kann ich da leider nicht helfen, da ich kein Ubuntu hier habe. Vielleicht weiß @Kevin Krüger mehr, da er ja wie oben beschrieben unter Ubuntu die neue Version nutzen konnte.

      1. Kevin Krüger

        Also auf den ersten Blick scheint es bei mir erfolgreich zu laufen, ohne weitere Installationen. Hatte gestern händisch den ersten Durchlauf angestoßen und rein von der Dateigröße sehen die Images auf dem Backup gut aus.

        Script ist seit heute als Cronjob angelegt und dann schauen wir nach dem Wochenende mal, wie es klappt.

        1. Kevin Krüger

          Falls es jemanden interessiert: Auf Ubuntu Server 14.04 LTS sieht es zumindest so aus, als wenn alls läuft, trotzdem wirft das Script einige Fehler über fehlende Zugriffsrechte.
          Habe jetzt einen weiteren Test auf Version 16.04 gemacht und dort läuft nun alles fehlerfrei durch

  5. Jens Tautenhahn

    Vielen Dank an Sylvia für den Hinweis auf qcow2 und Jan für die Lösung, um unabhängig vom Format die Snapshots zu bereinigen. Ich habe das Skript nocheinmal aktualisiert und jetzt kann es auch mit *.img und *.qcow2 umgehen. Ebenfalls werden die Backups jetzt in einem eigenen Verzeichnis pro VM gespeichert.

    1. Christian Rößner

      Moin,

      ich habe das Skript mal mit meinem gemergt. Bei mir habe ich noch Maschinen-Ausnahmen drin, die keinen Qemu-GA nutzen können (OpenBSD). Ferner verschlüssle ich im Anschluss alles mit GPG und schiebe es auf ein NFS.

      Wenn du mir eine kurze Mail schickst, sende ich dir mal meine aktuelle Fassung. Sie weicht vielleicht zu stark von deiner ab, so dass ich keinen Pull-Request erstellen will. Du kannst dir ja die nötigen Puzzle übernehmen.

      Herzliche Grüße

      Christian

  6. Oliver Günther

    Moin,

    super Arbeit. War gerade am selber bauen einer vergleichbaren Lösung und bin zufällig drauf gestoßen. Teste es grade in unserer Umgebung.

    2 kleine Ideen:
    - Progress des Rsyncs anzeigen.
    - Datum/Uhrzeit am Anfang und Ende, damit man eine Info zur Länge des Laufes hat.

    Werde in den nächsten Tagen sonst schauen ob ich das selber dazu hacke.

    Viele Grüße,
    Oliver Günther

    1. Jens Tautenhahn

      Danke für die Anregung. Ich habe die Ausgabe mal so geändert dass jede Logzeile mit einem Timestamp versehen wird. Daran ist dann die Laufzeit der einzelnen Schritte und die Gesamtlaufzeit zu erkennnen.

      Einen Progress des Rsyncs halte ich für keine gute Idee. Das Skript soll ja im Hintergrund mit Cron werkeln. Da ist ein Progress eher nicht sichtbar.

      Falls Du noch weitere Verbesserungsvorschläge hast: immer her damit. Oder einfach auf meinem Gogs anmelden (ist offen), forken und PR einreichen.

  7. Volker Cordes

    Hallo,

    vielen Dank für das Skript. Habe es erfolgreich bei uns am Laufen. Leider kommt es aber vor, dass virsh blockcommit nicht wartet, bis die Änderungen in das Basisimage eingebaut sind (virsh 1.3.1 + ubuntu 16.04). Etwas warten (bis virsh blockjob 100% anzeigt) und dann mit virsh blockjob --pivot rotieren funktioniert allerdings. Wäre es möglich, eine entsprechende Prüfung einzubauen?

  8. Thomas Rausch

    Ich will mich damit beschäftigen, da ich das gerne benutzen würde. Ich verwende das qcow2 incl. mehrere Snapshots in dem selben File für Softwarestände der Maschine. Geht das auch problemlos?

    1. Jens Tautenhahn

      qcow2 wurde bereits im Skript implementiert. Du hast mehrere Stände im gleichen File? Das könnte Probleme geben. Grundsätzlich erstellt das Skript einen neuen Snapshot bzw. schreibt alle Änderungen in ein neues File, kopiert die original VHDs der VMs weg und arbeitet anschließend die Änderungen in die original VHDs ein. Es wird wohl nur helfen, dass Du bei Dir genau anschaust, was das Skript mit deinen VHDs macht. Es fehlt leider noch eine "DryRun"-Option...

  9. Stephan Hohn

    Erst einmal vielen Dank für das tolle Script. Einen Verbesserungsvorschlag habe ich. Beim run_offline werden erst alle VMs heruntergefahren bevor sie dann einzeln gesichert werden und am Ende wieder alle gemeinsam hochgefahren.
    Um die Downtime der einzelnen VMs zu verkürzen wäre es schön wenn nur die aktuell zu sichernde VM heruntergefahren und danach wieder gestartet wird.

  10. Dave

    Hi,
    das script sieht ja recht elaboriert aus. Da ich neu in der qemu/kvm/libvirt-Welt bin und einige kopierte VMs mit VMware-Platten habe (vmdk). Die Frage: wurde das bereits getestet? Wenn nicht, kann ich vermutlich bald Erfahrungen beisteuern ;).

    Gruß
    Dave

  11. Olaf

    Hallo miteinand, ich habe das Skript noch nicht ausprobiert, scheint aber als Lösung für mich inn Frage zu kommen. Da man ja Backups nicht nur zum Spass macht: Wie sähe denn bei diesem Backup das Recovery einer VM aus?

    Gruß, Olaf

    1. Jens Tautenhahn

      Die Backups der Plattenimages einer VM werden in einem konfigurierbaren Verzeichnis gespeichert. Diese werden bei einen Restore mit runtergefahrener VM einfach zurückkopiert. Evtl. müssen sie noch entpackt / entschlüsselt werden. Ebenfalls gespeichert wird das Konfigurationsfile der VM, welches ebenfalls einfach zurückkopiert werden kann. Fertig.

  12. Griev

    Hi,

    wir nutzen das Script schon aktiv und ich hätte noch ein paar Wünsche. Nimmst Du sowas entgegen?
    Zum einen, das Script nicht abbricht, wenn eine VM nicht gesichert werden konnte. Zum anderen wäre ein selektieren praktisch, welche Maschinen man sichern will oder andersherum.

    Möchte aber auch mein Lob aussprechen - sehr gute Arbeit, hat mir schon sehr viel geholfen. Vielen Dank!

    1. Griev

      Hallo,

      ich war etwas Blind. Die Auswahl-Funktion gibt es ja schon. Nur das mit dem "Überspringen", falls eine Maschine nicht gesichert werden konnte, wäre noch praktisch.

      Vielen Dank

Kommentare sind geschlossen.