Velero 备份仓库缓存卷(Backup Repository Cache Volume)设计解析 Velero 备份仓库缓存卷Backup Repository Cache Volume设计解析【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero导读本篇文章围绕 Velero 在统一仓库Unified Repository架构下引入的**备份仓库缓存卷Backup Repository Cache Volume**机制展开解决数据移动 PodData Mover Pod与仓库维护 Pod 将缓存数据写入节点根文件系统、可能打爆系统盘的问题。通过本文你将掌握缓存卷适用的场景判定、缓存卷大小的计算公式、PVC/PV 的规格约束、node-agent ConfigMap 配置方法以及从节点代理到 Exposer、再到 Kopia 仓库的完整调用链与源码实现位置可直接指导你在真实集群中配置并使用该能力。背景为什么需要独立的缓存卷Velero 基于统一仓库设计引入了可选择的备份仓库Backup Repository用于承载 fs-backup、卷快照数据移动Volume Snapshot Data Movement等多种备份/恢复路径。部分备份仓库在执行仓库操作时需要在客户端侧缓存数据以加速执行例如 Kopia 仓库在连接远端对象存储时需要把内容索引、数据块等缓存到本地。在备份仓库配置中Velero 已经允许用户通过cacheLimitMB配置缓存数据的上限但缓存数据依然落在数据移动 PodData Mover Pod的根文件系统仓库维护 PodRepository Maintenance Pod的根文件系统即最终落在节点的根文件系统上这会带来两个突出问题系统盘容量受限很多发行版中节点系统盘大小是预定义、不可配置且有上限的例如 20G 甚至更小缓存数据容易把系统盘占满。并发数据移动放大风险Velero 支持在每个节点上并发执行多个数据移动每个并发数据移动 Pod 都会占用一部分系统盘缓存很快便会耗尽系统盘进而引发 Pod 驱逐pod eviction、Pod 创建失败、Kubernetes QoS 降级等问题。因此需要允许用户准备一个专用的缓存位置如独立卷而不是把缓存写死在根文件系统上。术语速览术语含义Backup Storage存放备份数据的存储详见统一仓库设计Backup Repository位于数据移动器Data Mover与 Backup Storage 之间的备份仓库层提供备份/恢复相关能力Velero Generic Data Path (VGDP)统一仓库设计引入的模块集合uploader 与备份仓库Velero 用其完成 PodVolume 备份/恢复、卷快照数据移动等数据传输Data Mover Pods持有 VGDP 并完成数据传输的中间 Pod见 VGDP 微服务卷快照数据移动 与 VGDP 微服务fs-backupRepository Maintenance Pods运行仓库维护任务的 Pod持有 VGDP 执行仓库维护设计目标与非目标目标Goals提供一种机制让用户能为运行 VGDP 的各种 Pod 配置缓存卷设计将缓存卷的 Pod 路径指派给备份仓库的工作流明确缓存卷在什么场景、以什么方式被使用。非目标Non-Goals本方案建立在统一仓库设计、VGDP 微服务卷快照数据移动与 VGDP 微服务fs-backup 之上不支持遗留数据路径。例如当 PodVolumeRestorePVR运行在遗留 Restic 路径时即使有缓存数据缓存依然位于根文件系统。缓存数据分析有效负载 vs 仓库元数据不同备份仓库的缓存数据内容各异总体上可分为两类Payload 数据有效负载与备份数据高度相关通常占仓库数据的大头也是缓存数据的主体。仓库元数据Repository Metadata与备份仓库的块切分算法、数据块映射方法等相关其大小与备份数据规模不成正比。对于某些仓库极端情况下元数据可能异常庞大。例如Kopia 的索引是按块per chunk建立的如果仓库中存在海量小文件Kopia 的索引数据可能与 payload 数据相当甚至更大。但设计文档同时指出在元数据成为多数的场景下通常会出现其他瓶颈数据移动的并发度会被显著约束因此对缓存卷的需求反而会降低。基于以上分析当前方案只为 payload 数据考虑缓存卷需求把元数据缓存卷留作未来增强。场景分析何时需要缓存卷在 VGDP 运行期间备份仓库缓存情况因仓库与操作类型而异。设计文档对四类场景做了逐一定性场景说明是否需要专用缓存卷数据上传备份例如 DataUpload、PodVolumeBackup数据几乎是直接写入仓库只会有小规模数据短暂驻留本地否仓库维护大多数情况下只访问仓库元数据且单节点上不宜并发运行多个维护任务缓存数据不大否数据下载恢复例如 DataDownload、PodVolumeRestore对于把数据存在远端备份存储如 Kopia 仓库存于远端对象存储的仓库会大规模缓存本地数据以加速恢复是备份删除仅连接仓库、枚举元数据以定位表示备份数据的仓库快照最多只缓存元数据否上述分析基于备份仓库的常见行为未考虑元数据占据缓存数据主要比例的情形。结论当前只为恢复场景创建专用缓存卷其他场景按未来需求扩展。设计上缓存卷的暴露与挂载机制对所有场景通用——例如未来若需覆盖元数据缓存可直接复用同一套缓存卷供给与挂接机制扩展到备份与仓库维护场景。缓存数据生命周期随 Pod 而生随 PVC 而灭缓存卷的生命周期遵循简单而严格的规则若配置可用一个缓存卷被专门指派给一个数据移动 Pod数据移动 Pod 完成时缓存数据随之销毁对应的备份仓库实例也随之关闭缓存数据完全由备份仓库管理仓库可以在数据移动 Pod 运行期间自行执行缓存 GC剩余数据在数据移动 Pod 与缓存 PVC 被销毁时自动清除——因为缓存 PVC 的reclaimPolicy始终为DeletePVC 销毁后底层卷一并销毁。因此无需为缓存数据设计额外的 GC 逻辑生命周期完全由 Pod/PVC 的销毁闭环托管。数据规模与缓存卷大小计算数据规模小备份不值得建卷缓存卷会占用存储空间与集群资源PVC、PV因此只在必要时创建且卷大小应与缓存数据规模匹配小备份不值得为其创建缓存卷应使用驻留缓存位置根文件系统中的缓存目录缓存数据有上限由既有配置cacheLimitMB控制。例如 1TB 的备份可设cacheLimitMB1024意味着最多缓存 1GB 数据超出部分会被清理。因此把缓存卷设得远大于cacheLimitMB没有意义。缓存卷大小公式对恢复Restore场景缓存卷大小由以下因素决定Limit缓存数据上限即cacheLimitMB的取值默认 5GBbackupSize备份大小作为是否创建缓存卷的参考量不代表备份数据一定决定缓存大小仅用于评估备份规模小规模备份需要小缓存。若 backupSize 与缓存数据规模无关则不应设置 ResidentThreshold直接使用 LimitbackupSize 基本不会缺失一旦缺失则忽略 ResidentThreshold直接用 LimitResidentThreshold创建缓存卷所需的最小备份大小InflationPercentage考虑文件系统开销与缓存清理可能的延迟最终卷大小相对逻辑大小需要有一个膨胀系数该百分比在代码中硬编码为 20%。计算公式如下cacheVolumeSize ((backupSize ! 0 ? (backupSize residentThreshold ? limit : 0) : limit) * (100 inflationPercentage)) / 100最终cacheVolumeSize会向上取整到 GiB以兼顾用户体验、存储与管理友好性。源码中的公式实现在 pkg/exposer/cache_volume.go 中getCacheVolumeSize精确实现了上述逻辑func getCacheVolumeSize(dataSize int64, info *CacheConfigs) int64 { if info nil { return 0 } if dataSize ! 0 dataSize info.ResidentThreshold { return 0 } // 20% inflate and round up to GB volumeSize : (info.Limit*12/10 (130) - 1) / (130) * (130) return volumeSize }对照设计公式可以验证实现细节dataSize ! 0 dataSize info.ResidentThreshold时直接返回 0即不创建缓存卷小备份走根文件系统驻留缓存膨胀 20% 通过info.Limit * 12 / 10实现(x (130) - 1) / (130) * (130)实现向上取整到 GiB。默认 5GB 的限制也已在 Kopia 后端代码中印证DefaultCacheLimitMB 5000见 pkg/repository/udmrepo/kopialib/backend/common.go。PVC/PV 规格约束与前置校验缓存卷的 PVC 有以下硬性约束创建于Velero 命名空间必须指定 storage class用于供给缓存 PVCaccessMode为ReadWriteOncevolumeMode为FileSystem。因此所选存储类必须同时支持这两项规格。否则数据移动 Pod 可能一直处于Pending状态直到数据移动超时如prepareTimeout后才最终失败。由于缓存卷不应在数据移动 Pod 删除后保留存储类的reclaimPolicy必须是Delete。前置校验为了尽早发现问题node-agent 会对存储类做校验一旦校验失败缓存配置将被忽略数据移动 Pod 以不带缓存卷的方式创建。在 pkg/cmd/cli/nodeagent/server.go 的validateCachePVCConfig中可以看到func (s *nodeAgentServer) validateCachePVCConfig(config velerotypes.CachePVC) error { if config.StorageClass { return errors.New(storage class is absent) } sc, err : s.kubeClient.StorageV1().StorageClasses().Get(s.ctx, config.StorageClass, metav1.GetOptions{}) if err ! nil { return errors.Wrapf(err, error getting storage class %s, config.StorageClass) } if sc.ReclaimPolicy ! nil *sc.ReclaimPolicy ! corev1api.PersistentVolumeReclaimDelete { return errors.Errorf(unexpected storage class reclaim policy %v, *sc.ReclaimPolicy) } return nil }校验逻辑包括两条存储类必须存在存储类的reclaimPolicy必须为Delete。校验失败时node-agent 会记录一条 Ignore cache config 的警告日志并继续以无缓存卷方式运行见 pkg/cmd/cli/nodeagent/server.go保证不影响恢复结果。在 pkg/exposer/cache_volume.go 中createCachePVC负责实际创建 PVC其实现与设计完全对应pvcObj : corev1api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Namespace: ownerObject.Namespace, Name: cachePVCName, OwnerReferences: []metav1.OwnerReference{...}, // 以数据移动的 owner 对象为属主 }, Spec: corev1api.PersistentVolumeClaimSpec{ AccessModes: []corev1api.PersistentVolumeAccessMode{corev1api.ReadWriteOnce}, StorageClassName: sc, VolumeMode: volumeMode, // corev1api.PersistentVolumeFilesystem Resources: corev1api.VolumeResourceRequirements{ Requests: corev1api.ResourceList{ corev1api.ResourceStorage: *resource.NewQuantity(size, resource.BinarySI), }, }, }, }值得注意的细节PVC 通过OwnerReferences绑定到所属对象数据下载/恢复任务随着属主对象销毁自动级联清理PVC 命名为owner 对象名 -cache后缀cacheVolumeDirSuffix -cache见 pkg/exposer/cache_volume.go若指定了目标节点PVC 会带上kube.KubeAnnSelectedNode注解volume.kubernetes.io/selected-node确保调度到执行数据移动的节点。缓存卷配置项与 ConfigMap 样例设计中引入两个新配置项residentThresholdMB触发创建缓存卷的最小待处理数据量MB即前文的 ResidentThresholdcacheStorageClass用于供给缓存 PVC 的存储类名称。与cacheLimitMB不同——cacheLimitMB作用于备份仓库本身——这两个配置项实际是数据移动器data mover侧的配置决定如何为数据移动 Pod 创建缓存卷且不需要按仓库区分。因此它们被添加到node-agent 的 Configuration中。在源码中对应的结构体位于 pkg/types/node_agent.gotype CachePVC struct { // StorageClass specifies the storage class for cache PVC StorageClass string json:storageClass,omitempty // ResidentThresholdInMB specifies the minimum size of the backup data to create cache PVC ResidentThresholdInMB int64 json:residentThresholdInMB,omitempty }并作为NodeAgentConfigs.CachePVCConfig挂载在 node-agent 配置根节点下pkg/types/node_agent.goJSON 字段名为cachePVC。三种配置样例以下是 node-agent ConfigMap 的配置示例Sample-1合法{ cacheVolume: { storageClass: sc-1, residentThresholdMB: 1024 } }恢复任务中备份数据大于 1G 时将使用存储类sc-1分配缓存卷。Sample-2合法{ cacheVolume: { storageClass: sc-1 } }数据移动 Pod 始终被分配使用存储类sc-1的缓存卷未设置阈值任何恢复都建卷。Sample-3不合法{ cacheVolume: { residentThresholdMB: 1024 } }缺少存储类不是合法配置Velero 将放弃创建缓存卷。需要说明设计文档示例中使用的键名为cacheVolume而当前仓库源码 pkg/types/node_agent.go 中该字段的 JSON 键为cachePVC结构体名CachePVC使用时请以当前仓库实际生成的 node-agent ConfigMap 结构为准可先查看集群中已安装的 velero node-agent ConfigMap 确认键名。创建 ConfigMap 的命令将上面的样例保存为 json 文件后执行kubectl create cm ConfigMap name -n velero --from-filejson file name由于缓存卷配置由 node-agent server 读取还需在velero node-agent参数中指定--node-agent-configmap详细设计从 restore 到 Kopia 的调用链1. 恢复任务携带备份大小snapshotSize字段恢复流程需要知道备份大小以计算缓存卷大小因此 DataDownload 与 PodVolumeRestore 两个 CRD 新增了snapshotSize字段spec: snapshotID: description: SnapshotID is the ID of the Velero backup snapshot to be restored from. type: string snapshotSize: description: SnapshotSize is the logical size of the snapshot. format: int64 type: integersnapshotSize表示备份的总大小恢复期间该值从 DataUpload/PodVolumeBackup 的Status.Progress.TotalBytes传递到 DataDownload/PodVolumeRestore。若Status.Progress.TotalBytes缺失概率极低按前述公式residentThresholdMB被忽略缓存卷大小直接按对应备份仓库的缓存上限计算。2. Exposer计算大小、创建 PVC、挂载并传递路径缓存卷配置由 node-agent 取出经 DataDownload/PodVolumeRestore 传递到GenericRestore exposer / PodVolume exposer。Exposer 负责计算缓存卷大小创建缓存 PVC将 PVC 挂载到 restorePod若计算出的缓存卷大小为 0或关键参数缺失如存储类缺失Exposer忽略缓存卷配置继续创建不带缓存卷的 restorePod不影响恢复结果这一点在 pkg/exposer/generic_restore.go 的代码中可以看到仅在getCacheVolumeSize返回大于 0 时才创建缓存 PVC否则记录日志并跳过。Exposer 将缓存卷挂载到预定义目录并通过--cache-volume-path参数把目录传给数据移动 Pod见 pkg/exposer/generic_restore.go挂载卷名为cachedir最终以fmt.Sprintf(--cache-volume-path%s, cacheVolumePath)传入。设计文档给出的 Expose 参数新增数据结构如下type GenericRestoreExposeParam struct { // RestoreSize specifies the data size for the volume to be restored RestoreSize int64 // CacheVolume specifies the info for cache volumes CacheVolume *CacheVolumeInfo } type PodVolumeExposeParam struct { // RestoreSize specifies the data size for the volume to be restored RestoreSize int64 // CacheVolume specifies the info for cache volumes CacheVolume *repocache.CacheConfigs } type CacheConfigs struct { // StorageClass specifies the storage class for cache volumes StorageClass string // Limit specifies the maximum size of the cache data Limit int64 // ResidentThreshold specifies the minimum size of the cache data to create a cache volume ResidentThreshold int64 }源码中对应的CacheConfigs定义见 pkg/exposer/cache_volume.goGenericRestoreExposeParam.CacheVolume字段见 pkg/exposer/generic_restore.go。控制器组装缓存配置在 pkg/controller/data_download_controller.go 中DataDownload 控制器把 node-agent 传入的缓存配置与仓库的客户端缓存上限合并为exposer.CacheConfigsvar cacheVolume *exposer.CacheConfigs if r.cacheVolumeConfigs ! nil { if limit, err : r.repoConfigMgr.ClientSideCacheLimit(velerov1api.BackupRepositoryTypeKopia, r.backupRepoConfigs); err ! nil { log.WithError(err).Warnf(Failed to get client side cache limit ...) } else { cacheVolume exposer.CacheConfigs{ Limit: limit, StorageClass: r.cacheVolumeConfigs.StorageClass, ResidentThreshold: r.cacheVolumeConfigs.ResidentThresholdInMB 20, } } }注意ResidentThresholdInMB 20把配置的 MB 值转换为字节单位Limit则来自仓库配置管理器按仓库类型如 Kopia解析出的客户端缓存上限即cacheLimitMB的语义。随后RestoreSize由dd.Spec.SnapshotSize提供pkg/controller/data_download_controller.go与CacheVolume一起组成GenericRestoreExposeParam传给 Exposer。3. 数据移动 Pod目录为空则回落根文件系统数据移动 Pod 从cache-volume-path参数取得缓存卷目录并将其传给 Unified Repository。若目录为空Unified Repository 使用驻留位置即根文件系统作为数据缓存——这保证了即使未配置缓存卷恢复功能依然可用。4. Kopia 仓库定制 CacheDirectoryKopia 仓库对元数据与数据都支持缓存目录配置。现有SetupConnectOptions被修改为按传入的存储选项定制CacheDirectoryfunc SetupConnectOptions(ctx context.Context, repoOptions udmrepo.RepoOptions) repo.ConnectOptions { ... return repo.ConnectOptions{ CachingOptions: content.CachingOptions{ CacheDirectory: cacheDir, ... }, ... } }当前源码实现位于 pkg/repository/udmrepo/kopialib/backend/common.go除了缓存目录还揭示了更多工程细节cacheLimit : optionalHaveIntWithDefault(ctx, udmrepo.StoreOptionCacheLimit, repoOptions.StorageOptions, DefaultCacheLimitMB) 20 cacheDir : optionalHaveString(udmrepo.StoreOptionCacheDir, repoOptions.StorageOptions) // 80% for data cache and 20% for metadata cache and align to KB dataCacheLimit : (cacheLimit / 5 * 4) 10 metadataCacheLimit : (cacheLimit / 5) 10 return repo.ConnectOptions{ CachingOptions: content.CachingOptions{ CacheDirectory: cacheDir, // softLimit 80% ContentCacheSizeBytes: (dataCacheLimit / 5 * 4) 10, MetadataCacheSizeBytes: (metadataCacheLimit / 5 * 4) 10, // hardLimit 100% ContentCacheSizeLimitBytes: dataCacheLimit 10, MetadataCacheSizeLimitBytes: metadataCacheLimit 10, ... }, ... }这里把cacheLimitMB按80% 数据缓存 / 20% 元数据缓存拆分并分别设置软上限80%与硬上限100%与设计文档中“缓存数据有上限、超出即清理”的描述相互印证。小结Backup Repository Cache Volume 机制为 Velero 的 VGDP 数据路径补齐了一块关键拼图在不改变备份仓库语义的前提下把恢复场景的大规模本地缓存从节点根文件系统迁移到用户指定的存储类供给的专用卷上。核心要点可归纳为场景收敛当前只为恢复DataDownload/PodVolumeRestore创建专用缓存卷备份、维护、删除场景暂不建卷机制本身对未来扩展通用大小可控cacheLimitMB默认 5GB 作为缓存上限residentThresholdMB过滤小备份20% 膨胀后向上取整到 GiB避免资源浪费与卷被写满生命周期自洽PVC 以 owner 对象为属主、reclaimPolicyDelete缓存随 Pod/PVC 销毁自动清理无需额外 GC失败可降级存储类缺失、校验失败或备份大小不可用等异常情况下Exposer 会忽略缓存配置恢复任务依旧以根文件系统缓存正常执行。如果你正在大规模恢复场景中遇到节点磁盘被 Kopia 等远端仓库缓存打满的问题可以通过配置 node-agent ConfigMap 中的cachePVC或cacheVolume配置并指定支持ReadWriteOnce、FileSystem、reclaimPolicyDelete的存储类为每个数据移动 Pod 挂载专属缓存卷从而把缓存对节点磁盘的冲击彻底隔离。延伸阅读统一仓库设计、VGDP 微服务设计、fs-backup 微服务设计、仓库维护任务配置、备份仓库配置。【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考