news 2026/9/16 19:58:40

Velero 对象图谱清单(Object Graph Manifest)设计解析:为 dry-run、并发备份恢复与图依赖恢复奠定基础

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Velero 对象图谱清单(Object Graph Manifest)设计解析:为 dry-run、并发备份恢复与图依赖恢复奠定基础

Velero 对象图谱清单(Object Graph Manifest)设计解析:为 dry-run、并发备份恢复与图依赖恢复奠定基础

【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero

导读

本文以 design/graph-manifest.md 设计提案为骨架,深入解析 Velero 提出的Object Graph Manifest(对象图谱清单)数据结构:一个随备份一起存放于对象存储、以最小字段描述备份内容全貌的manifest.json。读完本文,你将理解 Velero 现有备份/恢复在"内容感知"上的局限,掌握清单数据结构的 Go 类型设计、序列化与兼容性策略,并看到它如何为 dry-run、无重叠并行操作以及基于依赖图的复杂应用恢复铺平道路。

背景:为什么 Velero 需要一份"备份内容清单"

Velero 当前的工作方式是:备份时逐个处理对象,按 API Group 与命名空间排序;恢复时同样逐个处理对象,借助restoreResourcePriorities标志指定各 API Group 的对象恢复先后顺序。

这种线性处理模式对大多数应用足够,但对依赖关系呈图状而非严格线性的复杂应用存在明显挑战。文档以 Cluster API(CAPI)为例:CAPI 集群是一组复杂的 Kubernetes 对象,要求"根对象"先于"叶子对象"恢复——如果ClusterResourceSetBinding所引用的Cluster尚不存在,整个 CAPI 集群的恢复就会失败。

此外,Velero 缺少一种**在不真正执行操作的前提下可靠告知用户"哪些对象会受影响"**的机制。这会带来两个连锁问题:

  1. dry-run 困难:用户必须真实执行备份/恢复动作,才能知道会触碰哪些对象;
  2. 并发受限:当前无法判断某个 Kubernetes 对象是否同时被多个备份或恢复覆盖。一旦 Velero 走向更高并发,就可能导致不可靠、死锁乃至竞态条件。

目标与非目标

目标(Goals)

  • 引入一个定义备份内容的数据结构(Manifest);
  • 将 Manifest 数据与既有备份数据一并存入对象存储。

非目标(Non Goals)

本提案明确说明,以下能力是"被开启"而非"被定义"的,不会与数据结构的引入同期实现:

  • 在 Velero 现有并发之外实现更高并发;
  • 实现 dry-run 特性;
  • 实现新的恢复排序流程。

数据结构设计必须为上述场景留出空间,但它们不属于本提案的交付范围。

高层设计:用最小字段唯一标识一个 Kubernetes 对象

