news 2026/9/15 18:09:42

Velero Node-agent 负载舒缓(Load Soothing)机制:用 `prepareQueueLength` 约束数据移动 Pod 与快照的过度创建

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Velero Node-agent 负载舒缓(Load Soothing)机制:用 `prepareQueueLength` 约束数据移动 Pod 与快照的过度创建

Velero Node-agent 负载舒缓(Load Soothing)机制:用prepareQueueLength约束数据移动 Pod 与快照的过度创建

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

导读

在 Velero 的 CSI 快照数据移动(Volume Snapshot Data Movement)与文件系统备份(fs-backup)场景中,VGDP 并发限额(loadConcurrency)只有在数据移动 Pod 启动进入running之后才会真正生效,导致大批并行卷备份时会先创建大量“闲置”的数据移动 Pod 与卷快照,白白占用节点 Pod 配额、系统盘空间,甚至拖垮存储性能。本文以 Node-agent Load Soothing 设计文档 为核心,完整讲解 Velero 引入的prepareQueueLength配置项:它如何以“尽力而为(best effort)”的方式在 VGDP 节流之前就先抑制数据移动 CR(DataUpload / DataDownload / PodVolumeBackup / PodVolumeRestore)的进入Accepted/Prepared阶段,从而从源头削减中间对象;并结合仓库源码逐层还原其数据结构、Controller 工作流程、计数缓存与重排队机制,帮助你判断该特性是否适合你的集群环境并正确配置。

VGDP 与数据移动负载:为什么需要“舒缓”

VGDP 与并发限额的既有机制

Velero Generic Data Path(VGDP)是 Velero 在统一仓库(Unified Repository)设计中引入的模块集合,用于完成各类数据传输任务,包括 PodVolume 备份/恢复与卷快照数据移动,其核心组成部分是各类 uploader 与备份仓库(backup repository)。

VGDP 的并发控制机制来源于 Node-agent Concurrency 设计:CSI 快照数据移动的备份/恢复以及 fs-backup 的所有数据移动活动,都遵循node-agent-configmap中配置的loadConcurrency设置;一旦现有负载数量超过对应的并发数,多余负载就会被节流挂起,直到 VGDP 配额释放。

然而,这一节流存在一个关键盲区:它只发生在数据移动 Pod 启动并进入running之后。也就是说,当有大量并发卷备份时,可能会先创建出许多数据移动 Pod,而它们内部的 VGDP 实例实际处于挂起等待状态。

过度创建带来的三类问题

设计文档明确列举了这种“先建后等”模式的危害:

  1. Pod 配额阻塞:在某些环境中,集群或单节点存在 Pod 数量上限,大量未激活的数据移动 Pod 可能阻塞其他重要 Pod 的调度;
  2. 系统盘空间侵占:部分环境中节点系统盘容量有限,而 Pod 也占用系统盘空间,大量闲置数据移动 Pod 会挤占空间,导致关键 Pod 被驱逐;
  3. 快照数量与生命周期失控:对于 CSI 快照数据移动备份,在创建数据移动 Pod 之前卷快照就已创建;由于 VGDP 要等配额释放才开始工作,快照会大量产生并长时间存活。而某些环境中大量快照不被允许,或会造成存储性能下降。

需要强调的是,VGDP 节流本身是精确的控制机制——被节流的正是所需数量的数据移动 Pod;但它“启动后才生效”的时序,决定了必须有一个更前置的“舒缓”机制来避免上述问题的发生。

设计目标与边界

设计文档为本机制划定了明确的目标与非目标:

  • 目标 1:允许用户配置期望的、等待 VGDP 负载并发配额的负载数量上限;
  • 目标 2:创建一个舒缓机制,当现有负载数量超过预期值时,阻止新负载启动。
  • 非目标不追求从负载发起阶段就进行精确控制。

之所以不追求精确控制,是因为数据移动 Pod 会被调度到哪一组节点几乎无法预测——受所选节点、亲和性(affinity)、节点操作系统等多种复杂因素影响,精确预测不切实际。因此该机制只要求“有效减少”过多闲置数据移动 Pod 与卷快照,而非“精准控制”。

prepareQueueLength:配置结构与作用原理

新增字段与数据结构

解决方案是在node-agent-configmaploadConcurrency中引入一个新字段prepareQueueLength,它表示允许处于“准备中(preparing/expose)”状态的负载数量。所谓“准备中”,是指负载的 CR 已进入AcceptedPrepared阶段。

设计文档给出的数据结构如下,该结构已在仓库源码 pkg/types/node_agent.go 中原样落地:

