上周又有同事问我在线备份虚拟机到底怎么搞。他盯着一台跑了三百多天的 KVM 虚拟机,既不敢停机,也不敢直接去 cp 那个正在写入的 qcow2 文件,怕恢复出来文件系统是一锅粥。我把 virtnbdbackup 的仓库地址丢给他,让先看文档再动手。过两天他回了我一句:这工具怎么这么晚才知道。
如果你是 KVM/QEMU 平台的使用者,正愁“运行中的虚拟机如何保证一致性又不用停业务”,virtnbdbackup 是我目前用过最顺手的一套开源方案。它通过 libvirt 和 NBD(Network Block Device)机制,把虚拟机磁盘在不停机的前提下导出成一个稳定的块设备视图,备份出来的结果与 guest 内部业务写入保持一致。它支持全量、增量、差异三种模式,配合恢复工具 virtnbdrestore 能把备份目录还原成一块可以直接被 libvirt 挂载的磁盘镜像。运维、虚拟化管理员、负责备份脚本开发的人,用它都能省下大量时间。
这篇文章我会从原理、选型、部署、日常操作、恢复演练到踩坑记录完整讲一遍,按我实际使用的经验来说,不藏私。
1. 作为备份工具,virtnbdbackup 解决的是 KVM 环境里的哪类老大难
1.1 传统备份方式为什么每次都在“停机”和“一致性”之间纠结
自建 KVM 环境里,虚拟机备份不外乎那几种老路子,但每一条都有明显代价。
第一种是停机后直接拷贝磁盘文件。把虚机关掉,cp 或者 dd 一份磁盘镜像,想怎么拷怎么拷,恢复时 100% 一致。问题在于业务中断时间完全不可控,数据库实例、核心 Web 服务这类东西根本不可能给你一个“随便停”的窗口。
第二种是 qcow2 内部快照。virsh snapshot-create-as 然后用 virsh snapshot-list 管理,听起来很美。但 QEMU 在做内部快照时需要把当前状态写出去,磁盘往往要冻结一段时间。而且快照链越长,虚拟机的随机写性能越差,快照节点之间相互引用,一旦误删中间节点,整条链就废了。我在生产环境基本只用这种方式做短时间测试,不敢拿它当长期备份手段。
第三种是 LVM 快照。前提是你的虚拟磁盘文件本身放在 LVM 卷里,然后用 lvcreate -s 做外部快照。这个办法需要预分配快照空间,空间耗尽快照会自动失效;而且只适合存储层是 LVM 的场景,换到 ZFS、NFS 或者 Ceph 就不适用了。
最“野”的做法是直接在虚拟机运行期间 copy qcow2 文件。我见过很多新手这么干,操作简单,拷贝过程中虚拟磁盘文件持续变化,得到的备份文件从块分配表到磁盘数据都可能是半新半旧的状态。用 qemu-img check 能查出一堆不一致,恢复出来能不能启动全靠运气。
说到底,在线虚拟机的磁盘备份最难的不是“把数据读出来”,而是“在读的同时,保证读到的数据是一个逻辑上一致的磁盘状态”。virtnbdbackup 正是围绕这点设计的。
1.2 NBD 端点:在不打扰业务的前提下拿到一致磁盘视图
virtnbdbackup 的底层机制值得先弄懂。它不直接操作 qcow2 文件,而是通过 libvirt 接口调度 QEMU,把虚拟机的磁盘设备以 NBD 协议导出一个块设备端点,然后从该端点读取数据。
NBD 全称 Network Block Device,在 QEMU 项目里已经是非常成熟的特性。手写 qemu-nbd 也能实现类似的导出,比如 qemu-nbd --connect=/dev/nbd0 disk.qcow2,但 virtnbdbackup 把这个过程封装成和虚拟机生命周期紧密绑定的自动流程:它让 QEMU 自身基于当前磁盘镜像的稳定一致视图生成一个 NBD server,备份进程作为一个普通客户端去读取。
这个视图为什么能“一致”?关键在于 QEMU 的镜像机制。备份开始后,QEMU 把正在使用的活跃镜像重新打开,并让新的写入要么走临时层,要么落在 bitmap 标记上。备份进程读取的是 QEMU 提供的一份稳定快照视图,而不是“文件当前读取瞬间的字节状态”。你不需要关心底层的 dirty bitmap、临时 overlay 这些细节,virtnbdbackup 和 libvirt 会配合处理好。备份期间,guest 正常读写不受影响,业务不需要关机,磁盘文件也不需要冻结。
增量备份能成立,同样依赖 QEMU 的 dirty bitmap 能力。第一次全量备份后,设备上已经变化的块会被 QEMU 用 bitmap 标记出来,下次增量时只需要读取这些变化的块,不必整个磁盘全量扫一遍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 全量、增量、差异:三种备份模式到底怎么选
2.1 全量备份的场景与命令行示例
全量备份是所有备份策略的基线,它包含虚拟磁盘上全部数据块。virtnbdbackup 中全量模式的触发参数是 -l full。典型命令长这样:
bash复制virtnbdbackup -d vm01 -l full -p /data/backup/vm01
参数含义很直观:-d 指定要备份的 libvirt 域(也就是虚拟机名字),-l 指定备份级别,-p 指定输出目录。执行完成后会在 /data/backup/vm01 下生成一个独立备份目录,里面包含了磁盘数据的分片文件和描述元数据。
全量备份什么时候做?我的习惯是三条线:第一次接入备份时必做;恢复演练或迁移磁盘后必做;增量备份链条过长导致恢复性能下降时重新做一次。不要试图长期只靠增量叠增量,增量链越深,每次恢复要合并的数据越多,出问题的面也越广。通常我会每周固定做一次全量,频率可以根据数据变化量调。
2.2 增量备份与差异备份:区别一张表
很多人分不清增量(inc)和差异(diff)的区别,选错模式之后恢复时间完全失控。差异备份指“相对于上一次全量备份以来变化的数据”,增量备份指“相对于上一次任何类型备份以来变化的数据”。两者都依赖 dirty bitmap,区别在于 bitma的参考基准。
| 对比项 | 增量备份(inc) | 差异备份(diff) |
|---|---|---|
| 备份内容 | 上一次备份后变化的块 | 上一次全量备份后变化的块 |
| 备份时间 | 短,每天变化量小 | 随着距上次全量时间变长而变长 |
| 备份体积 | 小 | 中 |
| 恢复复杂程度 | 需要按链顺序合并多个备份点 | 只需要一个全量 + 一个差异 |
| 适合场景 | 日常高频备份、追求最小备份窗口 | 需要快速恢复、且能容忍较大备份体积 |
命令上,增量模式是 -l inc,还必须通过 -b 参数指明基础备份点:
bash复制virtnbdbackup -d vm01 -l inc -p /data/backup/vm01 -b /data/backup/vm01/full
这里 -b 指向的是“本次增量之前的那一次备份目录”。工具会读取该目录中保存的 bitmap 元数据,和磁盘当前 bitmap 做对比,算出变化块。差异模式同理,把 -l 换成 diff 即可。
2.3 我自己的选型逻辑
在用到现在这套方案之前,我也纠结过一阵子。实际跑下来我的策略是:每周一次全量备份,周一凌晨执行,目的是控制增量链长度;周二到周六每天一次增量备份,备份窗口控制在分钟级;每月第一周再补做一次差异备份,防止全量点坏了以后整个链路全部报废。
RTO 需求不一样,选型可以反过来:如果你需要恢复到任意一个时间点,那增量链必不可少;如果你的主要目标是“出事后快速回到最近某个状态”,那么“全量 + 每天差异”其实更好,因为恢复时只需要两个点,比增量链逐个合并快得多。不要盲目跟随别人的策略,先想清楚业务能接受的恢复窗口是多长。
3. 安装部署和第一个全量备份:从零跑通完整流程
3.1 依赖安装与版本要求
从零开始部署,先确认宿主机的几个关键依赖。
- Python 3 环境,以及
libvirt-python(即 python3-libvirt)。 - libvirt 服务端正常运行,
virsh list能看到虚拟机。 - QEMU 版本最好在 3.0 以上,dirty bitmap 的持久化能力在后续版本才稳定,老版本容易出现 bitmap 丢失或冲突。
- 运行备份命令的用户需要对 libvirt 有访问权限。最省事的做法是直接用 root 执行,或者把运维账号加入 libvirt 用户组,
virsh -c qemu:///system list不报错才算权限可用。
安装本身不复杂。GitHub 上项目仓库名是 abbbi/virtnbdbackup,直接拿 Releases 里的安装包即可;部分发行版也能通过包管理器安装:
bash复制# Ubuntu / Debian
apt install virtnbdbackup
# CentOS / RHEL 系
dnf install virtnbdbackup
# 通用方式,从源码或源码包安装
pip install virtnbdbackup
不同发行版的包更新速度不一样,个人更推荐从 Release 页拉最新包。这个项目迭代不算慢,老版本对一些新 QEMU 的兼容性会有坑,尽量别停在远古版本。
一个最容易忽略的点:如果宿主机 libvirt 大版本升级过,请同步升级 python3-libvirt。libvirt-python 的 API 绑定跟服务端版本强相关,版本错位时容易出现类似“备份开始时建 NBD 端点失败”的诡异问题,排查时非常浪费时间。
3.2 执行第一次备份并解读输出
依赖就绪后,先对一台测试虚拟机执行全量备份。我建议不要在第一天就直接备份生产环境,先用一台磁盘格式最简单、数据量最小的虚拟机跑一遍流程,确认输出、网络、存储路径都没问题。
bash复制virtnbdbackup -d vm-test -l full -p /data/backup/vm-test
执行过程中值得关注几个点。首先,备份不是直接读文件,它会先和 libvirt 建立连接,由 libvirt 触发 QEMU 的 NBD 导出,所以命令输出里会有类似“connecting to nbd”的日志。其次,整个磁盘的数据会被分片读取并写出,备份目录不是单个文件,而是多个分片文件加元数据文件的集合。这样设计的核心好处是并行度更高,也能在一定程度上兼容将来做去重或分块校验。
备份耗时取决于磁盘大小、虚拟磁盘的实际已用量以及宿主机的存储性能。对于大多数只有几十 GB 有效数据的虚拟机,第一次全量通常在几分钟内完成。如果备份耗时异常长,优先检查是不是有别的备份工具同时在跑、目标存储是否正在高负载。
3.3 备份输出目录与文件结构
备份完成后,进目录看一眼结构。通常能看到描述备份级别和配置的描述文件,以及磁盘数据的分片文件。需要记住一个原则:这个目录整个都别乱动,不要手工删里面的某些分片文件,也不要只拷贝其中一部分。恢复时工具会依赖这些描述文件和分片文件协同工作。
如果你需要在对象存储上保留备份,有个比较实用的做法:先备份到本地目录,再通过 s3fs 或 rclone mount 的方式将对象存储挂载为本地路径。把这套逻辑直接接到 cron 脚本里即可。不过我不建议让 virtnbdbackup 直接写 S3FS 挂载目录,因为分片文件数量多、随机写零碎,在 FUSE 文件系统上性能会很差。实测下来,先写本地再异步上传,稳定性好得多。
3.4 备份时的几个通用选项
除了核心参数,日常我还会用到下面几个选项:
--include-cfg:把虚拟机的 XML 配置也一并保存进备份目录。恢复后可以直接拿到原虚拟机的硬件配置,非常实用。-z:备份过程中对分片做压缩处理。如果磁盘数据大部分是已分配但实际为空洞的空间,压缩效果比较明显。- 时间戳相关参数:用于控制备份描述文件的命名风格,脚本化批量备份时建议开启,否则多个备份点容易混淆。
这些参数在不同版本里命名可能有差异,跑一遍 virtnbdbackup --help 看当前版本的实际支持情况。
4. 恢复不止是“拷回去”:virtnbdrestore 的完整用法
4.1 从全量备份恢复磁盘镜像
备份做得再勤,不会恢复等于白做。很多工具的问题恰恰在恢复环节,恢复操作比备份复杂,而且不常练,真出事时手忙脚乱。virtnbdbackup 配套的 virtnbdrestore 工具就是干这个的。
最简单场景,从全量备份恢复成一个原始磁盘镜像:
bash复制virtnbdrestore -p /data/backup/vm-test -b /data/backup/vm-test/full -o /var/lib/libvirt/images/vm-test-restored.qcow2
-p 指向备份根目录,-b 指向要用到的备份点,-o 指定恢复出的磁盘文件路径。恢复完成后,用 qemu-img info 查看恢复出的镜像格式,再临时建一个测试虚拟机把磁盘挂上去验证文件系统是否完整。
我的习惯是恢复后至少做两层验证:第一层是 libvirt 层面能否识别、能否启动;第二层是进入 guest 内跑一次文件系统检查和业务数据一致性抽查。不要只看虚拟机能否开机就宣布恢复成功,应用日志里的表损坏往往延迟暴露。
4.2 用增量链路恢复:如何合并多个备份点
增量备份真正给恢复带来的麻烦是“合并”。假设你做了 1 次全量、5 次增量,现在要恢复到第 5 个增量点。命令依然很简单:
bash复制virtnbdrestore -p /data/backup/vm-test -b /data/backup/vm-test/inc/5 -o /var/lib/libvirt/images/vm-test-restored.qcow2
指定 -b 为最新增量点后,工具会从初始全量点开始,自动把后续增量的变化叠加到输出镜像上,生成一个包含全部数据的最新磁盘。
这里有个实操中很容易踩的误区:恢复时不能在 Ceph、iSCSI 这类远端块设备上直接做叠加写入,输出路径最好指向本地文件系统。至少我试过的环境中,本地磁盘镜像恢复是兼容性最好的方式。恢复完成后再用 qemu-img 转格式、迁移到远端存储,都比直接让工具写远端可靠。
4.3 用恢复出的镜像克隆虚拟机
恢复出的磁盘文件还有一个常见用途:克隆新虚拟机。比如测试环境需要一份和生产一模一样的库,直接恢复最新备份点,用 virtinst 按原 XML 配置新建虚拟机,磁盘路径指到恢复出的镜像。
如果原虚拟机用了 <driver type='qcow2'/>,恢复出的镜像一般会保持可用的 qcow2 格式。万一出现镜像格式不匹配,用 qemu-img convert 转一下即可:
bash复制qemu-img convert -f qcow2 -O qcow2 vm-test-restored.qcow2 vm-test-clone.qcow2
4.4 灾难恢复演练怎么做
我用了这个工具之后最明显的改变是开始定期做灾备演练。流程不复杂:每月挑一台非核心虚拟机,从最近备份点完整恢复,然后把恢复出来的虚拟机启动起来,检查关键服务端口,核对核心数据,再正常关机删除。
这类演练不需要通知所有业务方,但一定要留下记录。有几个备份文件长期没做过恢复验证,等真出问题再发现备份损坏,那种情况我见过不止一次。备份系统本身也是系统,定期演练和监控缺一不可。
5. 备份策略自动化与监控:定时任务、钩子脚本与监控集成
5.1 用 cron 脚本管理全量+增量
就算 virtnbdbackup 再方便,手动备份终归不可持续。生产环境里我建议用 cron 驱动一套简单的脚本,把全量、增量、清理三件事管起来。
下面是一个可参考的示例脚本:
bash复制#!/bin/bash
# /usr/local/bin/backup-kvm.sh
DOMAIN="$1"
BACKUP_ROOT="/data/backup/${DOMAIN}"
DATE=$(date +%F)
if [ -d "${BACKUP_ROOT}" ]; then
# 目录存在,说明做过至少一次全量,跑增量
/usr/local/bin/virtnbdbackup -d "${DOMAIN}" -l inc -p "${BACKUP_ROOT}" -b "$(ls -dt ${BACKUP_ROOT}/*/ | head -n1)"
# 清理:保留最近 30 天的备份点
find "${BACKUP_ROOT}" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \;
else
# 首次备份,跑全量
/usr/local/bin/virtnbdbackup -d "${DOMAIN}" -l full -p "${BACKUP_ROOT}"
fi
注意脚本里的 -b 取的是备份根目录下最近的一个子目录,不同版本的目录层级结构可能不同,务必先确认你自己的备份目录长什么样再套用脚本。crontab 里按需配置:
crontab复制# 每天凌晨 2 点备份数据库相关虚拟机
0 2 * * * /usr/local/bin/backup-kvm.sh vm-db
0 3 * * * /usr/local/bin/backup-kvm.sh vm-web
5.2 监控集成:Zabbix / Graphite 与最关键的指标
virtnbdbackup 有向外部监控系统上报状态的能力,文档里能看到 Graphite、Zabbix 相关的配置参数。不过不同的版本接入方式有所差异,这里更通用的建议是:无论你用哪种监控系统,务必把三个指标监控起来。
- 备份任务退出码:备份脚本如果非零退出,必须告警。这是最重要的一条,很多备份“看似在跑,实际早已失败”。
- 备份目录新增数据量:通过 du 统计每日增量字节数,异常地增或减都值得警惕。
- 备份耗时:某天备份耗时突然翻倍,多半说明存储性能下降或数据积压,需要人工介入。
如果只想用最小成本接入告警,可以在 cron 命令末尾追加通知脚本:
bash复制0 2 * * * /usr/local/bin/backup-kvm.sh vm-db && /usr/local/bin/notify-backup-ok.sh || /usr/local/bin/notify-backup-fail.sh
这里注意 cron 的 && 和 || 组合很容易踩坑,最好写成脚本内部的 if 判断,把退出码当成变量,否则备份中途失败时通知逻辑可能不会执行。
5.3 备份完成后的自动压缩与保留策略
virtnbdbackup 备份出来的目录结构,天然适合用 zstd 或 tar 做整体压缩。把全量备份点打包成一个文件,再传到冷存储,比直接同步整个目录更省空间,也方便归档。
我的保留策略是双份:本地保留近两周的全量备份和增量链,供快速恢复;对象存储保留近半年的每周全量,作为灾备副本。传到对象存储时不要在同一个备份会话中同时跑两个传输任务,否则会占用大量网络和磁盘 IO,拖慢生产虚拟机的正常读写。
5.4 压缩命令示例
bash复制cd /data/backup && tar --zstd -cf vm-test-weekly.tar.zst vm-test
恢复时解压:
bash复制tar --zstd -xf vm-test-weekly.tar.zst -C /data/backup
压缩归档后,不要直接删原始未压缩目录,先解压一个副本验证完整性再执行删除。这种习惯能避免不少低级但致命的失误。
6. 我用 virtnbdbackup 一年后踩过的坑和对应解法
6.1 增量备份的 bitmap 依赖问题
增量备份依赖于磁盘镜像上的 dirty bitmap。bitmap 由 QEMU 持久化,但并不是所有操作都会保留 bitmap。
之前我就踩过一次:虚拟机做了全量备份后,为了回收 qcow2 文件因为频繁写入造成的膨胀,我跑了一次 qemu-img commit 和手动 blockcommit。这个操作把磁盘的活跃层数据和底层的 overlay 关系重新整理了一遍,同时也把 QEMU 里记录 dirty bitmap 的上下文给打乱了。下次增量备份时,工具基于旧 bitmap 计算变化块,备份出来的数据和实际状态对不上,恢复时才暴露出问题。
后来的规矩很简单:增量备份链路存在期间,不做任何手工 blockcommit、blockpull 操作;必须做时,先跑一次新的全量备份,让链路断点重来。
另外要注意,不同虚拟机的磁盘如果发生过设备路径变更(比如从 vda 变成 sda),备份工具记录的磁盘标识和当前设备对不上,也会出现 bitmap 匹配错误。变更过总线类型、磁盘名称之后,直接重做一次全量最稳妥。
6.2 磁盘格式与总线导致的兼容性坑
virtnbdbackup 的前提是虚拟磁盘作为块设备能通过 QEMU 的 NBD 端点导出。对常见格式 raw、qcow2 都支持良好,但有几个场景需要额外注意。
如果你的虚拟机磁盘是直接透传的 LVM 卷、iSCSI LUN 或者 Ceph RBD 设备,而不是一块普通镜像文件,NBD 导出后读取到的内容仍然是块设备层面的一致性视图,但格式转换、bitmap 支持方面可能不如 qcow2 平滑。我的建议是生产虚拟机尽量使用 qcow2 格式,既方便备份,也方便做磁盘扩容和转换。
磁盘总线的兼容性也存在变数。virtio-blk 和 virtio-scsi 在 libvirt 里的设备命名规则不同,遇到备份报告“找不到 disk”之类的问题,先检查虚拟机 XML 里 <disk> 设备类型和备份命令想的磁盘编号是不是一致。不是所有工具报错都能靠“加一个参数”解决,很多问题的根源是磁盘定义变了。
6.3 备份目录本身的安全与存储规划
备份目录放的磁盘如果和虚拟机数据盘在同一块物理盘上,一旦物理盘故障,备份和原始数据一起没。这个道理说出来谁都懂,实际环境中却极常见。备份目录尽量放到独立存储,不为省事妥协。
磁盘满是最容易踩的坑。备份进程执行到一半才发现目标目录空间不足,导致分片文件不完整,是一个需要重视的风险。我的做法是:脚本在备份前先执行一次容量检查,计算目标目录剩余空间与上次全量量的比值,低于阈值直接发告警并退出,避免写到半空。
备份目录的权限也不可忽视。我见过因为备份文件权限是 root:root 且没有 other 读权限,导致恢复后 libvirt 无法访问恢复镜像的情况。恢复后的磁盘镜像文件记得检查属主,改到 qemu/libvirt 用户组下:
bash复制chown root:libvirt /var/lib/libvirt/images/vm-test-restored.qcow2
chmod 660 /var/lib/libvirt/images/vm-test-restored.qcow2
6.4 恢复路径上的 SELinux 标签问题
如果宿主机是默认开启 SELinux 的 CentOS/RHEL 环境,把备份文件恢复到自定义目录后,文件的安全上下文可能不对。libvirt 默认对 /var/lib/libvirt/images 目录有 virt_image_t 的标签规则,恢复到其它路径再挂给虚拟机,会直接被 SELinux 拦截。
恢复完镜像先执行:
bash复制restorecon -v /var/lib/libvirt/images/vm-test-restored.qcow2
这样能省掉很多莫名其妙的权限报错。二进制层面备份没问题,最后死在安全策略上,真的很憋屈。
6.5 和另一个备份工具/脚本冲突
我刚开始用的时候,发现宿主机里已经有一个老备份脚本每天在跑,它也是通过 libvirt 做 snapshot。两个工具同时在一个虚拟机上进行快照相关操作,直接导致备份失败过一次。从那以后我在部署新工具前都会先排查现有 cron 任务,把同质化的备份任务全部停掉。
最后再分享一条我自己的使用习惯:每换一个大版本,先在测试虚拟机上来一轮全量备份加恢复演练,再切生产。工具本身很可靠,但虚拟机环境千差万别,QEMU 版本、libvirt 配置、存储驱动不同,组合起来的隐性坑永远比预想多。把每一次版本升级都当作一次新的部署来对待,才是真正让备份体系保持可信的关键。
