Инкрементальное и дифференциальное резервное копирование виртуальной машины QEMU/KVM

Копии необходимо создавать - это истина. Но не всегда требуется копировать всё, зачастую выгоднее по времени и по использованию дискового пространства копировать только то, что изменилось. QEMU предоставляет такую возможность.
Опишу этот алгоритм, как последовательность команд терминала.
Версии программ.
$ qemu-system-x86_64 --version
QEMU emulator version 2.11.1(Debian 1:2.11+dfsg-1ubuntu7.42)
Copyright (c) 2003-2017 Fabrice Bellard and the QEMU Project developers
$ virsh --version
4.0.0
Процесс копирования.
Запрашиваем состояние виртуальной машины (ВМ)
$ virsh list
Id Name State
----------------------------------------------------
2 my_vm running
ВМ работает (это важно).
Запрашиваем список дисков ВМ
$ virsh domblklist my_vm --details
Type Device Target Source
------------------------------------------------
file disk hda /var/lib/libvirt/images/my.img
Создаем папку для копии:
$ mkdir -p ~/backup/my_vm
Меняем права доступа к ней (для предоставления прав QEMU )
$ chmod 777 ~/backup/my_vm/
В QEMU у дисков собственные имена, поэтому запрашиваем список блочных устройств:
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"query-block"}' | grep '"node-name":'
"node-name": "#block189"
Итак, наш диск в дальнейшем - "#block189"
Создаем битовую карту измененных блоков "my_bitmap" для хранения информации об изменениях диска:
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"block-dirty-bitmap-add","arguments": {"node": "#block189", "name": "my_bitmap"}}'
{
"return": {
},
"id": "libvirt-30"
}
Операции по копированию QEMU будет проводить в фоновом режиме, для получения результата завершения запускаем второй терминал и выполняем в нем команду ожидания:
$ virsh qemu-monitor-event --timeout 3600 --event BLOCK_JOB_COMPLETED
Возвращаемся в первый терминал и запускаем процесс получения начальной полной копии диска ВМ
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"transaction","arguments": {"actions": [ {"type": "drive-backup","data": {"device": "#block189","target": "/home/piter/backup/my_vm/my_vm.inc.0","sync": "full","format": "qcow2","job-id":"JOB1"}}]}} '
{
"return": {
},
"id": "libvirt-34"
}
Периодически можем запрашивать состояние процесса в первом терминале:
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"query-block-jobs"}'
{
"return": [
{
"io-status": "ok",
"device": "JOB1",
"busy": true,
"len": 4089446400,
"offset": 3254517760,
"paused": false,
"speed": 0,
"ready": false,
"type": "backup"
}
],
"id": "libvirt-33"
}
Ожидаем завершения процесса копирования во втором терминале и получаем результат:
event BLOCK_JOB_COMPLETED at 1752478453.041380 for domain my_vm: {"device":"JOB1","len":4089446400,"offset":4089446400,"speed":0,"type":"backup"}
events received: 1
Процесс завершен без ошибок.
Копируем в папку xml файл описания ВМ
$ virsh dumpxml my_vm > ~/backup/my_vm/libvirt.xml
Создаем заготовку первого инкремента, как файла изменений для полного файла (inc.0):
$ qemu-img create -f qcow2 /home/piter/backup/my_vm/my_vm.inc.1 -b /home/piter/backup/my_vm/my_vm.inc.0 -F qcow2
Formatting '/home/piter/backup/my_vm/my_vm.inc.1', fmt=qcow2 size=4089446400 backing_file=/home/piter/backup/my_vm/my_vm.inc.0 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
Меняем на него права:
$ chmod 666 ~/backup/my_vm/my_vm.inc.1
Запускаем первое инкрементальное копирование:
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"transaction","arguments": {"actions": [ {"type": "drive-backup","data": {"device": "#block189","bitmap": "my_bitmap","target": "/home/piter/backup/my_vm/my_vm.inc.1","sync": "incremental","format": "qcow2","job-id":"JOB1","mode": "existing"}}]}} '
{
"return": {
},
"id": "libvirt-37"
}
Ожидание завершения операции - такое же. Аналогично создаем заготовку второго инкремента, как файла изменений для первого (inc.1):
$ qemu-img create -f qcow2 /home/piter/backup/my_vm/my_vm.inc.2 -b /home/piter/backup/my_vm/my_vm.inc.1 -F qcow2
Formatting '/home/piter/backup/my_vm/my_vm.inc.2', fmt=qcow2 size=4089446400 backing_file=/home/piter/backup/my_vm/my_vm.inc.1 backing_fmt=qcow2 cluster_size=65536 lazy_refcounts=off refcount_bits=16
Меняем на него права:
$ chmod 666 ~/backup/my_vm/my_vm.inc.2
Второе инкрементальное копирование:
$ virsh qemu-monitor-command my_vm --pretty '{"execute":"transaction","arguments": {"actions": [ {"type": "drive-backup","data": {"device": "#block189","bitmap": "my_bitmap","target": "/home/piter/backup/my_vm/my_vm.inc.2","sync": "incremental","format": "qcow2","job-id":"JOB1","mode": "existing"}}]}} '
{
"return": {
},
"id": "libvirt-39"
}
Процесс копирования инкрементов может продолжаться и далее.
Копия ВМ содержит xml файл описания и последовательный набор инкрементов:
$ ls ~/backup/my_vm/
libvirt.xml my_vm.inc.0 my_vm.inc.1 my_vm.inc.2
Следует помнить, что останавливать ВМ нельзя, т. к. битовая карта измененных блоков будет утрачена.
Процесс восстановления.
Копия ВМ — это цепочка файлов изменения:
$ qemu-img info ~/backup/my_vm/my_vm.inc.2 | grep 'backing file:'
backing file: /home/piter/backup/my_vm/my_vm.inc.1
$ qemu-img info ~/backup/my_vm/my_vm.inc.1 | grep 'backing file:'
backing file: /home/piter/backup/my_vm/my_vm.inc.0
Итак:
Необходимо сохранить все файлы из папки ~/backup/my_vm/ , так как они изменятся в процессе последующих действий.
Останавливаем ВМ
$ virsh shutdown my_vm
Восстанавливаем диск ВМ, последовательно присоединяя файлы изменений от самого последнего к нулевому:
$ qemu-img commit ~/backup/my_vm/my_vm.inc.2
Image committed.
$ qemu-img commit ~/backup/my_vm/my_vm.inc.1
Image committed.
Процесс восстановления диска ВМ завершен. Копия восстановленного диска - в файле ~/backup/my_vm/my_vm.inc.0
Копируем восстановленный диск. Напоминаем, что он в формате "qcow2", а my.img - "raw"
$ qemu-img convert -f qcow2 ~/backup/my_vm/my_vm.inc.0 -O raw /var/lib/libvirt/images/my.img
Процесс восстановления завершен.
Дифференциальное резервное копирование
В данном случае мы не держим всю цепочку изменений, а сохраняем только полную копию (inc.0) и суммарно накопленные изменения (inc.1).
После получения инкремента inc.2 сразу же вносим изменения в дифференциальный файл (inc.1):
$ qemu-img commit ~/backup/my_vm/my_vm.inc.2
Image committed.
$ rm ~/backup/my_vm/my_vm.inc.2
Далее процесс периодически повторяется.
При восстановлении из копии цепочка будет состоять всего из двух файлов, что ускорит процесс.
Более углублено изучить данную тему можно по следующим ссылкам: