Копии необходимо создавать – это знают многие. Но не всегда требуется копировать всё заново, зачастую выгоднее по времени и по использованию дискового пространства копировать только то, что изменилось. QEMU предоставляет такую возможность.
Статья является «конспектом» документации 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
У ВМ один диск в формате «raw»
- Создаем каталог для копии:
$ 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"
Итак, наш диск в недрах QEMU называется «#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"
}
С этого момента вся информация об измененных в результате работы ВМ блоках диска будет накапливаться в «my_bitmap». Останавливать ВМ нельзя, т.к. тогда информация будет утрачена.
- Операции по копированию QEMU будет проводить в фоновом режиме, для получения результата завершения запускаем второй терминал и выполняем в нем команду ожидания:
$ virsh qemu-monitor-event --timeout 3600 --event BLOCK_JOB_COMPLETED
- Возвращаемся в первый терминал и запускаем процесс получения начальной (полной) копии диска ВМ. Записывает её в файл my_vm.inc.0. Будем использовать формат копии qcow2 для сокращения размера:
$ 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
- При наступлении времени очередного копирования создаем заготовку первого инкремента my_vm.inc.1, как файла изменений для полного файла (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"
}
Ожидание завершения операции - такое же. В файл my_vm.inc.1 попадут все изменения, призошедшие после создания «my_bitmap».
- Аналогично при очередном копировании создаем заготовку второго инкремента, как файла изменений для первого (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"
}
В файл my_vm.inc.2 попадут все изменения, призошедшие после копирования my_vm.inc.1.
Процесс копирования инкрементов может аналогично продолжаться и далее .
Копия ВМ содержит 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
Далее процесс периодически повторяется. При восстановлении из копии цепочка будет состоять всего из двух файлов, что ускорит процесс.
Результаты.
Эта страхолюдная механика проверена для случаев ручного создания, удаления, изменения файлов внутри ВМ. Как это работает в более серьезных случаях - проверяйте и пишите.
Ссылки:
Features/IncrementalBackup: https://wiki.qemu.org/Features/IncrementalBackup
Dirty Bitmaps and Incremental Backup: https://qemu-project.gitlab.io/qemu/interop/bitmaps.html