news 2026/9/16 10:08:46

Velero BackupItemAction v2 API 设计详解:异步操作、进度上报与 v1 兼容适配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Velero BackupItemAction v2 API 设计详解:异步操作、进度上报与 v1 兼容适配

Velero BackupItemAction v2 API 设计详解:异步操作、进度上报与 v1 兼容适配

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

本文深入剖析 Velero 开源项目中 BackupItemAction(BIA)v2 API 的完整设计(对应仓库中的设计文档)。BIA 是 Velero 备份插件体系中针对"单个被备份对象"执行自定义逻辑的核心扩展点;v2 版本在其上引入了异步长时操作能力:插件可以在Execute()返回后继续在后台执行操作,并由 Velero 周期性地查询进度、在超时时发起取消。读完本文,你将掌握 v2 接口的 proto/gRPC 定义、Go 接口签名、OperationProgress进度模型、BackupItemActionV2插件类型,以及让既有 v1 插件零改动平滑运行的适配器实现原理,并能对照仓库源码自行验证与扩展。

一、背景与动机:为什么需要 BIA v2

在 BIA v1 中,Execute()方法被设计为同步调用:插件对单个 Kubernetes 资源执行自定义逻辑(如改写对象、注入注解、备份关联资源),在方法返回时操作必须已经完成。然而真实场景中存在一类执行耗时可能超过单次 RPC 调用生命周期的插件操作,例如:

  • 调用外部 API 做数据一致性快照;
  • 需要长时间等待云厂商异步任务完成;
  • 操作本身是"发起任务 + 后台轮询"模式。

若继续沿用同步模型,Velero 只能阻塞等待,既拖慢备份进度,也容易因进程重启、超时中断而丢失操作状态。BIA v2 正是为 Item Action Progress Monitoring 功能而设计:允许Execute()返回一个operationID来"挂起"一个后台操作,Velero 随即继续处理下一个插件、下一个对象,随后再通过新增的Progress()/Cancel()方法与插件交互。

关键设计取舍在于:该能力是可选特性。不需要异步能力的插件只需返回空的operationID,并让新增方法成为 no-op,完全不影响原有行为。

二、目标与非目标

设计文档明确了边界:

  • 目标:允许 BIAExecute()可选地发起一个长时运行的操作,并上报该操作的进度状态。
  • 非目标:不让 Velero 控制长时操作何时开始——操作何时启动、如何调度完全由插件自身决定,Velero 只在操作进行中负责查询与取消。

这一边界保证了 v2 只是对现有同步模型的"增量扩展",而不是对插件执行语义的重新定义。

三、高层设计概览

根据 Plugin Versioning 的版本化策略,v2 设计包含四部分工作:

  1. 新建 BIAv2 插件.proto文件,定义新的 gRPC 接口(仓库中落地为 pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto)。
  2. plugin/clientmgmt/backupitemactionplugin/framework/backupitemaction下分别创建 v2 的 Go 文件(客户端管理端与插件框架端)。
  3. 新增一个插件类型(PluginKind)BackupItemActionV2,并修改 Velero 备份流程,使其引用 v2 插件而非 v1 插件。
  4. 创建一个 v1→v2 适配器,使所有存量 BIA v1 插件在备份执行时仍能以 v2 插件的形式被调用。

四、详细设计(一):proto 变更

v2 的BackupItemAction.proto整体沿袭 v1,仅做三处修改。以下代码与仓库中的实际 proto 文件一致:

4.1 ExecuteResponse 新增 operationID 字段

message ExecuteResponse { bytes item = 1; repeated generated.ResourceIdentifier additionalItems = 2; string operationID = 3; repeated generated.ResourceIdentifier postOperationItems = 4; }

operationID是本次变更的核心:插件在发起后台操作后,通过该字段返回一个全局唯一的操作标识,供后续Progress()/Cancel()引用。此外新增第 4 个字段postOperationItems(设计文档早期草案中写作itemsToUpdate,最终实现定名为postOperationItems),表示"在所有操作完成后需要被备份的相关资源",仅当operationID非空时才有意义。

