ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Velero 恢复依赖等待机制深度解析:`RestoreItemAction` 的 `AdditionalItems` 就绪等待设计

Velero 恢复依赖等待机制深度解析:`RestoreItemAction` 的 `AdditionalItems` 就绪等待设计 Velero 恢复依赖等待机制深度解析RestoreItemAction的AdditionalItems就绪等待设计【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero导读本文围绕 Velero 恢复Restore流程中的一个经典竞态问题展开当RestoreItemAction插件通过AdditionalItems声明当前资源依赖的其他资源时Velero 默认不会等待这些额外资源真正就绪就立刻恢复当前资源从而可能导致恢复失败。文中详细解析了该设计文档design/Implemented/wait-for-additional-items.md提出的等待额外资源就绪机制包括RestoreItemAction插件接口新增的AreAdditionalItemsReady方法、RestoreItemActionExecuteOutput新增的WaitForAdditionalItems与AdditionalItemsReadyTimeout字段、WithItemsWait()辅助函数、超时控制与向后兼容策略。读者读完后将掌握该机制的设计动机、接口契约、调用链与插件实现要点并能在自研恢复插件中正确使用这一能力。背景AdditionalItems恢复顺序带来的竞态问题在 Velero 的恢复流程中RestoreItemAction插件的Execute()函数除了可以修改被恢复对象本身之外还可以通过返回值中的AdditionalItems字段声明一批当前资源恢复前必须先恢复的关联资源ResourceIdentifier列表包含 GroupResource、Namespace 与 Name。Velero 会先恢复这些额外资源再恢复当前资源。但问题在于已经执行了恢复与已经处于可用状态是两个概念。Velero 在触发额外资源的恢复操作后并不会等待其真正就绪ready而是立即继续恢复当前资源。此时如果当前资源与额外资源之间存在强依赖关系例如当前资源引用了额外资源中的字段、需要额外资源的控制器先完成初始化等当前资源的恢复就可能因为额外资源尚未可用而失败。这一竞态在 Kubernetes 生态中非常典型某些自定义资源CRD只有在底层的 CRD 定义被 API Server 完全接纳Established之后才能创建某些 Secret、ServiceAccount、存储类StorageClass等资源在创建后还需要控制器完成后续处理才能真正被使用。设计文档明确指出了这一点Because Velero does not wait after restoring additional items to restore the current item, in some cases the current item restore will fail if the additional items are not yet ready.因此Velero 需要与插件协同实现等到额外资源就绪后再恢复当前资源的能力。设计目标让 Velero 能够确保恢复插件Execute()返回的AdditionalItems在当前资源被恢复之前已经就绪。扩展RestoreItemAction插件接口允许插件自行判定额外资源何时算作就绪——因为就绪的定义高度依赖具体资源类型只有资源自身的插件才具备这种领域知识。高层设计在恢复当前资源前等待额外资源设计文档给出的高层思路非常清晰在每次RestoreItemAction.Execute()调用返回、并且其声明的AdditionalItems完成恢复之后Velero 需要对这些额外资源执行等待就绪逻辑然后再恢复当前资源。为了实现这一点需要对RestoreItemActionExecuteOutput结构体进行扩展让返回了额外资源的插件能够决定是否等待、以及等待多久。整个机制可以拆解为三部分restoreItem恢复流程中的等待逻辑itemsAvailableRestoreItemAction插件接口新增AreAdditionalItemsReady方法RestoreItemActionExecuteOutput新增两个可选字段与一个链式辅助函数WithItemsWait()。详细设计一restoreItem中的等待逻辑itemsAvailable调用时机在恢复额外资源之后、恢复当前资源之前在恢复单个资源时restoreItem会依次执行所有匹配的RestoreItemAction。当某个插件的Execute()返回后RestoreItemActionExecuteOutput中携带了必须先行恢复的AdditionalItems切片。此时 Velero 会遍历这些额外资源逐一执行恢复对应设计文档中所指的 restore.go 中循环恢复额外资源的代码段完成之后仍然持有两份关键引用额外资源的标识GroupResource namespaced name以及要求这些额外资源的插件实例。就在这个时点——额外资源恢复完毕、但当前资源尚未恢复——插入等待就绪逻辑是最合适的。当前仓库中这段调用位于 pkg/restore/restore.govar filteredAdditionalItems []velero.ResourceIdentifier for _, additionalItem : range executeOutput.AdditionalItems { // ... 定位备份归档中的额外资源文件、执行恢复递归调用 restoreItem... w, e, additionalItemExists : ctx.restoreItem(additionalObj, additionalItem.GroupResource, additionalItemNamespace, mustIncludeAdditionalItems) if additionalItemExists { filteredAdditionalItems append(filteredAdditionalItems, additionalItem) } warnings.Merge(w) errs.Merge(e) } executeOutput.AdditionalItems filteredAdditionalItems available, err : ctx.itemsAvailable(action, executeOutput) if err ! nil { errs.Add(namespace, errors.Wrapf(err, error verifying additional items are ready to use)) } else if !available { errs.Add(namespace, fmt.Errorf(additional items for %s are not ready to use, resourceID)) }从源码可以确认两点实现细节其一恢复失败的额外资源会从AdditionalItems切片中被过滤掉只有additionalItemExists为 true 的资源才会被保留到filteredAdditionalItems随后再传给就绪等待逻辑其二itemsAvailable返回的错误或未就绪状态都会作为恢复错误errs被记录其处理方式与额外资源本身恢复失败时的错误处理保持一致。itemsAvailable的具体实现设计文档提出当RestoreItemActionExecuteOutput.WaitForAdditionalItems为true时调用一个与既有crdAvailable等待 CRD 就绪的既有实现同样定义在 restore.go 中类似的函数itemsAvailable。当前仓库中的实现位于 pkg/restore/restore.go// itemsAvailable waits for the passed-in additional items to be available for use before letting the restore continue. func (ctx *restoreContext) itemsAvailable(action framework.RestoreItemResolvedActionV2, restoreItemOut *velero.RestoreItemActionExecuteOutput) (bool, error) { // if RestoreItemAction doesnt define set WaitForAdditionalItems, then return true if !restoreItemOut.WaitForAdditionalItems { return true, nil } var available bool timeout : ctx.resourceTimeout if restoreItemOut.AdditionalItemsReadyTimeout ! 0 { timeout restoreItemOut.AdditionalItemsReadyTimeout } err : wait.PollUntilContextTimeout(go_context.Background(), time.Second, timeout, true, func(go_context.Context) (bool, error) { var err error available, err action.AreAdditionalItemsReady(restoreItemOut.AdditionalItems, ctx.restore) if err ! nil { return true, err } if !available { ctx.log.Debug(AdditionalItems not yet ready for use) } // If the AdditionalItems are not available, keep polling (false, nil) // If the AdditionalItems are available, break the poll and return back to caller (true, nil) return available, nil }) if wait.Interrupted(err) { ctx.log.Debug(timeout reached waiting for AdditionalItems to be ready) } return available, err }该实现与设计文档的对应关系可以逐条印证不等待的默认路径若插件未设置WaitForAdditionalItemsitemsAvailable直接返回(true, nil)保持 Velero 原有行为。超时来源等待的超时默认取自恢复上下文的resourceTimeout服务器端--resource-timeout参数默认 10 分钟定义见 pkg/cmd/server/config/config.go若插件在输出中显式设置了AdditionalItemsReadyTimeout非零则用插件值覆盖服务器级默认值。轮询策略以 1 秒为间隔调用插件的AreAdditionalItemsReady直到返回true或到达超时返回错误则立即终止轮询并向上传播。超时后的行为超时wait.Interrupted不会当作错误返回而是返回(false, nil)。在调用方restoreItem中available false会记录一条错误additional items for ... are not ready to use随后流程继续——也就是说等待超时不会无限阻塞恢复Velero 会继续尝试恢复当前资源与设计文档if the timeout is reached without ready returning true, velero will continue on to attempt restore of the current item的描述一致。详细设计二RestoreItemAction插件接口新增AreAdditionalItemsReady要让等待逻辑生效插件必须有能力回答这批额外资源是否已就绪。因此设计文档为RestoreItemAction接口新增了一个方法type RestoreItemAction interface { // AppliesTo returns information about which resources this action should be invoked for. // A RestoreItemActions Execute function will only be invoked on items that match the returned // selector. A zero-valued ResourceSelector matches all resources. AppliesTo() (ResourceSelector, error) // Execute allows the ItemAction to perform arbitrary logic with the item being restored, // including mutating the item itself prior to restore. The item (unmodified or modified) // should be returned, along with an optional slice of ResourceIdentifiers specifying additional // related items that should be restored, a warning (which will be logged but will not prevent // the item from being restored) or error (which will be logged and will prevent the item // from being restored) if applicable. Execute(input *RestoreItemActionExecuteInput) (*RestoreItemActionExecuteOutput, error) // AreAdditionalItemsReady allows the ItemAction to communicate whether the passed-in // slice of AdditionalItems (previously returned by Execute()) // are ready. Returns true if all items are ready, and false // otherwise. The second return value is an error string if an // error occurred. AreAdditionalItemsReady(restore *api.Restore, AdditionalItems []ResourceIdentifier) (bool, string) }值得注意的是设计文档中的AreAdditionalItemsReady第二个返回值是string错误字符串而当前仓库中的 v2 接口实际实现返回的是error类型——例如 pkg/restore/actions/csi/volumesnapshotclass_action.go 中的实现func (p *volumeSnapshotClassRestoreItemAction) AreAdditionalItemsReady( additionalItems []velero.ResourceIdentifier, restore *velerov1api.Restore, ) (bool, error) { return true, nil }同时参数顺序也与设计初稿相反当前实现为additionalItems在前、restore在后。这说明该设计在落地过程中经历过迭代修正读者在参考设计文档编写插件时应以当前仓库 v2 接口的实际签名为准。源码佐证v2 插件协议与 RPC 调用链当前仓库中AreAdditionalItemsReady已经完整落地于插件协议层。从 pkg/plugin/proto/restoreitemaction/v2/RestoreItemAction.proto 可以看到RestoreItemAction服务中新增了对应的 RPCservice RestoreItemAction { rpc AppliesTo(RestoreItemActionAppliesToRequest) returns (RestoreItemActionAppliesToResponse); rpc Execute(RestoreItemActionExecuteRequest) returns (RestoreItemActionExecuteResponse); rpc Progress(RestoreItemActionProgressRequest) returns (RestoreItemActionProgressResponse); rpc Cancel(RestoreItemActionCancelRequest) returns (google.protobuf.Empty); rpc AreAdditionalItemsReady(RestoreItemActionItemsReadyRequest) returns (RestoreItemActionItemsReadyResponse); }其中RestoreItemActionItemsReadyRequest携带plugin插件名、restore序列化的 Restore 对象与additionalItemsrepeated generated.ResourceIdentifier响应RestoreItemActionItemsReadyResponse只含一个ready布尔字段——这也解释了为什么设计文档中该函数只返回(bool, string)而不包含更复杂的就绪信息。完整的 RPC 调用链为Velero 主进程通过 pkg/plugin/clientmgmt/restoreitemaction/v2/restartable_restore_item_action.go 中的RestartableRestoreItemAction.AreAdditionalItemsReady发起调用该方法会先确保插件进程存活再委托给 gRPC 客户端gRPC 客户端在 pkg/plugin/framework/restoreitemaction/v2/restore_item_action_client.go 中构造RestoreItemActionItemsReadyRequest并调用远端 RPC插件侧服务端在 pkg/plugin/framework/restoreitemaction/v2/restore_item_action_server.go 中反序列化Restore与AdditionalItems调用插件实现并回传ready。此外pkg/plugin/clientmgmt/restoreitemaction/v2/restartable_restore_item_action.go 中还有一个值得注意的适配层v1 旧版插件适配器AdaptedV1RestartableRestoreItemAction的AreAdditionalItemsReady直接返回true——因为 v1 插件不参与等待逻辑这保证了旧插件在 v2 协议下的兼容性。详细设计三RestoreItemActionExecuteOutput新增字段与WithItemsWait()两个可选字段设计文档为RestoreItemActionExecuteOutput新增了两个可选字段当前仓库的完整定义见 pkg/plugin/velero/restore_item_action_shared.go// RestoreItemActionExecuteOutput contains the output variables for the ItemActions Execution function. type RestoreItemActionExecuteOutput struct { // UpdatedItem is the item being restored mutated by ItemAction. UpdatedItem runtime.Unstructured // AdditionalItems is a list of additional related items that should // be restored. AdditionalItems []ResourceIdentifier // SkipRestore tells velero to stop executing further actions // on this item, and skip the restore step. When this fields // value is true, AdditionalItems will be ignored. SkipRestore bool // v2 and later // OperationID is an identifier which indicates an ongoing asynchronous action which Velero will // continue to monitor after restoring this item. If left blank, then there is no ongoing operation. OperationID string // v2 and later // WaitForAdditionalItems determines whether velero will wait // until AreAdditionalItemsReady returns true before restoring // this item. If this fields value is true, then after restoring // the returned AdditionalItems, velero will not restore this item // until AreAdditionalItemsReady returns true or the timeout is // reached. Otherwise, AreAdditionalItemsReady is not called. WaitForAdditionalItems bool // v2 and later // AdditionalItemsReadyTimeout will override serverConfig.additionalItemsReadyTimeout // if specified. This value specifies how long velero will wait // for additional items to be ready before moving on. AdditionalItemsReadyTimeout time.Duration }两个字段的语义分别如下WaitForAdditionalItemsbool等待开关。为true时restoreItem会在恢复完AdditionalItems后调用itemsAvailable进而调用插件的AreAdditionalItemsReady轮询直到返回true或超时为false默认值时保持 Velero 原有行为AreAdditionalItemsReady不会被调用。AdditionalItemsReadyTimeouttime.Duration单插件超时覆盖项。非零时覆盖服务器全局配置设计初稿中为serverConfig.additionalItemsReadyTimeout默认 10 分钟当前仓库实现中该默认值体现为--resource-timeout的默认值 10 分钟见 pkg/cmd/server/config/config.go。这一字段的价值在于不同依赖类型的就绪耗时差异极大例如等待一个存储类注册可能只需几秒而等待大规模自定义资源控制器收敛可能需要数分钟插件可以按需收紧或放宽等待窗口。在 gRPC 传输层这两个字段对应 RestoreItemAction.proto 中的bool waitForAdditionalItems 5;与google.protobuf.Duration additionalItemsReadyTimeout 6;客户端在 restore_item_action_client.go 中通过res.WaitForAdditionalItems与res.AdditionalItemsReadyTimeout.AsDuration()完成回填。链式辅助函数WithItemsWait()设计文档还提出新增一个与既有WithoutRestore()风格一致的链式函数WithItemsWait()用于把WaitForAdditionalItems置为true。当前仓库实现见 pkg/plugin/velero/restore_item_action_shared.go// WithItemsWait returns RestoreItemActionExecuteOutput with WaitForAdditionalItems set to true. func (r *RestoreItemActionExecuteOutput) WithItemsWait() *RestoreItemActionExecuteOutput { r.WaitForAdditionalItems true return r }插件中的典型用法设计文档给出了一个完整的使用范式插件实现AreAdditionalItemsReady内部按资源类型执行具体的就绪判定并在Execute()中通过WithItemsWait()声明需要等待func AreAdditionalItemsReady(restore *api.Restore, additionalItems []ResourceIdentifier) (bool, string) { // ... 按资源类型检查每个 additional item 的就绪状态 ... return true, } func (p *RestorePlugin) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) { // ... 构造 AdditionalItems 与其余逻辑 ... return velero.NewRestoreItemActionExecuteOutput(input.Item).WithItemsWait(), nil }即先通过NewRestoreItemActionExecuteOutput(input.Item)创建输出再链式调用.WithItemsWait()打开等待开关必要时同时设置AdditionalItemsReadyTimeout。这样插件既声明了我依赖这些额外资源又声明了我需要等待它们就绪而就绪的判定逻辑完全由插件自己掌控。设计迭代插件版本化与向后兼容2021 年 2 月修订设计文档后半部分记录了一次重要的实现迭代值得单独说明——因为它直接影响了接口的最终形态与版本发布节奏。最初的实现思路是在RestoreItemActionExecuteOutput中存放一个等待函数指针wait func pointer但实现过程中发现了一个关键约束Velero 插件调用基于 gRPC Protocol Buffers 的预定义 RPC 消息格式函数是固定的 RPC 调用无法在结构体中直接传递普通的 Go 函数指针自动生成的 Go 代码不支持这种模式。因此设计被修正为显式的AreAdditionalItemsReady函数。由于向RestoreItemAction接口新增方法会破坏与现有插件的向后兼容性所有已存在的插件都会因缺少该方法而编译失败设计文档明确建议该特性的实现应等待 Velero 的插件版本化机制plugin versioning对应 upstream issue #3285落地之后再进行。有了插件版本化之后不定义AreAdditionalItemsReady的旧版插件无版本号或 1.0 版本可以与定义了新方法的 2.0或 1.1版本RestoreItemAction插件共存而不会破坏既有生态。当前仓库中这一结论已经得到落地验证AreAdditionalItemsReady完整存在于 v2 插件协议RestoreItemAction.proto、v2 gRPC 客户端/服务端restore_item_action_client.go、restore_item_action_server.go以及 v1 适配层restartable_restore_item_action.gov1 插件适配器恒返回true以保持行为兼容。这与设计文档等待插件版本化后再实现的规划完全一致。设计文档还指出迁移到新插件版本后绝大多数插件其实并不需要等待额外资源。它们应对接口变更的最小改动仅仅是补上一个恒真实现func AreAdditionalItemsReady(restore *api.Restore, additionalItems []ResourceIdentifier) (bool, string) { return true, }只要插件从不把WaitForAdditionalItems置为true这个函数就不会被调用即便被调用由于恒返回true也不会有任何等待开销。测试与验证该特性在仓库中拥有对应的单元测试覆盖。在 pkg/restore/restore_test.go 中测试用的 mock 插件recordResourcesAction与pluggableAction都实现了AreAdditionalItemsReady见 restore_test.go 与 restore_test.go并且测试结构体中支持配置WaitForAdditionalItems见 restore_test.go用于验证restoreItem在等待开关开启时是否正确地进入就绪轮询路径。此外内置的 CSI 恢复插件是理解该接口落地的现成范例volumeSnapshotClassRestoreItemAction在Execute()中把关联的 lister Secret 作为AdditionalItems返回见 pkg/restore/actions/csi/volumesnapshotclass_action.go并实现了恒真的AreAdditionalItemsReady见同文件 L101-L106。同一目录下的pvc_action.go、volumesnapshot_action.go、volumesnapshotcontent_action.go也都实现了该方法共同构成了该特性在内置插件中的最佳实践参照。总结等待AdditionalItems就绪机制解决了 Velero 恢复流程中一个真实的竞态问题它把额外资源已恢复与额外资源已可用两个阶段明确区分开并借助RestoreItemAction插件自身的领域知识来定义就绪。整套机制的三个核心契约可以归纳为接口层面RestoreItemAction新增AreAdditionalItemsReady(additionalItems []ResourceIdentifier, restore *api.Restore) (bool, error)由插件判定就绪状态输出层面RestoreItemActionExecuteOutput新增可选字段WaitForAdditionalItems与AdditionalItemsReadyTimeout并配套WithItemsWait()链式函数流程层面restoreItem在恢复完额外资源后调用itemsAvailable以 1 秒间隔轮询插件就绪判定超时默认 10 分钟可由插件覆盖后继续尝试恢复当前资源而不是无限阻塞。同时该特性依托插件版本化机制平滑落地v1 旧插件通过适配层恒返回就绪新插件按需启用等待既不破坏既有生态又为依赖敏感型资源如需要等 CRD Established、等控制器收敛的资源提供了可靠的恢复保障。【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表