type LoadConcurrency struct { // GlobalConfig specifies the concurrency number to all nodes for which per-node config is not specified GlobalConfig int `json:"globalConfig,omitempty"` // PerNodeConfig specifies the concurrency number to nodes matched by rules PerNodeConfig []RuledConfigs `json:"perNodeConfig,omitempty"` // PrepareQueueLength specifies the max number of loads that are under expose PrepareQueueLength int `json:"prepareQueueLength,omitempty"` }

在 NodeAgentConfigs 中,LoadConcurrency作为loadConcurrencyJSON 字段被整体挂载在 node-agent 配置上。

生效语义

  • prepareQueueLength必须是正数,负数会被忽略
  • 一旦设置了有效值,舒缓机制即开始生效:以尽力而为的方式,只允许配额数量的 CR 进入AcceptedPrepared阶段,其余 CR 保持New状态继续等待;由此,数据移动 Pod 与卷快照的创建数量也被限制在配额之内;
  • 如果未设置(或为无效值),node-agent 保持与传统行为完全一致:CR 一经 Controller 处理就进入Accepted/Prepared,数据移动 Pod 与卷快照无约束地创建。

设计文档同时给出了一条重要的取舍建议:只有当环境中确实存在 Pod 或卷快照数量约束、需要限制过量的待处理数据移动 Pod 与快照时,才根据 VGDP 负载并发数来设置该值;否则无需使用该特性——并行准备反而有助于提高并发度。

生效时机:启动时读取,修改后需重启

node-agent server 在启动时检查该配置并用它初始化相关的 VGDP 模块。因此用户虽然可以随时编辑 ConfigMap,但要让修改生效,必须重启 node-agent server。

这一点在源码 pkg/cmd/cli/nodeagent/server.go 中有直接体现:当dataPathConfigs.LoadConcurrency.PrepareQueueLength > 0时,调用exposer.StartVgdpCounter启动 VGDP 计数器,并记录日志"VGDP loads are constrained with %d";若启动失败,仅记录告警日志"Failed to start VGDP counter, VDGP loads are not constrained",不会导致 node-agent 崩溃。官方文档 Node-agent Configuration 也明确要求:编辑 ConfigMap 后执行kubectl rollout restart -n <velero-namespace> daemonset/node-agent使配置生效。

ConfigMap 完整示例与创建方法

组合完整配置的示例

设计文档给出了一个同时包含globalConfigperNodeConfigprepareQueueLength的完整示例,其中globalConfig设置所有节点的默认并发数,perNodeConfig通过 label selector 为特定节点指定不同并发数:

{ "loadConcurrency": { "globalConfig": 2, "perNodeConfig": [ { "nodeSelector": { "matchLabels": { "kubernetes.io/hostname": "node1" } }, "number": 3 }, { "nodeSelector": { "matchLabels": { "beta.kubernetes.io/instance-type": "Standard_B4ms" } }, "number": 5 } ], "prepareQueueLength": 2 } }

上述perNodeConfig的规则结构对应源码中的 RuledConfigs 类型:NodeSelector为匹配节点的 label selector,Number为匹配节点的并发数值。

创建 ConfigMap

将类似上面的示例保存为 JSON 文件后,执行以下命令创建 ConfigMap(官方文档 node-agent-prepare-queue-length 与 node-agent-configmap 中的操作方式一致):

kubectl create cm node-agent-config -n velero --from-file=<json file name>

ConfigMap 的生命周期管理由用户负责,Velero 不参与其创建与维护。

将 ConfigMap 提供给 node-agent

安装时可通过velero install --node-agent-configmap=<ConfigMap-Name>指定(见 node-agent-configmap 文档);安装后则编辑 node-agent DaemonSet:

kubectl edit ds node-agent -n velero

spec.template.spec.containers中加入--node-agent-configmap参数:

spec: template: spec: containers: - args: - --node-agent-configmap=<configMap name>

只配置prepareQueueLength的简化示例

如果只想开启舒缓机制、沿用默认并发,只需(见 官方文档示例):

{ "prepareQueueLength": 10 }

配置项的取值要点(依据 node-agent-configmap 文档):

  • 取值范围:从 1 起,无上限;
  • 作用范围:全局作用于所有 node-agent Pod 上的 PVB、PVR、DataUpload、DataDownload Pod 的待处理数量;
  • 默认值:不设置则无限制;
  • 受影响 CR 阶段:DataUpload/DataDownload 处于AcceptedPrepared阶段的 CR,以及 PodVolumeBackup/PodVolumeRestore 处于准备阶段的 CR。

详细设计:Controller 层面的实现

四条控制链路

该机制涉及四个 Controller 的改动:DataUpload Controller、DataDownload Controller、PodVolumeBackup Controller、PodVolumeRestore Controller。设计文档规定了四步处理流程:

  1. 舒缓仅作用于处于New状态的数据移动 CR(DataUpload、DataDownload、PodVolumeBackup 或 PodVolumeRestore);
  2. 开始处理 CR 之前,对应 Controller 统计集群中已存在或等待暴露(expose)的 CR 总数,即所有处于AcceptedPreparing状态的 DataUpload、DataDownload、PodVolumeBackup、PodVolumeRestore 之和;
  3. 若总数未超过允许值,Controller 将 CR 的阶段置为Accepted
  4. 一旦总数超过允许值,Controller 放弃处理该 CR 并稍后重新入队(requeue),重试延迟为5 秒

源码实现与这四步完全对应。以 data_upload_controller.go 为例,当 CR 阶段为空或为New且未被取消时:

if r.vgdpCounter != nil && r.vgdpCounter.IsConstrained(ctx, r.logger) { log.Debug("Data path initiation is constrained, requeue later") return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil }

RequeueAfter: time.Second * 5正是设计文档中的 5 秒重试延迟。相同的判断逻辑同时存在于 data_download_controller.go、pod_volume_backup_controller.go 与 pod_volume_restore_controller.go 中,形成四条统一的约束链路。

VgdpCounter:基于客户端缓存的计数与缓存

设计文档强调,计数发生在所有节点的所有 Controller 上,为避免拖垮 API server,计数基于Controller 客户端缓存(client caches)完成,且计数结果会被缓存,只在必要时才重新统计。

这一设计在 pkg/exposer/vgdp_counter.go 中得到完整实现,核心结构如下:

  • VgdpCounter为四类 CR(DataUpload / DataDownload / PodVolumeBackup / PodVolumeRestore)各维护一对状态:duState/ddState/pvbState/pvrState(原子变化的changeID)与对应的缓存duCacheState等(缓存的队列长度);
  • initListeners(L58-L164)为每类 CR 注册 Informer 的UpdateFunc事件处理器,监听阶段变化;
  • IsConstrained(L166-L223)通过changeID判断是否需要重新计数:仅当 ID 与缓存 ID 不一致时才用client.List按标签velero.io/expose-on-going=true(常量 ExposeOnGoingLabel)重新统计处于暴露中的 CR 数量,否则直接复用缓存值;最后将四类 CR 的队列长度求和,当existing >= allowedQueueLength时返回“已受限”。

触发重新计数的四种阶段变化

设计文档列出了判断“是否需要重新计数”的四个事件,与initListeners中注册的 UpdateFunc 逻辑一一对应(以 DataUpload 为例,L66-L79):

  1. 一个或多个 CR 的阶段变为AcceptednewDu.Status.Phase == DataUploadPhaseAccepted);
  2. 一个或多个 CR 的阶段Accepted变为某个终态oldDu.Status.Phase == Accepted && newDu.Status.Phase != Prepared,即非转入 Prepared 的离开 Accepted 情况);
  3. 一个或多个 CR 的阶段Prepared变为某个终态
  4. 一个或多个 CR 的阶段Prepared变为InProgress