4.2 BackupItemAction 服务新增两个 RPC

service BackupItemAction { rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse); rpc Execute(ExecuteRequest) returns (ExecuteResponse); rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse); rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty); }

4.3 新增请求/响应消息类型

message BackupItemActionProgressRequest { string plugin = 1; string operationID = 2; bytes backup = 3; } message BackupItemActionProgressResponse { generated.OperationProgress progress = 1; } message BackupItemActionCancelRequest { string plugin = 1; string operationID = 2; bytes backup = 3; }

三个请求消息统一携带plugin(插件名)、operationID(操作标识)和backup(序列化后的 Backup 对象,供插件在查询/取消时读取上下文);Cancel的响应使用google.protobuf.Empty,因为取消不需要返回值。

4.4 共享消息类型 OperationProgress

OperationProgress被设计为跨插件类型的共享消息,未来 v2 的 RestoreItemAction 与 VolumeSnapshotter 同样需要它,因此放在共享 proto(仓库中为 pkg/plugin/proto/Shared.proto):

message OperationProgress { bool completed = 1; string err = 2; int64 nCompleted = 3; int64 nTotal = 4; string operationUnits = 5; string description = 6; google.protobuf.Timestamp started = 7; google.protobuf.Timestamp updated = 8; }

各字段语义:

字段类型含义
completedbool操作是否已完成
errstring操作出错时的错误信息(空表示无错误)
nCompletedint64已完成的工作单元数
nTotalint64总工作单元数(与nCompleted配合表示进度)
operationUnitsstring工作单元的名称/单位描述(如 "items"、"bytes")
descriptionstring操作的文字描述,供 Velero 展示
startedTimestamp操作开始时间
updatedTimestamp操作状态最近一次更新时间

五、详细设计(二):Go 接口定义与新的 PluginKind

5.1 BackupItemAction v2 Go 接口

除了两个新 RPC,接口还增加了一个Name()方法。该方法仅由 Velero 内部使用,用于获取插件注册时使用的名称,不会通过 RPC 委托给插件进程——但它仍必须被实现,以完成接口定义。仓库中的完整接口见 pkg/plugin/velero/backupitemaction/v2/backup_item_action.go:

