Velero 卷快照插件备份/恢复进度报告机制设计解析(VolumePluginBackup/VolumePluginRestore CR)
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
导读
本文基于 Velero 仓库中的设计文档 plugin-backup-and-restore-progress-design.md,系统梳理 Velero 为卷快照(volume snapshotter)插件制定的备份/恢复进度上报机制:通过新增VolumePluginBackup与VolumePluginRestore两类自定义资源(CR),让不同云厂商插件以统一、可选的方式向用户暴露备份与恢复操作的真实进度。读完本文,你将掌握该机制的两套候选设计方案(Approach 1 将现有volume.Snapshot结构体升级为 CR;Approach 2 由插件自主创建 CR)、CR 的完整字段语义、标签定位规则、持久化与跨集群同步链路,以及向后兼容、删除策略、备选方案和安全边界等实现细节。
问题背景:卷快照操作为何"不可见"
Velero 此前已经为 restic/kopia 这类文件级备份/恢复提供了进度可见性:用户能够了解操作的大致规模(如需要处理的字节总量)、操作处于进行中还是已挂起(hung)等状态。然而,由卷快照插件(volume snapshotter)处理的备份/恢复操作,用户很难获知进度,主要痛点包括:
- 各插件进度信息不统一:每个插件可能有各自的进度查询方式,但彼此之间没有统一约定;
- 恢复时拿不到备份信息:即使插件能提供备份进度信息,这些信息在目标集群执行恢复操作时也无法传递过去;
- 必须可选用:部分插件可能根本没有提供进度信息的能力,因此该机制不能是强制性的。
设计文档因此提出一套插件需遵循的进度上报约定,目标是:为卷快照插件执行的备份/恢复操作提供统一的可见性(Provide uniform way of visibility into backup/restore operations performed by volume snapshotters)。注意,插件本身的具体实现不在本设计的目标范围内(Non Goals)。
核心设计思路:把进度写入专用 CR
高层设计非常简洁、与 Velero 既有的PodVolumeBackup/PodVolumeRestore模式一脉相承:
- 备份进度:由卷快照插件在"与该备份操作对应的"
VolumePluginBackupCR 中持续更新进度; - 恢复进度:由卷快照插件在"与该恢复操作对应的"
VolumePluginRestoreCR 中持续更新进度。
进度信息以字节数为单位表达:Progress字段由VolumeOperationProgress结构体承载,包含TotalBytes(卷/快照总字节数)与BytesDone(已完成字节数)两个字段。用户通过读取 CR 即可知晓当前备份/恢复的完成度。
Approach 1:将 volume.Snapshot 升级为 CR
方案一的核心判断是:现有volume包中的SnapshotGo 结构体已经包含了卷快照备份操作的大部分细节,但它有两个缺陷——该结构体在常规时间间隔内不会被同步到其他集群,仅在恢复操作期间才会被同步,且 Velero CLI 只展示其中的部分内容。因此方案一的做法是:为Snapshot结构体增加进度跟踪相关字段并将其转换为 CR,彻底移除volume.Snapshot结构体;备份目标中保存的不再是 Go 结构体,而是 CR,并通过目标集群中的backupSyncController同步到集群。
仓库中当前实现的Snapshot结构体(见 internal/volume/native_snapshot.go)正是设计文档描述的演进起点:SnapshotSpec含BackupName、BackupUID、Location、PersistentVolumeName、ProviderVolumeID、VolumeType、VolumeAZ、VolumeIOPS等字段;SnapshotStatus含ProviderSnapshotID与Phase(New/Completed/Failed三种生命周期阶段,对应SnapshotPhase常量)。
VolumePluginBackup CR 的 Spec
设计文档在volume.SnapshotSpec的基础上仅新增一个字段ProviderName,即得到VolumePluginBackupSpec:
type VolumePluginBackupSpec struct { // BackupName is the name of the Velero backup this snapshot // is associated with. BackupName string `json:"backupName"` // BackupUID is the UID of the Velero backup this snapshot // is associated with. BackupUID string `json:"backupUID"` // Location is the name of the VolumeSnapshotLocation where this snapshot is stored. Location string `json:"location"` // PersistentVolumeName is the Kubernetes name for the volume. PersistentVolumeName string `json:"persistentVolumeName"` // ProviderVolumeID is the provider's ID for the volume. ProviderVolumeID string `json:"providerVolumeID"` // Provider is the Provider field given in VolumeSnapshotLocation Provider string `json:"provider"` // VolumeType is the type of the disk/volume in the cloud provider // API. VolumeType string `json:"volumeType"` // VolumeAZ is the where the volume is provisioned // in the cloud provider. VolumeAZ string `json:"volumeAZ,omitempty"` // VolumeIOPS is the optional value of provisioned IOPS for the // disk/volume in the cloud provider API. VolumeIOPS *int64 `json:"volumeIOPS,omitempty"` }字段语义要点:BackupName/BackupUID关联本次快照所属的 Velero 备份;Location指向存储该快照的VolumeSnapshotLocation;Provider即VolumeSnapshotLocation中给定的 Provider;VolumeType/VolumeAZ/VolumeIOPS描述云厂商侧磁盘/卷的类型、可用区与可选 IOPS 配置。
VolumePluginBackup CR 的 Status
除前两个字段外,volume.SnapshotStatus中新增若干字段后形成VolumePluginBackupStatus,进度字段即落在此处:
type VolumePluginBackupStatus struct { // ProviderSnapshotID is the ID of the snapshot taken in the cloud // provider API of this volume. ProviderSnapshotID string `json:"providerSnapshotID,omitempty"` // Phase is the current state of the VolumeSnapshot. Phase SnapshotPhase `json:"phase,omitempty"` // PluginSpecific are a map of key-value pairs that plugin want to provide // to user to identify plugin properties related to this backup // +optional PluginSpecific map[string]string `json:"pluginSpecific,omitempty"` // Message is a message about the volume plugin's backup's status. // +optional Message string `json:"message,omitempty"` // StartTimestamp records the time a backup was started. // Separate from CreationTimestamp, since that value changes // on restores. // The server's time is used for StartTimestamps // +optional // +nullable StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` // CompletionTimestamp records the time a backup was completed. // Completion time is recorded even on failed backups. // Completion time is recorded before uploading the backup object. // The server's time is used for CompletionTimestamps // +optional // +nullable CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"` // Progress holds the total number of bytes of the volume and the current // number of backed up bytes. This can be used to display progress information // about the backup operation. // +optional Progress VolumeOperationProgress `json:"progress,omitempty"` } type VolumeOperationProgress struct { TotalBytes int64 BytesDone int64 } type VolumePluginBackup struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ObjectMeta `json:"metadata,omitempty"` // +optional Spec VolumePluginBackupSpec `json:"spec,omitempty"` // +optional Status VolumePluginBackupStatus `json:"status,omitempty"` }设计细节值得注意:
StartTimestamp与CreationTimestamp分离:设计文档明确说明,StartTimestamp单独记录备份开始时间,因为CreationTimestamp的值在恢复时会变化;CompletionTimestamp在失败时也会记录,且在备份对象上传之前记录;PluginSpecific是一组 key-value 映射,允许插件向用户暴露与本备份相关的插件专有属性;Phase沿用SnapshotPhase类型(New/Completed/Failed),与当前 internal/volume/native_snapshot.go 中定义的快照生命周期阶段保持一致。
CR 创建时机与标签定位
对于每个卷的备份操作,Velero 在调用卷快照插件的CreateSnapshotAPI之前创建VolumePluginBackupCR。为便于定位"某次备份的某个卷"对应的 CR,设计文档规定 Velero 为 CR 添加以下标签:
velero.io/backup-name:值为备份名称;velero.io/pv-name:值为正在执行备份的卷名称。
由于备份名称唯一,不会出现重复识别 CR 的问题。所有标签值都必须经过GetValidName函数处理(仓库实现见 pkg/label/label.go,其作用是把输入字符串转换为符合 RFC 1035 DNS Label 规范的合法 Kubernetes 标签值)。仓库中已定义对应的标签常量velerov1api.BackupNameLabel = "velero.io/backup-name"(见 pkg/apis/velero/v1/labels_annotations.go),GetValidName也已用于备份、恢复选择器构造(见 pkg/label/label.go)。
插件侧更新进度
若插件支持展示自身操作进度,它需要:
- 利用
CreateSnapshot调用中传入的tags找到与该备份操作对应的VolumePluginBackupCR; - 定期更新该 CR 的进度信息(即
Status.Progress的TotalBytes/BytesDone)。
持久化、同步与删除
- 持久化:
takePVSnapshot(仓库中的实际入口见 pkg/backup/item_backupper.go)从CreateSnapshot返回后,原本把volume.Snapshot加入backupRequest;新设计中改为加入 CR,并在persistBackup阶段随备份一起持久化到备份存储。 - 跨集群同步:恢复集群上的
backupSyncController检查备份存储中是否有需要同步的VolumePluginBackupCR,并按需同步到集群。这解决了引言中"恢复时拿不到备份信息"的核心痛点。 - 删除:
VolumePluginBackup的生命周期与备份存储中的数据一致——当备份被手动删除或因过期被删除时,该 CR 也可随之删除。backupDeletionController的processRequest会在调用卷快照插件的DeleteSnapshot之前执行对VolumePluginBackup的删除。
向后兼容策略
当前volume.Snapshot以<backupname>-volumesnapshots.json.gz文件的形式保存在备份存储中。为了让VolumePluginBackupCR 替代volume.Snapshot时保持兼容,CR 仍以同一文件名(<backupname>-volumesnapshots.json.gz)备份。设计文档考虑了恢复侧 Velero 版本与 json.gz 文件格式组合的四种情况:
| 恢复侧 Velero 版本 | 备份存储中 json.gz 格式 | 兼容性处理 |
|---|---|---|
| 旧版 | 旧格式 | 正常(第一类) |
| 旧版 | 新格式 | GetBackupVolumeSnapshots解码时应只填充旧版本所需字段,保证可用 |
| 新版 | 旧格式 | 解码后metadata.name为空;GetBackupVolumeSnapshots解码旧格式为 CR 可行,需改为返回[]VolumePluginBackupSpec,调用方同步调整 |
| 新版 | 新格式 | 正常(最后一类) |
若第二类场景在实现中解码失败,则需将该 CR 备份到不同的文件;新版本代码应先检查旧文件是否存在——存在则走旧逻辑,否则检查新文件并走新逻辑。
backupSyncController在恢复集群上从备份存储获取<backupname>-volumesnapshots.json.gz对象并解码为内存中的VolumePluginBackupCR:若metadata.name已填充则创建 CR,否则不创建(文档同时提到也可考虑在集群上创建该 CR)。
VolumePluginRestore CR
对应地,恢复侧设计了VolumePluginRestoreCR,完整定义如下:
// VolumePluginRestoreSpec is the specification for a VolumePluginRestore CR. type VolumePluginRestoreSpec struct { // SnapshotID is the identifier for the snapshot of the volume. // This will be used to relate with output in 'velero describe backup' SnapshotID string `json:"snapshotID"` // BackupName is the name of the Velero backup from which PV will be // created. BackupName string `json:"backupName"` // Provider is the Provider field given in VolumeSnapshotLocation Provider string `json:"provider"` // VolumeType is the type of the disk/volume in the cloud provider // API. VolumeType string `json:"volumeType"` // VolumeAZ is the where the volume is provisioned // in the cloud provider. VolumeAZ string `json:"volumeAZ,omitempty"` } // VolumePluginRestoreStatus is the current status of a VolumePluginRestore CR. type VolumePluginRestoreStatus struct { // Phase is the current state of the VolumePluginRestore. Phase string `json:"phase"` // VolumeID is the PV name to which restore done VolumeID string `json:"volumeID"` // Message is a message about the volume plugin's restore's status. // +optional Message string `json:"message,omitempty"` // StartTimestamp records the time a restore was started. // Separate from CreationTimestamp, since that value changes // on restores. // The server's time is used for StartTimestamps // +optional // +nullable StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` // CompletionTimestamp records the time a restore was completed. // Completion time is recorded even on failed restores. // The server's time is used for CompletionTimestamps // +optional // +nullable CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"` // Progress holds the total number of bytes of the snapshot and the current // number of restored bytes. This can be used to display progress information // about the restore operation. // +optional Progress VolumeOperationProgress `json:"progress,omitempty"` // PluginSpecific are a map of key-value pairs that plugin want to provide // to user to identify plugin properties related to this restore // +optional PluginSpecific map[string]string `json:"pluginSpecific,omitempty"` } type VolumePluginRestore struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ObjectMeta `json:"metadata,omitempty"` // +optional Spec VolumePluginRestoreSpec `json:"spec,omitempty"` // +optional Status VolumePluginRestoreStatus `json:"status,omitempty"` }恢复侧的定位标签与备份侧略有不同,共三个:
velero.io/backup-name:值为备份名称;velero.io/snapshot-id:值为需要恢复的快照 ID;velero.io/provider:值为VolumeSnapshotLocation中的Provider。
同样,标签值需经GetValidName规范化。插件通过CreateVolumeFromSnapshotAPI 收到的snapshotID参数及其 Provider 名称即可唯一定位到对应的VolumePluginRestoreCR,若支持进度展示则定期更新其恢复进度。
生命周期方面:Velero 在处理 Restore CR 删除时,会一并删除对应的VolumePluginRestoreCR。VolumePluginRestoreStatus中的VolumeID记录恢复完成后的 PV 名称,SnapshotID则用于与velero describe backup输出建立关联。
Approach 2:插件自主创建 CR
方案二与方案一仅对备份侧不同,恢复侧设计一致。其区别在于 CR 的创建主体与字段设计:
// VolumePluginBackupSpec is the specification for a VolumePluginBackup CR. type VolumePluginBackupSpec struct { // Volume is the PV name to be backed up. Volume string `json:"volume"` // Backup name Backup string `json:"backup"` // Provider is the Provider field given in VolumeSnapshotLocation Provider string `json:"provider"` } // VolumePluginBackupStatus is the current status of a VolumePluginBackup CR. type VolumePluginBackupStatus struct { // Phase is the current state of the VolumePluginBackup. Phase string `json:"phase"` // SnapshotID is the identifier for the snapshot of the volume. // This will be used to relate with output in 'velero describe backup' SnapshotID string `json:"snapshotID"` // Message is a message about the volume plugin's backup's status. // +optional Message string `json:"message,omitempty"` // StartTimestamp records the time a backup was started. // Separate from CreationTimestamp, since that value changes // on restores. // The server's time is used for StartTimestamps // +optional // +nullable StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` // CompletionTimestamp records the time a backup was completed. // Completion time is recorded even on failed backups. // Completion time is recorded before uploading the backup object. // The server's time is used for CompletionTimestamps // +optional // +nullable CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"` // PluginSpecific are a map of key-value pairs that plugin want to provide // to user to identify plugin properties related to this backup // +optional PluginSpecific map[string]string `json:"pluginSpecific,omitempty"` // Progress holds the total number of bytes of the volume and the current // number of backed up bytes. This can be used to display progress information // about the backup operation. // +optional Progress VolumeOperationProgress `json:"progress,omitempty"` } type VolumeOperationProgress struct { TotalBytes int64 BytesDone int64 } type VolumePluginBackup struct { metav1.TypeMeta `json:",inline"` // +optional metav1.ObjectMeta `json:"metadata,omitempty"` // +optional Spec VolumePluginBackupSpec `json:"spec,omitempty"` // +optional Status VolumePluginBackupStatus `json:"status,omitempty"` }与方案一的差异点:
- Spec 更精简:只保留
Volume(待备份 PV 名)、Backup(备份名)、Provider三个字段;而SnapshotID从 Spec 移到了 Status; - CR 由插件创建:每个卷备份操作由卷快照插件在 Velero 命名空间中创建
VolumePluginBackupCR,并持续更新进度及卷名、备份名、SnapshotID 等详情; - 标签约定:插件需添加
velero.io/backup-name与velero.io/volume-name两个标签(注意与方案一的velero.io/pv-name不同),同样需经GetValidName规范化; - CR 命名惯例:虽然对 CR 名称没有强制限制,但作为通用实践,插件可用
CreateSnapshot的返回值作为 CR 名称。
其余机制与方案一一致:takePVSnapshot从CreateSnapshot返回后,若该卷对应的VolumePluginBackupCR 存在,Velero 将其加入backupRequest并在persistBackup时持久化;backupSyncController负责跨集群同步;backupDeletionController.processRequest在调用DeleteSnapshot前删除 CR。此外,方案二提出了另一种删除途径:CR 的删除可以委托给插件,插件利用DeleteSnapshot请求中传入的snapshotID自行删除VolumePluginBackup。
core Velero 客户端/服务端所需变更
设计文档列出了核心组件必须完成的改动清单:
- 安装时创建
VolumePluginBackup/VolumePluginRestore的 CRD; - 备份操作接近尾声时持久化
VolumePluginBackupCR; - 备份同步时,同步与该备份相关的
VolumePluginBackupCR(对应backupSyncController职责); - 调用卷快照插件的
DeleteSnapshot时删除VolumePluginBackup; - 处理 Restore CR 删除时删除
VolumePluginRestore; - 若采用方案一,还需:
- 将
volume.Snapshot结构体转换为 CR 及配套改动; - 在调用卷快照插件 API 之前创建
VolumePluginBackup/VolumePluginRestoreCR; - 修改
GetBackupVolumeSnapshots及其调用方,使其返回类型从[]volume.Snapshot变为[]VolumePluginBackupSpec。
- 将
从当前仓库源码可以印证相关调用链的真实存在:GetBackupVolumeSnapshots在恢复控制器中被用于获取备份的卷快照列表(见 pkg/controller/restore_controller.go),在备份删除控制器中被用于清理快照(见 pkg/controller/backup_deletion_controller.go),且原生快照列表在备份时以 gzip 压缩的 JSON 形式编码(见 pkg/controller/backup_controller.go)——这些正是设计文档所述改动的落点。
Velero CLI 所需变更
velero describeCLI 将从 API server 拉取相关 CR,并在输出中展示其内容,例如备份名称、PV 名称(若因标签长度限制发生变更则以 CR 内容为准)、PV 快照大小(即Progress中的字节信息)等。
API 升级与版本兼容
- 多版本支持:CR 升级时,Velero 在旧 API 版本被弃用之前仍可继续支持,以便识别需要持久化到备份存储的 CR,同时优先使用最新的受支持 API 版本;
- 同版本新增字段无风险:若在不改变 API 版本的前提下新增字段,不会引发问题——因为这类资源只用于提供信息,不存在对这些资源的调谐(reconciliation)逻辑;
- 新插件配旧版 Velero:支持此类 CR 的插件在 CRD 未安装时应优雅处理,即能够消化创建/更新 CR 过程中产生的错误,不能因此中断备份/恢复主流程。
已知限制
非 K8s 原生的插件无法实现该机制——因为它们无法创建 CR。设计文档对此有清醒的认知,并在备选方案一节讨论了是否值得为此付出更多工程代价。
备选方案对比与取舍
设计文档明确评估了四个备选方案,并给出了最终取舍理由:
- 向
VolumeSnapshotter接口新增Progress方法:由 Velero server 定期轮询该方法并代表插件更新VolumePluginBackupCR。这可以规避"插件必须 K8s 原生"的限制,但改动量大且需要额外的向后兼容机制。鉴于卷插件绝大多数都是 K8s 原生的,接受当前限制是合理的; - 直接更新 Backup CR 的 status:插件可直接更新 Backup CR 的状态,但这样会偏离 Velero 现有"用独立 CR 观察操作进度"(如
PodVolumeBackup/PodVolumeRestore)的一贯做法; - 用命名约束代替标签:要求
VolumePluginBackupCR 的名称与CreateSnapshot返回值一致。隐患在于:若卷快照插件在返回快照 ID 给 Velero 之前崩溃,则无法通过名称定位 CR; - 将 CR 备份到不同的对象:若把
VolumePluginBackupCR 备份到#backup-volumesnapshots.json.gz之外的其他对象,恢复控制器就需要实现"回退模式"(先检查新对象,不存在则走旧流程),而这种模式易出错。因此设计最终决定将 CR 备份到与volume.Snapshot相同的位置,即<backupname>-volumesnapshots.json.gz。
安全考量
当前 Velero 的所有组件都运行在同一个veleroservice account 之下,因此所有插件拥有宽泛的权限,包含修改由其他插件创建的 CR 的能力。这意味着在设计 CR 权限模型时需要考虑插件间的信任边界——这是该机制落地时需要在 RBAC 层面额外关注的安全点。
小结
VolumePluginBackup/VolumePluginRestoreCR 设计为 Velero 卷快照插件的进度可见性提供了清晰的统一契约:以字节级进度(TotalBytes/BytesDone)为核心,配合Phase、Message、StartTimestamp/CompletionTimestamp、PluginSpecific等状态字段,借助标签定位、backupSyncController跨集群同步、备份存储持久化与删除控制器联动,构建了一条从插件到用户 CLI 的完整进度信息链路。虽然该设计以"非 K8s 原生插件无法实现"为已知代价,但两套候选方案与详尽的兼容性、备选方案分析,为后续实现者提供了可以直接落地的技术路线图。
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考