任一事件发生时都会atomic.AddUint64(&w.duState.changeID, 1),使下一次IsConstrained调用感知到状态变化并重新计数;而阶段未变化(oldDu.Status.Phase == newDu.Status.Phase)的事件则直接忽略。DataDownload、PodVolumeBackup、PodVolumeRestore 的处理逻辑完全对称,且均使用了原子操作与uint64changeID,避免并发访问冲突。

单元测试 pkg/exposer/vgdp_counter_test.go 验证了上述行为:例如在允许队列长度为 1 时,仅存在一个带ExposeOnGoingLabel标签的 DataUpload 即为受限;而当四类 CR 混合、总数达到配额时也会被判定受限,印证了“四类 CR 合并计数”的设计。

为什么是“尽力而为”:跨节点同步的取舍

设计文档特别解释了为什么该机制不是精确控制,并给出了理论上的边界分析:

  1. 跨节点无法精确同步:不同节点的客户端缓存并不一致(coherent),因此不同节点上的 Controller 之间无法精确同步计数;
  2. 同节点同步代价过高:虽然同一节点内的 Controller 之间理论上可以同步,但步骤 2~3(计数与暴露)本就属于 expose 工作流的一部分,强加同步会损害既有工作流的性能与稳定性;
  3. 最终一致性仍可保证:即使不做同步,舒缓机制最终依然生效——当 Controller 看到所有已放行的负载(包括预期内的和超额放行的)之后,就会停止创建新负载,直到配额重新可用;
  4. 窗口极短:需要同步的步骤 2~3 完成得非常快,实际触发最坏情况的概率很低。