type BackupItemAction interface { // Name returns the name of this BIA. ...其内容不重要,不会被 RPC 调用, // Velero 插件基础设施将直接实现它,以返回插件注册时使用的名称。 Name() string // AppliesTo 返回该 action 应作用于哪些资源;零值 ResourceSelector 匹配所有资源。 AppliesTo() (velero.ResourceSelector, error) // Execute 允许插件对被备份对象执行任意逻辑(含在备份前修改对象)。 Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) // Progress 上报异步操作的进度。 Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) // Cancel 取消异步操作(如支持);若插件不支持取消,直接返回即可,无需返回错误。 Cancel(operationID string, backup *api.Backup) error }

5.2 Execute 返回值的五个组成部分

Execute()的返回签名由 v1 的三元组扩展为五元组:

  1. runtime.Unstructured:修改后的对象(若未修改,可返回原对象,gRPC 服务端会自动回退为原始 item,见下文服务端实现);
  2. []velero.ResourceIdentifier:需要立即备份的附加相关资源;
  3. stringoperationID,非空表示插件发起了一个异步操作;
  4. []velero.ResourceIdentifierpostOperationItems,需要在所有操作完成后备份的相关资源;
  5. error:执行错误。

关于第 4 个返回值,接口注释特别强调:它只在operationID非空时才会被 Velero 关注;只有当操作过程中会更新该资源的 Kubernetes 元数据(且这些元数据在恢复时必须存在)时,才应填入该列表。

5.3 异步操作与备份阶段(Finalize)的约束

接口注释明确了一条重要限制:Finalize 阶段的备份不支持异步操作。当备份 Phase 为FinalizingFinalizingPartiallyFailed时,插件不应返回operationID——因为备份已经越过"等待插件操作完成"的阶段,此时 Velero 只会为postOperationItems中返回的资源再次调用插件。因此插件在发起操作前应检查传入backup.Status.Phase

5.4 新增 PluginKind:BackupItemActionV2

设计文档规定新增BackupItemActionV2插件类型,并让备份流程改用该类型。仓库中该类型的注册可见于 pkg/plugin/clientmgmt/process/client_builder.go,其将 v2 插件名映射到biav2.NewBackupItemActionPlugin(...);而 pkg/plugin/clientmgmt/manager.go 则通过GetBackupItemActionsV2()GetBackupItemActionV2(name)统一获取 v2 action(PluginKindBackupItemActionV2注册表查询后包装为可重启的 v2 action)。

六、源码实现纵深:gRPC 服务端与客户端

6.1 服务端(插件进程侧)

v2 的 gRPC 服务端实现位于 pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go,每个 RPC 的执行流程都是"通过ServerMux按插件名取出实现 → 调用 → 序列化返回"。几个值得注意的实现细节:

  • Execute 的 nil 回退:若插件返回的updatedItem为 nil(表示未修改对象),服务端会把updatedItemJSON重置为req.Item,避免客户端因空对象反序列化失败:
var updatedItemJSON []byte if updatedItem == nil { updatedItemJSON = req.Item } else { updatedItemJSON, err = json.Marshal(updatedItem.UnstructuredContent()) ... }
  • Progress 的时间戳转换:Go 侧OperationProgressStarted/Updatedtime.Time,通过timestamppb.New(...)转为 proto 的google.protobuf.Timestamp
  • Name() 占位:服务端的Name()恒返回空字符串,因为该调用永远不应被委托到插件进程(注释明确说明这一设计)。
  • panic 防护:每个 RPC 都通过defer+common.HandlePanic(recover())将插件 panic 转为 gRPC 错误返回,防止插件崩溃拖垮整个 Velero 进程。

6.2 客户端(Velero 进程侧)

客户端实现在 pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go,其职责是将 Go 对象序列化为 proto 消息并转发到插件进程:

  • Execute:将 item 与 backup 分别json.Marshal后放入ExecuteRequest,返回时把AdditionalItemsPostOperationItems逐个还原为velero.ResourceIdentifier(按 Group/Resource/Namespace/Name 组装);
  • Progress:调用后把res.Progress的 8 个字段逐一映射回velero.OperationProgress,其中时间戳用AsTime()还原;
  • Cancel:成功后返回 nil。

6.3 可重启封装:RestartableBackupItemAction

restartable_backup_item_action.go 中的RestartableBackupItemAction是客户端管理端的核心封装:每次方法调用前先通过SharedPluginProcess.ResetIfNeeded()检查插件进程是否存活、必要时自动重启,再按{Kind: PluginKindBackupItemActionV2, Name}取回委托对象执行调用。其中Name()直接返回r.Key.Name——即注册名,这正是设计文档所说"该值不真正重要、但必须实现接口"的落地方式。相应单元测试见同目录下的restartable_backup_item_action_test.go

七、v1 兼容:适配器如何让老插件零改动运行

设计文档的兼容性目标——"任何既有 BackupItemAction 插件都能按预期工作"——由 restartable_backup_item_action.go 中的AdaptedV1RestartableBackupItemAction实现。其行为完全对应设计文档的描述:

方法适配器行为原因
Execute委托 v1 的Execute(item, backup),返回(updatedItem, additionalItems, "", nil, err)v1 插件没有异步概念,operationID恒为空、postOperationItems恒为 nil
Progress直接返回AsyncOperationsNotSupportedError()v1 插件永远不会返回 operationID,因此传入的任意 operationID 都是无效的
Cancel直接返回 nilv1 插件未实现取消逻辑,静默忽略即可
type AdaptedV1RestartableBackupItemAction struct { V1Restartable *biav1cli.RestartableBackupItemAction } func (r *AdaptedV1RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, []velero.ResourceIdentifier, error) { updatedItem, additionalItems, err := r.V1Restartable.Execute(item, backup) return updatedItem, additionalItems, "", nil, err } func (r *AdaptedV1RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { return velero.OperationProgress{}, biav2.AsyncOperationsNotSupportedError() } func (r *AdaptedV1RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { return nil }

AdaptedBackupItemActions()函数将两种插件类型统一注册进可重启机制:PluginKindBackupItemActionV2直接使用原生 v2 封装,PluginKindBackupItemAction(v1)则包装为适配器。这意味着备份流程只需面向统一的 v2 接口编程,不必关心插件底层是 v1 还是 v2。

八、插件作者实践要点

若要开发一个支持异步操作的 v2 BIA 插件,需注意以下要点(综合设计文档与源码注释):

  1. 发起操作:在Execute()中自行启动后台任务(goroutine 或外部系统任务),返回唯一的operationID;若不支持异步,返回空字符串即可。
  2. 检查备份阶段:发起操作前检查backup.Status.Phase,若为Finalizing/FinalizingPartiallyFailed则不要返回operationID
  3. 上报进度:实现Progress(operationID, backup),返回包含CompletedErrNCompletedNTotalOperationUnitsDescriptionStartedUpdatedOperationProgress;Velero 会周期性轮询直到Completed == true
  4. 实现取消:实现Cancel(operationID, backup)尽量中止操作;若无法取消,直接返回 nil(不视为错误)。
  5. 必须实现Name():返回值不会被 Velero 用于定位插件(注册名由基础设施直接提供),但必须实现以满足接口。

九、兼容性、实现时间线与总结

  • 兼容性:v1 适配器保证了任何存量 BackupItemAction 插件均可继续工作,无需修改、无需重新编译,属于向后兼容的增量 API 演进。
  • 实现时间线:设计文档明确本变更在Velero 1.11 开发周期内实现;当前仓库中的 proto、框架代码、可重启封装与适配器均已落地并配套测试(见restartable_backup_item_action_test.gobackup_item_action_test.go),印证了设计的完整闭环。

总结而言,BIA v2 是 Velero 插件体系的一次"增量式"演进:它用最少的接口变更(一个返回字段 + 两个 RPC + 一个内部方法),为插件作者打开了异步长时操作与进度上报的空间,同时通过适配器层保障了海量存量 v1 插件的平滑过渡。对于需要在备份流程中执行耗时任务的插件场景(如等待云侧快照完成、调用外部系统协调数据一致性),BIA v2 提供的Progress/Cancel/OperationProgress协议即是标准答案。

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

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

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

Discord和YouTube卡顿的真相:DNS、MTU与接入路径优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 10:07:26

STN1110+R7KA8D2KFLCAC:多协议OBD硬件协同设计实战

1. 为什么“终极多协议OBD解决方案”不是营销话术,而是工程现实中的刚性需求你拆过一辆2005年款丰田凯美瑞的OBD接口吗?用同一根线缆插进2022年款比亚迪汉EV的诊断口,再换到2018年款宝马X3——三台车,三个响应:第一台返…

作者头像 李华
网站建设 2026/9/16 10:07:07

MMC5983MA磁传感器例程实战:寄存器、校准与航向角误差排除

简介:QMC5983地磁传感器C语言例程包,面向使用模拟IIC接口开发无人机、机器人导航及姿态控制系统的嵌入式工程师。资源以单个C文件呈现,压缩包仅3KB,包含完整的传感器驱动代码,涵盖初始化、IIC读写、寄存器配置、数据解…

作者头像 李华
网站建设 2026/9/16 10:06:28

STM32 RTC可靠性设计:晶振、后备电源与校准全解析

1. 这不是普通闹钟:一个能“记住时间”的STM32项目到底在解决什么问题你有没有遇到过这样的场景:凌晨三点,手机闹钟没响,因为昨晚睡前忘了关勿扰模式;或者出差回来发现家里温湿度计显示的还是出发那天的数据&#xff0…

作者头像 李华
网站建设 2026/9/16 10:05:38

mcp-builder 的 Inspector 连不上?TaoToken 这样改 Claude Code 通道再查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华