要在一个集群或备份内唯一标识 Kubernetes 对象,以下字段已经足够(见 design/graph-manifest.md):

  • API Group 与 Version(例如backup.velero.io/v1
  • Namespace
  • Name
  • Labels

这组标准覆盖了 Velero 绝大部分的包含/排除(inclusion/exclusion)逻辑。此外,以下附加字段可进一步解锁更多用例:

  • Owners:与该对象存在某种关系的其他 Kubernetes 对象,可能是严格依赖,也可能是软依赖;
  • Annotations:对象额外的元数据,方便其他程序消费;
  • UUID:Kubernetes 生成的唯一标识,用于定义 Owner 关系,为对象提供单一且不可变的主键。注意:UUID在恢复时不会被考虑,仅内部用于定义对象间链接。

为什么不直接解析备份 tarball?

上述信息其实都已存在于 Velero 备份 tarball 中,但提取成本高昂:必须完整下载并解压整个 tarball,再解析其中的 JSON 才能读到 labels、owners、annotations 和 UUID;其余信息则编码在 tarball 的目录结构里。可行但"重量级",耗时且可能消耗大量内存。

从仓库源码可以印证这种结构的实际形态。备份 tarball 内的对象文件路径由 pkg/archive/filesystem.go 的GetVersionedItemFilePath生成:

path := filepath.Join(rootDir, velerov1api.ResourcesDir, groupResource, versionPath, GetScopeDir(namespace), namespace, name+".json")

即形如<root>/resources/<groupResource>/<version>/<cluster 或 namespaces>/<namespace>/<name>.json(命名空间级对象)与<root>/resources/<groupResource>/<version>/cluster/<name>.json(集群级对象)。对应测试见 pkg/archive/filesystem_test.go。

因此,提案主张新增一种与备份 tarball 并排存放的 Manifest 结构,它只包含上述字段,可用来:

  • 对备份执行包含/排除逻辑;
  • 从备份中选择特定资源;
  • 对备份或恢复内容做集合运算,识别重叠资源。

清单将解锁的三类场景

  1. 备份/恢复的 dry-run:先构建并保存一份 Manifest,让用户看到"如果执行会选中什么",确认后再真正执行备份;恢复操作同理。
  2. 无重叠的高效并行:在执行备份/恢复前先构建或读取 Manifest,判断资源是否存在重叠——无重叠则并行执行,有重叠则串行执行。
  3. 面向非线性依赖的图式恢复:Kubernetes 集群中并非所有资源都能用严格线性方式定义,它们可能有多个 Owner。与其要求插件作者在BackupItemAction/RestoreItemAction里手工返回一长串 Owner 链,不如由 Velero 基于包含足够信息的 Manifest,自动构建"依赖先于被依赖者恢复"的离散列表,从而大幅降低插件作者的负担。

详细设计:Go 数据结构与接口

核心类型定义

Manifest 数据结构的 Go 类型设计如下(原文完整摘录自 design/graph-manifest.md):

// NamespacedItems maps a given namespace to all of its contained items. type NamespacedItems map[string]*Item // APIGroupNamespaces maps an API group/version to a map of namespaces and their items. type KindNamespaces map[string]NamespacedItems type Manifest struct { // Kinds holds the top level map of all resources in a manifest. Kinds KindNamespaces // Index is used to look up an individual item quickly based on UUID. // This enables fetching owners out of the maps more efficiently at the cost of memory space. Index map[string]*Item } // Item represents a Kubernetes resource within a backup based on it's selectable criteria. // It is not the whole Kubernetes resource as retrieved from the API server, but rather a collection of important fields needed for filtering. type Item struct { // Kubernetes API group which this Item belongs to. // Could be a core resource, or a CustomResourceDefinition. APIGroup string // Version of the APIGroup that the Item belongs to. APIVersion string // Kubernetes namespace which contains this item. // Empty string for cluster-level resource. Namespace string // Item's given name. Name string // Map of labels that the Item had at backup time. Labels map[string]string // Map of annotations that the Item had at Backup time. // Useful for plugins that may decide to process only Items with specific annotations. Annotations map[string]string // Owners is a list of UUIDs to other items that own or refer to this item. Owners []string // Manifest is a pointer to the Manifest in which this object is contained. // Useful for getting access to things like the Manifest.Index map. Manifest *Manifest }

结构要点:

  • Manifest.Kinds采用三级映射(Kind → Namespace → Items),天然对齐 Velero 按 API Group 与命名空间组织的处理模型;
  • Manifest.Index是以 UUID 为键的索引表,用于快速定位某个 Item,从而高效解析 Owner 关系——以内存换取查询效率;
  • Item刻意不保存完整的 Kubernetes 资源对象,只保存过滤所需的关键字段,这正是内存开销可控的根本原因;
  • Item.Owners存的是其他 Item 的 UUID 列表,配合Index即可在清单内完成图遍历。

便捷接口与哨兵错误

除数据类型外,提案还提供以下 Go 接口:

type Itermer interface { // Returns the Item as a string, following the current Velero backup version 1.1.0 tarball structure format. // <APIGroup>/<Namespace>/<APIVersion>/<name>.json String() string // Owners returns a slice of realized Items that own or refer to the current Item. // Useful for building out a full graph of Items to restore. // Will use the UUIDs in Item.Owners to look up the owner Items in the Manifest. Owners() []*Item // Kind returns the Kind of an object, which is a combination of the APIGroup and APIVersion. // Useful for verifying the needed CustomResourceDefinition exists before actually restoring this Item. Kind() *Item // Children returns a slice of all Items that refer to this item as an Owner. Children() []*Items } // This error type is being created in order to make reliable sentinel errors. // See https://dave.cheney.net/2019/06/10/constant-time for more details. type ManifestError string func (e ManifestError) Error() string { return string(e) } const ItemAlreadyExists = ManifestError("item already exists in manifest") type Manifester interface { // Set returns the entire list of resources as a set of strings (using Itemer.String). // This is useful for comparing two manifests and determining if they have any overlapping resources. // In the future, when implementing concurrent operations, this can be used as a sanity check to ensure resources aren't being backed up or restored by two operations at once. Set() sets.String // Adds an item to the appropriate APIGroup and Namespace within a Manifest // Returns (true, nil) if the Item is successfully added to the Manifest, // Returns (false, ItemAlreadyExists) if the Item is already in the Manifest. Add(*Item) (bool, error) }

接口设计的意图值得展开:

  • String()将 Item 序列化为符合备份 tarball 目录约定的字符串<APIGroup>/<Namespace>/<APIVersion>/<name>.json。这与上述 pkg/archive/filesystem.go 中的 tarball 路径规则相呼应,只是 Manifest 用更轻量的方式表达了同一份"位置信息";
  • Owners()/Children()借助Item.Owners(UUID 列表)与Manifest.Index在清单内正向/反向遍历依赖图,是构建"依赖优先恢复顺序"的直接入口;
  • Kind()返回 APIGroup + APIVersion 的组合,可用于在真正恢复某个 Item 前校验所需 CRD 是否存在;
  • Manifester.Set()把整份清单变成字符串集合,用于比较两份 Manifest 的重叠情况——这正是未来并发操作前的"健全性检查"(sanity check)原语;
  • Add()幂等地向 Manifest 写入 Item,重复写入时返回哨兵错误ItemAlreadyExists。文档特意将错误定义为ManifestError字符串类型,借鉴 Go 社区"常量时间错误"(constant-time sentinel error)实践,使调用方可以用errors.Is/==做可靠判断。

序列化:manifest.json 的存储形态

整个Manifest会被序列化到单个备份对应的manifest.json文件中,存放于对象存储内;文档同时指出该文件可考虑压缩以节省空间

从当前仓库的持久化层可以直观看到这种"备份目录下并列多个附属文件"的既有模式。pkg/persistence/object_store_layout.go 定义了备份目录下的各类产物 key,例如:

  • velero-backup.json(备份元数据)
  • <backup>.tar.gz(备份内容 tarball)
  • <backup>-logs.gz(日志)
  • <backup>-resource-list.json.gz(资源清单)
  • <backup>-podvolumebackups.json.gz<backup>-volumesnapshots.json.gz<backup>-itemoperations.json.gz

而 pkg/persistence/object_store.go 中的BackupInfoPutBackup方法(见 object_store.go)则把这些io.Reader逐个上传到对象存储。manifest.json正是要作为这一类新增附属文件加入上传流程,即文档"Implementation"一节所述:在persistence包中确保新文件"可上传且被允许(uploadable and allowed)"。

内存考量

由于Manifest只保存最小字段集合(而非完整对象),对绝大多数集群而言内存不应成为顾虑。文档在此留有一个 TODO:记录 API Group 名称、资源名称与 Kind 名称的已知字符长度限制,作为后续明确的上限依据。

安全与兼容性

安全(Security Considerations)

引入 Manifest不会扩大 Velero 的攻击面:清单中的字段本就存在于既有备份数据中;将manifest.json放在既有备份数据旁也不会改变访问模式(仍受同样的对象存储凭证与 BSL 配置约束)。

兼容性(Compatibility)

  • manifest.json的引入对应Velero 备份版本 1.2.0(即备份格式版本随之提升);
  • 该文件是**纯增量(additive)**的,不会干扰不支持Manifest的旧版 Velero 读取备份;
  • 长期看,manifest.json取代<backup>-resource-list.json.gz文件,但为兼容起见,两者会并存一段时期(正如 object_store_layout.go 所展示的resource-listkey 仍然存在);
  • 首次落地时,Velero 只需在备份 Item 的过程中顺带构建 Manifest,并在备份结束时序列化保存。任何依赖Manifest的逻辑变更都必须以独立的设计文档另行提出,并自带各自的兼容性考量。

实现路径:从类型到上传的落地点

文档明确了实现方向(design/graph-manifest.md):

  1. Manifest不会实现为 Kubernetes CustomResourceDefinition,而是 Velero 的内部构造(internal construct);
  2. 数据结构的实现量应尽量小:在独立的manifest中定义上述类型;
  3. 备份流程创建Manifest,并将其传递给backup包中的各个*Backupper,由这些方法逐个插入Item
  4. persistence包中新增逻辑,确保新的manifest.json可被上传且被允许。

从源码结构看,pkg/backup包中的 item_backupper.go、backed_up_items_map.go、backup.go 正是文档所描述的"各种 Backupper"所在的实现位置;它们逐项采集对象并维护去重映射,天然适合在采集过程中同步向 Manifest 插入 Item。持久化侧则以 pkg/persistence/object_store.go 的PutBackup为参照,为manifest.json增加对应的 key 生成与上传逻辑。

开放问题与后续展望

文档以三个开放问题收尾,为社区后续讨论留白:

  1. 何时正式移除对<backup>-resource-list.json.gz的兼容
  2. 除 Cluster API 外,还有哪些适合验证该特性的测试用 Kubernetes 资源/控制器?(Cluster API 是显而易见的选择,但显然不止于此)
  3. 既然 Manifest 不是 CRD,如何保留一份 Manifest 以便用户先执行 dry-run 再执行真实操作?一个思路是存放在 Velero 的临时目录中——但需要指出,这会让 Velero 自身变得更"有状态"(stateful)。

总结

Object Graph Manifest 是 Velero 在"备份内容可感知性"上的关键设计增量:它以一份极轻量的manifest.json随备份存入对象存储,用最小的字段集合完整描述备份内容与对象间依赖关系。它本身不直接交付 dry-run、并发或新恢复排序,但通过IndexOwnersSet()等精心设计的原语,为这些更高阶的能力提供了坚实的结构性基础——这也是本文将其称为 Velero 后续架构演进"地基"的原因。对这一提案感兴趣的读者,可继续阅读原始设计文档 design/graph-manifest.md,并结合 pkg/persistence/object_store_layout.go 与 pkg/archive/filesystem.go 理解其落地的存储与归档上下文。

【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:57:13

Synology NAS共享文件夹创建与权限配置、跨设备自动挂载实战指南

做运维这些年&#xff0c;我经手的Synology NAS少说也有几十台&#xff0c;每次帮朋友或客户配置共享文件夹&#xff0c;看起来就是个点鼠标的活&#xff0c;但真正踩过坑才知道&#xff0c;权限模型、访问协议、自动挂载这些环节&#xff0c;任何一个没处理好&#xff0c;后面…

作者头像 李华
网站建设 2026/9/16 19:56:54

基于卷积神经网络的垃圾识别分类系统实战:数据集到部署全流程

简介&#xff1a;基于深度学习卷积神经网络实现的垃圾识别分类系统&#xff0c;提供完整Python源码、近两千张标注图片数据集及训练好的模型文件。项目源自导师指导下的高分课程设计&#xff0c;流程完整&#xff0c;下载解压即可运行&#xff0c;适合作为深度学习、计算机视觉…

作者头像 李华