Velero Block Data Mover 设计解析:基于 CBT 的块级备份/恢复架构与实现
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
块级数据移动是 Velero 在 CSI 快照数据移动(Volume Snapshot Data Movement)之上推出的重要能力,它借助 Kubernetes Change Block Tracking(CBT)API 与 Kopia 仓库的增量感知扩展,让块存储卷只传输真实变化的数据,从而显著降低网络、仓库与备份存储的开销。本文以仓库内 设计文档 为骨架,结合 Unified Repository 设计、Volume Snapshot Data Movement 设计 与当前源码实现,系统讲解块数据移动器的架构、数据路径、CBT 层、增量仓库扩展、CRD 与 CLI 变更,帮助你理解 Velero 如何在文件系统级备份之外提供更高效、更低 TCO 的块级备份/恢复方案。
背景:为什么需要块级备份/恢复
Kubernetes 的持久卷有两种卷模式:FileSystem与Block。底层存储既可以用块存储来提供这两种模式的卷,也可以用文件存储来提供FileSystem模式卷。由此引出一个基本事实:
- 由块存储提供的卷(无论卷模式是
FileSystem还是Block),都可以从块层面进行备份/恢复; - 只要数据能从文件系统访问,就可以从文件系统层面备份/恢复,即
FileSystem模式卷无论底层存储类型如何,都能走文件系统级备份; - 当
FileSystem模式卷由块存储提供时,两种备份方式都可用。
Velero 现有的 CSI 快照数据移动(由 VBDM 实现)只内置了文件系统上传器,因此备份/恢复全部发生在文件系统层面。而一旦条件允许,块级备份/恢复相比文件系统级有着明确优势:
- 可借助 CBT 只处理最小数据量:大幅降低对网络、备份仓库和备份存储的开销,从而显著降低 TCO(总拥有成本);
- 吞吐与资源消耗更优:无需处理文件系统的复杂度,尤其适合文件系统内存在海量小文件的场景;
- 对操作系统依赖更少:上传器无需操作系统感知卷内的文件系统。
设计文档写作时,Kubernetes CBT API 已趋于成熟并接近 Beta,许多平台/存储厂商已经或即将支持。因此 Velero 需要交付块级备份/恢复,并在满足以下条件时推荐用户优先于文件系统数据移动器使用:
- 卷由块存储提供,块级访问可行;
- 平台支持 CBT。
同时,文件系统级备份/恢复在以下场景依然有价值:卷由文件存储提供(如 AWS EFS、Azure File、CephFS 等);卷由块存储提供但 CBT 不可用;卷不支持 CSI 快照而只能使用 Velero PodVolume Backup。设计上,块数据移动器应基于 VGDP、VBDM 与 VGDP 微服务构建,以复用这些模块已有的丰富能力(并发、节点选择、缓存卷、去重、压缩、加密等)。
两个需要说明的平台限制:
- VBDM 同时支持 Linux 与 Windows 节点,但 Windows 容器不支持块模式卷,因此在 Windows 容器移除该限制前,无法从 Windows 节点进行块级备份/恢复;集群同时存在 Linux 与 Windows 节点时,块数据移动器只能运行在 Linux 节点;
- Kubernetes CBT 服务与 Velero 都运行在集群边界内,即使后端存储被多个集群共享,Velero 也只能保护其所在集群的工作负载。
目标与非目标
设计目标聚焦于为 CSI 快照数据移动 增加块数据移动器:
- 支持
FileSystem与Block两种模式卷的块级全量备份; - 支持两种模式卷的块级增量备份;
- 支持从全量/增量备份进行块级恢复(两种卷模式);
- 支持从 Linux 集群节点对 Linux 与 Windows 工作负载进行块级备份/恢复;
- 支持既有特性(负载并发、节点选择、缓存卷、去重、压缩、加密等);
- 支持同一备份/恢复中同时存在文件系统级与块级处理的卷。
非目标同样明确:
- PodVolume Backup 只做文件系统级备份/恢复,不支持块级;
- 由文件存储提供的卷只能走文件系统级备份/恢复;
- 不支持从 Windows 节点备份/恢复;
- 块级增量备份对备份仓库有特殊能力要求,而 Velero Unified Repository 支持多种仓库;当前设计只聚焦 Kopia 仓库,其他仓库的块级增量备份将在其接入 Unified Repository 后再考虑。
总体架构与数据路径
块数据移动器并非另起炉灶,而是基于现有 VBDM 增量改造。下图展示了 VGDP 集成到 Unified Repository(由 Kopia 仓库实现)后的数据路径架构:
在 VGDP 中,一个新的块数据移动器被添加在既有文件系统数据移动器旁边,两者通过 Unified Repo 接口对同一个备份仓库进行读写;同时,Unified Repo 接口与备份仓库需要增强以支持增量备份。关于 VGDP 的更多细节可参考 Unified Repository 设计、Volume Snapshot Data Movement 设计 与 VGDP 微服务设计。
备份架构
备份架构复用了现有 VBDM,主要变化点有四个:
- Exposer:无论源 PVC 的模式如何,始终创建块模式的 backupPVC;
- CBT 层:新增的层次,负责检索、转换与存储变化块,通过 gRPC 与 CSI SnapshotMetadataService 交互;
- Uploader:新增块上传器,与 CBT 层交互,持有从块设备高效读数据以及向 Unified Repository 增量写数据的专用逻辑;
- 扩展 Kopia 仓库:在 Kopia 的 CAOS 之上新增增量感知对象扩展(Incremental Aware Object Extension)以支持增量写,Kopia 仓库的其他部分(既有 CAOS 与 CABS)保持不变。
恢复架构
恢复同样复用 VBDM,主要变化点:
- Exposer:当 restorePV 为块模式时,需要把 restorePV 重新绑定(rebind)到目标 PVC(目标 PVC 既可以是文件系统模式也可以是块模式);
- Uploader:同一个块上传器持有高效写块设备、以及从 Unified Repository 备份链读取数据的专用逻辑。
数据移动器类型选择
每次备份的选择(Per Backup Selection)
当前备份接受一个DataMover参数:当值为空或velero时使用 VBDM。引入块数据移动器后,VBDM 将包含两种类型:Velero 文件系统数据移动器与 Velero 块数据移动器。设计引入了两个新取值:
velero-block:使用 Velero 块数据移动器;velero-fs:使用 Velero 文件系统数据移动器。
为向后兼容,velero仍是合法值,它表示"默认数据移动器",默认值可以在不同发行版之间变化:当前默认是 Velero 文件系统数据移动器,未来可切换为 Velero 块数据移动器。这些取值在源码中已有对应定义,见 pkg/util/datamover/datamover.go(DataMoverTypeVeleroFs = "velero-fs"、DataMoverTypeVeleroBlock = "velero-block")以及 pkg/uploader/types.go 中的BlockType = "velero-block"常量。
通过 Volume Policy 进行逐卷选择
实际场景中,用户可能在一次备份里希望部分卷走文件系统数据移动器、另一部分卷走块数据移动器。为此设计采用"每次备份选择 + Volume Policy"的组合方案。VolumePolicy 的核心数据结构如下(与 internal/resourcepolicies 中实现对应):
type volPolicy struct { action Action conditions []volumeCondition } type volumeCondition interface { match(v *structuredVolume) bool validate() error } type structuredVolume struct { capacity resource.Quantity storageClass string nfs *nFSVolumeSource csi *csiVolumeSource volumeType SupportedVolume pvcLabels map[string]string pvcPhase string } type Action struct { Type VolumeActionType `yaml:"type"` Parameters map[string]any `yaml:"parameters,omitempty"` } const ( ConfigmapRefType string = "configmap" Skip VolumeActionType = "skip" FSBackup VolumeActionType = "fs-backup" Snapshot VolumeActionType = "snapshot" )action.parameters用来提供动作的额外信息,恰好适合区分 Velero 文件系统与块数据移动器。Velero 内置数据移动器支持在parameters中携带dataMover键,取值velero-fs或velero-block,含义与"每次备份选择"一致。
使用示例:一次备份中同时使用velero-block与velero-fs——
- 备份的
DataMover参数设置为velero-block; - 在 Volume Policy 中增加一条记录,用
conditions过滤希望走文件系统数据移动器的卷,action.type设为snapshot,并在action.parameter中加入dataMover:velero-fs。
这样,所有被conditions匹配的卷将使用 Velero 文件系统数据移动器,其余卷回退到备份级选择的 Velero 块数据移动器。反之亦可:将备份级方法设为文件系统数据移动器,再为部分卷选择块数据移动器。每个卷最终选用的数据移动器需要记录到volumeInfo.json中。
控制器与 Exposer
控制器
Backup 控制器与 Restore 控制器保持不变,仍然通过异步操作(async operations)与 VBDM 交互。DataUpload 控制器与 DataDownload 控制器基本保持不变,仅做少量修改以正确处理数据移动器类型与备份类型,并将其传递给 exposer。得益于 VGDP 微服务 的设计,控制器与 VGDP 几乎解耦,因此无需大改。相关控制器的现有实现可参考 pkg/controller 与 pkg/controller。
CSI Snapshot Exposer
复用现有 CSI Snapshot Exposer,但需按访问模式决定 backupPVC 的卷模式。对 Velero 块数据移动器,访问模式恒为Block,因此 backupPVC 的卷模式恒为Block。backupPVC 以正确的卷模式创建后,现有代码即可正确地创建 backupPod 并挂载 backupPVC。实现位于 pkg/exposer/csi_snapshot.go。
Generic Restore Exposer 与重新绑定流程
复用现有 Generic Restore Exposer,但工作流需要调整。对块数据移动器,restorePV 始终是块模式,而目标 PVC 可能是文件系统模式或块模式。Kubernetes 不允许将 PV 绑定到卷模式不匹配的 PVC 上。因此,Volume Snapshot Data Movement 设计 中介绍的Finish Volume Readiness工作流被修改为:
- 恢复完成、restorePV 创建后,将 restorePV 的
deletionPolicy设为Retain; - 创建另一个 rebindPV,复制 restorePV 的
volumeHandle,但volumeMode与目标 PVC 匹配; - 删除 restorePV;
- 将 rebindPV 的
claimRef字段指向目标 PVC; - 给 rebindPV 打上
velero.io/dynamic-pv-restore标签。
这样,目标 PVC 会被 Kubernetes 立即绑定到 rebindPV。该新流程对文件系统数据移动器同样有效,因此旧流程将被替换,只保留新流程。相关实现可参考 pkg/exposer/generic_restore.go。
VGDP 与 Unified Repo 的增量能力增强
块数据移动器下,每个卷对应一个 Unified Repo 对象,并伴随保存描述卷的元数据。备份写入采用"可跳过写"(skippable-write)方式:
- 对不跳过的数据区间,以真实数据写入对象;
- 对被跳过的区间,数据要么填 ZERO、要么从父对象克隆。具体地,全量备份填 ZERO;增量备份从父对象克隆。
ObjectWriter 接口扩展
为支持可跳过写,ObjectWriter接口需要扩展以支持io.WriterAt。当前仓库中 pkg/repository/udmrepo/repo.go 已实现该接口:
type ObjectWriter interface { // Write writes data to the object in the sequential manner. Write([]byte) (int, error) // WriterAt is used in the cases that the object is not written sequentially. WriteAt([]byte, int64) (int, error) // Checkpoint is periodically called to preserve the state of data written to the repo so far. // Checkpoint returns a unified identifier that represent the current state. // An empty ID could be returned on success if the backup repository doesn't support this. Checkpoint() (ID, error) // Result waits for the completion of the object write. // Result returns the object's unified identifier after the write completes. Result() (ID, error) // Close closes the object writer and releases all resources. Close() error }ObjectWriteOptions 扩展
要从父对象克隆数据,调用方需要指定父对象,因此ObjectWriteOptions增加了ParentObject字段;既有AccessMode用于指示数据访问类型(文件系统或块)。仓库 pkg/repository/udmrepo/repo.go 中的完整定义如下:
// Below consts describes the data type of one object. // Metadata: This type describes how the data is organized. // For a file system backup, the Metadata describes a Dir or File. // For a block backup, the Metadata describes a Disk and its incremental link. ObjectDataTypeUnknown int = 0 ObjectDataTypeMetadata int = 1 ObjectDataTypeData int = 2 // Below consts defines the access mode when creating an object for write ObjectDataAccessModeUnknown int = 0 ObjectDataAccessModeFile int = 1 ObjectDataAccessModeBlock int = 2 ObjectDataBackupModeUnknown int = 0 ObjectDataBackupModeFull int = 1 ObjectDataBackupModeInc int = 2 // ObjectWriteOptions defines the options when creating an object for write type ObjectWriteOptions struct { FullPath string // Full logical path of the object DataType int // OBJECT_DATA_TYPE_* Description string // A description of the object, could be empty Prefix ID // A prefix of the name used to save the object AccessMode int // OBJECT_DATA_ACCESS_* BackupMode int // OBJECT_DATA_BACKUP_* AsyncWrites int // Num of async writes for the object, 0 means no async write ParentObject ID // The object in the previous snapshot, for incremental backup }BackupRepo 接口扩展
为了让非 Kopia 上传器也能把快照与元数据保存到 Unified Repo,BackupRepo接口增加了快照相关方法与元数据相关方法(均已在 pkg/repository/udmrepo/repo.go 落地):
// SaveSnapshot saves a repo snapshot SaveSnapshot(ctx context.Context, snapshot Snapshot) (ID, error) // GetSnapshot returns a repo snapshot from snapshot ID GetSnapshot(ctx context.Context, id ID) (Snapshot, error) // DeleteSnapshot deletes a repo snapshot DeleteSnapshot(ctx context.Context, id ID) error // ListSnapshot lists all snapshots in repo for the given source ListSnapshot(ctx context.Context, source string) ([]Snapshot, error) // WriteMetadata writes metadata to the repo, metadata is used to describe data, e.g., file system // dirs are saved as metadata WriteMetadata(ctx context.Context, meta *Metadata, opt ObjectWriteOptions) (ID, error) // ReadMetadata reads a metadata from repo by the metadata's object ID ReadMetadata(ctx context.Context, id ID) (*Metadata, error)Unified Repo 的 kopia-lib 通过调用对应的 Kopia 仓库函数实现这些接口。在 pkg/repository/udmrepo/kopialib/lib_repo.go 中可以看到:当opt.AccessMode == udmrepo.ObjectDataAccessModeBlock时,NewObjectWriter返回增量感知的kopiaObjectWriterEx实现,它支持WriteAt、Checkpoint、异步写入与错误面向上层暴露;同文件的测试 pkg/repository/udmrepo/kopialib/lib_repo_ex_test.go 覆盖了WriteAt、并发写、异步写、大稀疏写等多种场景。
Kopia 仓库:增量感知对象扩展
Kopia 仓库的 CAOS 实现了 Unified Repo 的 Objects,但它只支持全量与顺序写。为支持可跳过写,设计在既有 CAOS 之上创建了Incremental Aware Object Extension(增量感知对象扩展):
块地址表(BAT)
Kopia CAOS 使用块地址表(Block Address Table,BAT)跟踪对象,全量与增量备份都复用它。在增量感知对象扩展中,一个对象代表一个卷:
- 全量备份:跳过的区域由扩展写成全 ZERO——因为 Kopia 仓库接口不支持可跳过写。这没问题,ZERO 数据会被 Kopia 仓库去重,实际不会写入备份存储;
- 增量备份:跳过的区域从父对象克隆表项;写过的区域写入 Kopia 仓库并生成新表项。最终,扩展为增量对象生成覆盖其全部逻辑空间的新块地址表。
增量感知对象扩展在ObjectWriteOptions的AccessMode为块模式数据访问时自动激活。
去重与 1MB 块大小
增量感知对象扩展使用固定大小分割器做去重,对块级备份足够,原因有三:
- 磁盘写不同于文件,不会向磁盘中间插入数据,只做原地更新或追加,数据不会在两个磁盘或同一磁盘的两次备份之间漂移;
- 文件系统对磁盘的 IO 一般按特定大小对齐(如 NTFS 与 ext4 的 4KB),只要块大小是该大小的倍数,就能有效避免一次 IO 破坏两个去重块;
- 当磁盘作为无文件系统的裸块设备使用时,IO 同样按特定边界对齐。
块大小特意选择1MB,理由:
- 1MB 是文件系统 4KB 或裸块设备常见块大小的倍数;
- 1MB 是现代操作系统(MBR 与 GPT)的分区起始边界,分区元数据可被隔离到独立块中;
- 块越多,仓库索引越多,1MB 对 Kopia 仓库的索引开销是适中的取值。
复用 BAT 带来的收益与性能
由于复用并保持既有 CAOS 块地址表不变,带来以下收益:
- 所有表项仍由 Kopia CAOS 管理,Velero 无需额外维护数据;
- Velero 块上传器写出的对象对 Kopia 依然可识别(全量与增量皆可);
- Kopia 仓库既有的数据管理(快照 GC、仓库维护等)对块上传器生成的对象依然有效。
更重要的是性能:增量写时不从父对象复制任何数据,只克隆对象块地址表项;备份删除时也不需要移动任何数据,只删除对象的 BAT。
上传器行为约束
块上传器的可跳过写必须对齐 1MB 边界,因为增量感知对象扩展需要从父对象克隆被跳过的表项。文件系统上传器仍使用变长去重,两种上传器的数据可以共存于同一个 Kopia 仓库(虽然通常不会互相去重)。此外,卷可以被扩容,且卷大小不一定对齐 1MB 边界;由于增量感知对象扩展不能部分复制 BAT 表项,上传器需要妥善处理卷大小变化。
CBT 层
CBT 层提供两类功能:
- 全量备份:提供已分配的数据区间。例如 1TB 卷中只有 1MB 文件时,上传器可跳过无真实数据的区间;
- 增量备份:基于提供的父快照提供变化的数据区间,上传器跳过未变化数据从而实现增量备份。
对情况 1,上传器以已分配数据的偏移调用 Unified Repo Object 的WriteAt,偏移之前的区间由统一仓库填 ZERO;对情况 2,上传器以变化数据的偏移调用WriteAt,偏移之前的区间由统一仓库从父对象克隆。
每次备份都会保存一个 changeId,下一次备份取出父快照的 changeId 并用它检索 CBT。从 Kubernetes API 拿到的 CBT 是一组BlockMetadata,每个区间可以是固定大小或可变大小;块上传器需要维护对自身备份仓库与上传器友好的粒度。从 API 侧,GetMetadataAllocated或GetMetadataDelta会被循环调用直到取回全部BlockMetadata;同时考虑到上传器内部(读写多流)的复杂度,工作流应由上传器驱动而非 CBT 迭代器驱动,因此实践中应在传给上传器之前把全部已分配/变化块取回并保存。
由于直接保存BlockMetadata列表非常耗内存,设计采用Bitmap数据结构保存已分配/变化块(称为 CBT Bitmap)。CBT Bitmap 的块大小可设为 1MB 或其倍数,但更大的块会放大备份体积,因此采用1MB。
CSI Snapshot Metadata Service、CBT 层与上传器之间的交互如下:
这样 CBT 层与上传器解耦,CBT Bitmap 充当上传器的北向参数。
块上传器
块上传器由异步运行的 reader 与 writer 组成:
- 备份时:reader 从块设备读数据并参考 CBT Bitmap 判断已分配/变化块;writer 把数据写入 Unified Repo;
- 恢复时:reader 从 Unified Repo 读数据;writer 把数据写入块设备。
reader 与 writer 通过环形缓冲区连接:reader 把块数据推入环形缓冲区,writer 取出数据写入目标。为提升性能,块设备以direct IO打开,避免数据无谓地经过系统缓存。
恢复时,为优化写吞吐与存储用量,零块应被跳过(恢复到新卷)或取消映射(恢复到已有卷)。为统一覆盖两种情况,使用 SCSI 命令WRITE_SAME,逻辑如下:
- 检测从备份读到的块是否全为零数据;
- 若为全零,上传器通过
BLKZEROOUTioctl 发送WRITE_SAMESCSI 命令; - 若调用失败,回退到保守方式:把全零字节直接写入磁盘。
上传器实现与操作系统相关,但由于 Windows 容器不支持块卷,当前实现仅面向 Linux。这与 Volume Snapshot Data Movement 设计 中"Kopia 块模式上传器仅支持非 Windows 平台"的说明一致。
ChangeId 与快照保留
ChangeId 的一致性保障
ChangeId 标识 CBT 生成所基于的基准,它必须严格映射到仓库中的父快照,否则增量备份将产生数据损坏。因此:
- ChangeId 与仓库快照一起保存;
- 数据移动器总是从 Unified Repo 一起查询父快照与 ChangeId,避免不匹配;
- 上传器内部,上层(DataUpload 控制器)也可提供 ChangeId 作为双重确认机制,收到的 ChangeId 会与所提供快照中的 ChangeId 重新核对。
在 Kubernetes API 中,changeId 由BaseSnapshotId表示。changeId 的检索是存储相关的:通常从 VolumeSnapshotContent 对象的SnapshotHandle获取,但某些存储也可能从其他位置获取;也就是说SnapshotHandle与 changeId 可能是两个不同的值,此时两者都需要保留。
卷快照保留策略
存储/CSI 驱动对 changeId 的支持方式取决于存储能力,分两种情形:
- 某些存储要求在调用
GetMetadataDelta时父快照(映射到 changeId)始终存在,因此只要存在基于它的增量备份,父快照就不能删除; - 某些存储计算变化时不需要父快照本身,因此父备份完成后父快照可立即删除。
现有 exposer 与情形 1 完美契合(备份完成时快照照常删除)。对情形 2,由于必须保留快照,exposer 需要如下改动:
- 每次备份结束时,把当前 VolumeSnapshot 的
deletionPolicy保持为Retain,这样备份结束时删除 VolumeSnapshot 对象,快照仍保留在存储中; - 以保留的 changeId 作为
BaseSnapshotId调用GetMetadataDelta; - 删除备份时,用保留的 snapshotHandle 重建一对 VolumeSnapshot-VolumeSnapshotContent,
deletionPolicy设为delete; - 删除重建的 VolumeSnapshot,从而从存储中删除卷快照。
由于无法自动探测某个卷支持哪种方式,设计向用户暴露了设置卷快照保留方式的接口,可放在 Volume Policy 的Action.Parameters中。默认 Velero 块数据移动器采用方式 1(卷快照不保留);若用户指定RetainSnapshot参数则采用方式 2。例如用户可以针对存储类 "xxx" 或 CSI 驱动 "yyy" 指定:通过 CSI 快照配合 Velero 块数据移动器备份并保留快照。
增量大小
备份结束时,上传器还会返回增量大小(incremental size),与 Velero 文件系统上传器一致,表示基于给定 CBT 上传器实际处理的唯一数据量。
回退到全量备份
以下情况增量备份无法继续,数据移动器回退到全量备份:
GetMetadataAllocated或GetMetadataDelta返回错误;- ChangeId 缺失;
- 父快照缺失。
回退发生时,卷从块级做全量备份,但由于备份仓库的数据去重,未分配/未变化的数据大概率会被去重。恢复时卷也会全量恢复,前述零块处理依然生效,因此未分配数据的写 IO 大概率会被消除。回退用于处理异常场景,绝大多数备份/恢复不会触发。
不规则卷大小与卷大小变化
增量备份期间块上传器 IO 必须对齐去重块大小(1MB),但用户的卷大小没有对齐的硬性要求。为支持不规则大小的卷,采取以下措施:
- 仓库中的卷对象始终按 1MB 对齐;
- 若卷大小不规则,卷对象尾部以零字节填充;
- 真实大小记录在仓库快照中;
- 恢复时按真实大小恢复数据(填充必须始终是零字节)。
当卷被扩容时,增量备份可以继续,块上传器支持写入任意大小的磁盘,无需逐案例处理。具体处理方式:
- 与 CBT 循环配合;
- 读取
RoundDownTo1M(newSize)与newSize之间的尾部数据; - 若没有尾部数据(卷大小已按 1MB 对齐),调用
WriteAt(newSize, nil); - 否则调用
WriteAt(RoundDownTo1M(newSize), taildata),taildata也填充到 1MB。
也就是说:若 CBT 覆盖卷尾部,无论扩容还是缩容,与 CBT 循环配合即可;否则卷扩容时WriteAt保证从父对象克隆适当的对象表项并为扩展区域追加零数据——特别是父卷大小不规则时,其零填充字节也会被复用,因此父对象的填充字节必须是零;卷缩容时写尾部数据可确保新卷对象填充零字节,而不是继承父对象的非零数据。
取消、并行与进度报告
- 取消:复用现有取消机制,块上传器外部无变化;上传器内部在 reader 与 writer 中嵌入取消检查点,使取消发生后执行能在合理时间内退出。
- 并行:数据移动器之间的并行复用既有机制——负载并发(load concurrency)。数据移动器内部,上传器 reader 与 writer 始终并行运行,数量恒为 1;卷的顺序读/写总是最优,没有证据表明多个 reader/writer 更有益。
- 进度报告:数据移动器外部复用既有机制;内部进度更新嵌入上传器 writer。进度结构保持原样,块数据移动器仍支持
TotalBytes与BytesDone:
type Progress struct { TotalBytes int64 `json:"totalBytes,omitempty"` BytesDone int64 `json:"doneBytes,omitempty"` }备份结束时,块数据移动器的进度同样提供GetIncrementalSize,以与文件系统数据移动器相同的方式向用户报告增量大小。该结构在 pkg/uploader/types.go 中已有定义,而SnapshotInfo中的IncrementalSize字段(pkg/uploader/types.go)正是用于承载该值。
可选备份类型:full 与 incremental
由于数据完整性等原因(例如每 1 周或 1 个月做一次全量以确保增量链间数据完整性),周期性全量备份是必要的。因此,手动备份与备份调度都应支持备份类型(full/incremental),备份类型也会写入volumeInfo.json以支持可观测性。
备份 TTL 仍用于指定备份的保留时长。默认全量与增量备份都是 30 天保留(虽然对全量备份而言并不十分合理),待 Velero 支持更精细的保留策略后可增强。当前变通做法:为同一范围的备份创建两个调度——一个用于全量备份(频率更低、TTL 更长),一个用于增量备份(正常频率、TTL 更短)。
文件系统数据移动器的对齐
目前 Velero 文件系统数据移动器不支持可选备份类型,一旦可能就总是做增量备份,从用户体验看并不合理。因此,为与块数据移动器对齐,文件系统数据移动器也将支持备份类型。其数据路径已具备该能力,只需向用户开放。
备份描述
备份描述中应体现备份类型,有两处:
- Backup CR 中的
backupType:用户选择的备份类型; volumeInfo.json中记录的备份类型:实际执行的类型。
借助这两个值,用户能知道实际备份类型以及是否发生了回退。已有备份描述中的DataMover项也应更新,以反映实际完成备份的数据移动器(可从volumeInfo.json获取)。
同步、删除、重启与日志
- Backup Sync:同步不需要额外数据,保持不变;
- Backup Deletion:删除 Velero 块数据移动器的仓库快照时不移动任何数据,因此对仓库快照而言删除逻辑不变;对卷快照保留场景,删除逻辑会相应修改以删除被保留的快照;
- Restarts:复用既有机制,无改动;
- Logging:日志机制不变。
CRD 变更
Backup CRD:新增backupType字段
backupType支持两个值:full(指示数据移动器做全量备份)与incremental(默认值,做增量备份)。
spec: description: BackupSpec defines the specification for a Velero backup. properties: backupType: description: BackupType indicates the type of the backup enum: - full - incremental type: string该字段已落地到 API 类型,见 pkg/apis/velero/v1/backup_types.go(BackupType BackupType)与同文件中的BackupTypeFull = "Full"、BackupTypeIncremental = "Incremental"常量(pkg/apis/velero/v1/backup_types.go)。
DataUpload CRD:新增parentSnapshot字段
parentSnapshot支持以下取值:
"":回退到auto;auto:数据移动器从 Unified Repository 找到同一卷最近的快照作为父快照;none:数据移动器不分配父快照,执行全量备份;- 具体 snapshotID:数据移动器用该 ID 查找父快照;找不到则回退到全量备份。
最后一个选项是为备份计划准备的,当前不会使用,可能在 Velero 支持精细保留策略时有用——目前 Velero 总是以最近备份作为父快照。当 Backup 的backupType为full时,数据移动器控制器把 DataUpload 的parentSnapshot设为none;为incremental时设为auto;""仅为向后兼容保留。
spec: description: DataUploadSpec is the specification for a DataUpload. properties: parentSnapshot: description: |- ParentSnapshot specifies the parent snapshot that current backup is based on. If its value is "" or "auto", the data mover finds the recent backup of the same volume as parent. If its value is "none", the data mover will do a full backup If its value is a specific snapshotID, the data mover finds the specific snapshot as parent. type: string对应的 API 字段可见 pkg/apis/velero/v1/pod_volume_backup_types.go 中的ParentSnapshot(当前位于 PodVolumeBackupSpec 中)。
DataDownload CRD
不需要修改。
插件数据移动器
本设计不会破坏任何插件数据移动器。VolumePolicy 的增强同样可用于插件数据移动器:用户可以通过 VolumePolicy 选择插件数据移动器,方式与选择 Velero 内置数据移动器相同。DataUpload/DataDownload 控制器的现有实现逻辑可参考 pkg/controller/data_upload_controller.go 与 pkg/controller/data_download_controller.go,其中处理DataMover类型与BackupType的细节可结合 pkg/util/datamover/datamover.go 阅读。
安装、升级与 CLI
- 安装:无需变更。
- 升级:无影响。CRD 新增字段均为可选字段且具有向后兼容的取值。
- CLI:备份类型参数加入 Velero CLI:
velero backup create --full velero schedule create --full未指定该参数时,默认执行增量备份。
小结
Velero Block Data Mover 设计在既有 VBDM 与 VGDP 之上,以"复用为主、增量改造"的方式为 CSI 快照数据移动引入块级备份/恢复能力。核心思路可以概括为四条主线:一是通过 Kubernetes CBT API(GetMetadataAllocated/GetMetadataDelta)获取已分配/变化块,并用 1MB 粒度的 CBT Bitmap 承载,实现全量与增量备份;二是通过 Unified Repo 接口的WriteAt、ParentObject、快照与元数据方法扩展,以及 Kopia 仓库的 Incremental Aware Object Extension,实现基于 BAT 克隆的增量写与零数据移动的删除;三是通过块上传器的 reader/writer 环形缓冲、direct IO 与WRITE_SAME零块处理,保证吞吐与写 IO 效率;四是通过velero-block/velero-fs数据移动器类型、Volume Policy 的dataMover参数、backupType/parentSnapshotCRD 字段与--fullCLI 参数,把选择权交给用户并保持向后兼容。
需要说明的是,当前设计明确聚焦 Kopia 仓库,且实现仅面向 Linux 节点;文件存储卷、Windows 节点与 PodVolume Backup 仍走文件系统级路径。随着 Kubernetes CBT API 在更多平台落地,这套架构为 Velero 在降低 TCO、提升备份恢复吞吐方面提供了清晰的演进路径。
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考