基于以上分析,理论上可能出现的超额放行上限为:

最大等待负载数 = prepareQueueLength 数值 + 集群节点数

这是指多个同类型 Controller(如不同节点上的 DataUpload Controller)在重叠时间窗口内同时完成计数与放行时的情形。另一种情形是混合负载并发计数(如数据移动备份、数据移动恢复、PodVolume 备份、PodVolume 恢复混跑),超额放行的数量取决于并发混合负载的数目。两种情形下,由于步骤 2~3 耗时极短,达到理论最坏结果的可能性很小——这正是设计文档将其定性为“舒缓(soothing)”而非“精确节流”的根本原因。

与其他设计的关联

prepareQueueLength机制并非孤立存在,它与以下设计文档构成完整的数据移动控制体系:

  • Node-agent Concurrency 设计:定义loadConcurrency与 VGDP 节流,本机制是其前置补充;
  • Volume Snapshot Data Movement 设计:CSI 快照数据移动,DataUpload/DataDownload 的触发来源;
  • VGDP Micro Service 设计:VGDP 微服务的整体架构;
  • VGDP Micro Service for fs-backup 设计:fs-backup(PodVolumeBackup/PodVolumeRestore)场景的 VGDP 应用。

使用建议与注意事项

  • 按需启用:如果你的环境对每节点/整集群的 Pod 数量、节点系统盘空间或卷快照数量有严格限制,且并行备份/恢复卷数远超可用节点的并行度,建议根据loadConcurrency的并发总量设置prepareQueueLength;反之,若环境无此类约束,保留默认(不设置)以充分利用并行准备带来的吞吐优势。
  • 设置正数:负值会被忽略;值为 0 或不设置均表示不启用舒缓机制。
  • 修改后重启:配置只在 node-agent 启动时读取,修改 ConfigMap 后必须kubectl rollout restart -n velero daemonset/node-agent才能生效(参考 官方文档)。
  • 接受“尽力而为”:由于跨节点缓存不一致且无显式同步,实际放行的等待负载数可能短暂超过prepareQueueLength(理论上限为prepareQueueLength + 节点数),这是设计上的有意取舍,不应视为缺陷。
  • 中间对象随之受限:开启后,不仅数据移动 Pod 的数量受到约束,随 Pod 一并创建的中间资源(PVC、VolumeSnapshot、VolumeSnapshotContent 等)也会同步受限,从而缓解 API server 与存储侧压力(参考 官方文档 与 node-agent-configmap 文档)。

总结

prepareQueueLength是 Velero node-agent 在“启动后节流”的精确 VGDP 并发控制之外,补充的一层前置的、尽力而为的负载舒缓机制。它通过让 DataUpload、DataDownload、PodVolumeBackup、PodVolumeRestore 四类 Controller 在将 CR 从New推进到Accepted之前,基于客户端缓存统计全集群“准备中”的负载总量,并在超出配额时以 5 秒间隔延迟重新入队,从而把数据移动 Pod 与卷快照的创建规模约束在可控范围。该机制以pkg/exposer/vgdp_counter.goVgdpCounter为核心实现,以pkg/cmd/cli/nodeagent/server.go的启动时初始化为入口,并以pkg/controller下四个 Controller 的IsConstrained检查为执行点,构成了一个完整、可测试、可运维的负载控制闭环。

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

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

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

FrankenPHP 快速上手指南:安装、运行与现代 PHP 应用服务器入门

FrankenPHP 快速上手指南&#xff1a;安装、运行与现代 PHP 应用服务器入门 【免费下载链接】frankenphp &#x1f9df; The modern PHP app server 项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp 本文基于仓库中的俄语版项目主页&#xff08;docs/ru/RE…

作者头像 李华
网站建设 2026/9/15 18:07:00

开题报告反复改?2026届避坑指南请收好

导师在群里连发三条消息催开题报告初稿&#xff0c;你盯着文档光标闪了半小时没敲出一个字。这种场景太熟悉了——开题报告写得慢、改得勤&#xff0c;问题多半不在文笔&#xff0c;而在动笔前没把几个关键环节想透。结合过来人经验&#xff0c;四个返工重灾区逐一拆解&#xf…

作者头像 李华