Velero(原 Heptio Ark)FAQ 深度解读:etcd 备份替代与恢复保真度剖析
2026/9/17 7:17:46 网站建设 项目流程

Velero(原 Heptio Ark)FAQ 深度解读:etcd 备份替代与恢复保真度剖析

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

导读

本文围绕 Velero 仓库 site/content/docs/v0.6.0/faq.md 中两个最核心的经典问题展开:什么场景下应该用 Velero 而不是 etcd 自带的备份/恢复工具,以及Velero 能否把 Kubernetes 资源"原样"恢复出来。结合当前仓库中pkg/restore的恢复引擎与 RestoreItemAction 源码实现,你将理解 Velero 跨集群迁移能力的底层机制,以及恢复过程中 Pod、Service、Job、PV/PVC 等资源会被改写哪些字段、为什么必须改写。

历史背景说明:本文关联的 FAQ 出自 v0.6.0 时代,当时项目名为Heptio Ark。如今该项目已更名为 Velero(见 README.md 中的描述 "Velero (formerly Heptio Ark)"),FAQ 中的 "Ark" 即今天的 Velero。

一、什么时候该用 Velero,而不是 etcd 自带的备份/恢复?

这是 Velero 用户最常问的第一个问题。FAQ 给出的核心结论是:etcd 自带工具适合单 etcd 集群的数据丢失恢复,而集群级的备份与恢复管理,Velero(Ark)是更合适的选择。

1. etcd 内置备份/恢复的适用边界

FAQ 明确指出,etcd 的 backup/restore 工具链擅长处理单个 etcd 集群内部的数据丢失,一个典型场景就是:

  • 在升级 etcd 本身之前,先对 etcd 做一次备份。

也就是说,etcd 备份解决的是"etcd 这个数据存储层自身"的可用性问题,它的恢复单位是 etcd 集群本身,而不是"整个 Kubernetes 集群的业务状态"。

2. Velero 的定位:可以"扔弃坏集群、在全新集群中恢复"

FAQ 给出了 Velero(Ark)相比 etcd 备份更合适的核心理由:

It gives you the ability to throw away an unstable cluster and restore your Kubernetes resources and data into a new cluster, which you can't do easily just by backing up and restoring etcd.

即:Velero 允许你直接丢弃一个不稳定的集群,把 Kubernetes 资源与持久化数据恢复到另一个全新集群中——这是单纯备份/恢复 etcd 很难做到的事。

这个能力在当前仓库的架构中可以得到印证。v0.6.0 的架构文档 site/content/docs/v0.6.0/_index.md 说明:Ark 的 Backup、Schedule、Restore 本身都是基于 CRD 定义的自定义资源,由对应的自定义控制器处理。一次ark backup create会触发如下流程:

  1. ark 客户端调用 Kubernetes API Server,创建Backup自定义资源;
  2. BackupController发现新 Backup 并完成校验;
  3. 校验通过后,控制器通过 API Server 聚合集群资源数据;
  4. 控制器把备份数据打包上传到对象存储(如 Amazon S3);
  5. 默认情况下还会通过云厂商 API 对持久卷做磁盘快照(可通过--snapshot-volumes=false关闭)。

注意第 1 步中的一个精妙细节:Ark 自己的 Backup CR 确实也存放在 etcd 里(所有 Kubernetes 对象都存于 etcd),但真正的备份数据——资源清单的 tarball 和 PV 快照——被放在了对象存储 + 云快照中,与 etcd 完全解耦。这正是它能够"脱离原集群、在新集群重建一切"的根本原因。

3. 适合使用 Velero 的典型场景(FAQ 原列 5 类)

FAQ 列举了 5 个典型的 Ark/Velero 使用场景,其中多个场景是 etcd 备份天然无法覆盖的:

场景为什么 etcd 备份难以胜任
你无法访问 etcd(例如运行在 GKE 上)托管集群不开放 etcd 访问权,只能从集群外部做应用层备份
同时备份 Kubernetes 资源与持久卷状态etcd 只保存 API 对象,不保存 PV 数据内容
集群迁移需要在另一个全新集群中重建资源,而非恢复原 etcd
只备份 Kubernetes 资源的一个子集etcd 是全量 dump,无法按命名空间/标签/资源类型做选择性备份
资源分散存储在多个 etcd 集群(例如自定义 apiserver)单个 etcd 备份只能覆盖它自己承载的那部分资源

其中"备份子集"的能力在实操中有直接体现:v0.6.0 快速入门示例使用ark backup create nginx-backup --selector app=nginx只备份带app=nginx标签的对象;恢复侧则支持更细粒度控制,见 site/content/docs/v0.6.0/cli-reference/ark_create_restore.md,包括--include-namespaces--exclude-namespaces--include-resources--exclude-resources--include-cluster-resources--namespace-mappings--selector等参数——这种"按需圈定范围"的灵活性正是 etcd 全量备份不具备的。

二、Velero 能把我的 Kubernetes 资源"原样"恢复吗?

FAQ 的第二个问题直指恢复保真度:

Will Ark restore my Kubernetes resources exactly the way they were before? — Yes, with some exceptions.

答案是**"基本可以,但有例外"**。FAQ 给出的例子是:恢复 Pod 时,Velero 会删除 Pod 的nodeName字段,以便它能在新节点上重新调度。文档引用了当时的pod_action.go,对应到当前仓库的源码位于 pkg/restore/actions/pod_action.go。

下面从当前仓库源码出发,系统梳理"恢复时被改写的字段"——这些改写不是缺陷,而是为了让对象能在新集群中合法、正确地重建所必需的。

1. "可以原样恢复"的部分:通用元数据重置

在恢复引擎层,所有资源对象在落库前都会经过通用的元数据与状态重置。见 pkg/restore/restore.go 中的resetMetadata(约 L2489-L2508)与resetStatus(L2510-L2512):

  • resetMetadata会删除generateNameselfLinkuidresourceVersiongenerationcreationTimestampdeletionTimestampdeletionGracePeriodSecondsownerReferences等字段;
  • resetStatus会删除整个status子对象(例如 Pod 的运行阶段、IP 等运行时状态,这些在新集群中毫无意义且可能干扰调度);
  • addRestoreLabels会给恢复对象打上velero.io/backup-namevelero.io/restore-name两个标签(L2539-L2552),用于追溯来源。

这些字段都是集群级的唯一标识或运行时状态,直接拷贝到新集群会导致冲突或误导控制器,因此必须清除。这也是"有例外"的第一个层次:Kubernetes 对象的"存在性元数据"不会被原样恢复

2. 例外之一:Pod 的nodeName与调度

FAQ 明确提到的 PodnodeName删除逻辑,在 pkg/restore/actions/pod_action.go 的Execute方法中实现(L46-L52):

pod.Spec.NodeName = "" pod.Spec.Priority = nil
  • nodeName记录了 Pod 在原集群中被调度到哪个节点;原节点在新集群中可能根本不存在,若保留该字段,调度器会认为 Pod 已被调度,Pod 将卡在 Pending 状态无法运行。清空后由新集群的调度器重新分配节点。
  • Priority是调度器计算出的优先级快照,同样属于"绑定旧集群状态"的字段。

对应的单测 pkg/restore/actions/pod_action_test.go 中有一个用例名称就叫"nodeName (only) should be deleted from spec",直接验证了该行为:输入含NodeName: "foo"的 Pod,输出中NodeName必须被清空、其余字段保持不变。

3. 更多例外:PodAction 还做了什么?

顺着同一个文件往下读可以发现,Pod 的改写远不止nodeName。当前实现(L52-L95)还会:

  • 清空pod.Spec.Priority(如上所述);
  • 删除 ServiceAccount Token 卷:凡是卷名以<serviceAccountName>-token-前缀开头的 Volume(以及容器、initContainer 中对应的 VolumeMount)都会被移除——这些 token 卷是集群自动注入的,恢复时应当由新集群重新注入,否则恢复出的卷定义是失效的;
  • 把 PriorityClass 加入AdditionalItems:若 Pod 声明了PriorityClassName,该 PriorityClass 会被登记为"待恢复的附加资源",确保依赖的优先级类也一并恢复(L90-L94)。

pod_action_test.go的多个用例逐一验证了 token 卷删除、initContainer 卷挂载清理、PriorityClass 附加项等行为。这些都属于"恢复后由新集群重建运行时状态"的设计。

4. 更多例外:Service 的 ClusterIP 与 NodePort

pkg/restore/actions/service_action.go(ServiceAction)说明了另一类"不能原样恢复"的字段:

  • ClusterIP:除非是 headless Service(ClusterIP == "None"),否则恢复时spec.clusterIPspec.clusterIPs会被清空,让新集群重新分配。因为原集群的 ClusterIP 在新集群中很可能已被占用或不再可用(L57-L60)。
  • NodePort:默认情况下自动分配的 NodePort 会被删除(deleteNodePorts,L145-L253),避免端口冲突。其判断逻辑相当精细:通过kubectl.kubernetes.io/last-applied-configuration注解或 ManagedFields 识别哪些 NodePort 是用户显式指定的,显式指定的保留、自动分配的置 0。同时deleteHealthCheckNodePort会处理 LoadBalancer 类型的健康检查端口。
  • 如果用户在创建恢复时显式声明保留端口(--preserve-nodeports,对应 Restore 规格中的PreserveNodePorts),则跳过 NodePort 删除逻辑(L62-L72)。

相关测试见 pkg/restore/actions/service_action_test.go,其中既有"clusterIP/clusterIPs should be deleted from spec"也有"headless clusterIP should not be deleted from spec"的用例。

5. 更多例外:Job 的controller-uid标签

pkg/restore/actions/job_action.go 中的JobAction处理 Job 的选择器与模板标签:它会从spec.selector.matchLabelsspec.template.metadata.labels中删除controller-uid(Kubernetes 1.27+)与旧版controller-uid兼容标签。原因在于这些标签携带的是原 Job 控制器生成的 UID 标识,恢复后若保留,会让新集群中的 Job 与无关的历史 Pod 产生错误的属主关联。

6. 更多例外:PV/PVC 的绑定信息

在恢复引擎的通用逻辑中,resetVolumeBindingInfo(pkg/restore/restore.go L2463-L2487)会清理 PV 上的绑定信息:

  • 删除spec.claimRef.uidspec.claimRef.resourceVersion(这些是高度唯一的绑定标识);
  • 删除pv.kubernetes.io/bind-completedpv.kubernetes.io/bound-by-controller两个注解。

这样恢复出的 PV 看起来像一个"静态供给、待手动绑定"的卷,PVC 与 PV 可以在新集群中由 PV(C) 控制器重新完成绑定,而不是带着旧集群的绑定指纹直接落地。代码注释明确指出:若不清除这些信息,卷将无法被 Velero 重新绑定。

7. 更多例外:终态对象

pkg/restore/restore.go 的isCompleted(L2554-L2578)还会识别"已完结"的对象:状态为Failed/Succeeded的 Pod,或带有status.completionTime的 Job。这类对象在备份时刻已经是终态,恢复一个"已完成"的 Pod/Job 没有意义,恢复引擎会据此决定是否跳过,避免在新集群中恢复出陈旧的一次性工作负载。

三、实操:如何验证"恢复后的集群状态"

FAQ 本身没有给出验证步骤,但其结论"all of the objects ... should be just as they were before"可以在 v0.6.0 的快速入门 site/content/docs/v0.6.0/_index.md 中找到完整的验证路径,这里补充为可操作的检查清单:

  1. 发起恢复ark restore create nginx-backup(当前版本对应命令为velero restore create ...)。
  2. 查看恢复状态ark restore get,关注STATUSWARNINGSERRORS三列。
  3. 判定标准:当STATUSCompleted、且WARNINGSERRORS均为 0 时,恢复成功。
  4. 排查细节:若有告警或错误,用ark restore get <RESTORE NAME> -o yaml查看恢复对象的详细结果。
  5. 预期差异提醒:验证时请记住本文第二部分的内容——Pod 会被重新调度到新节点、Service 会拿到新的 ClusterIP 与 NodePort、对象的uid/resourceVersion/status均会重置。"恢复成功"不等于"字节级一致",而是"业务状态一致、且能在新集群正常运行"

小结

回到 FAQ 的两个核心结论:

  1. etcd 备份 vs Velero:前者解决单个 etcd 集群的数据丢失恢复(如 etcd 升级前的保险),后者解决集群级的备份/恢复、迁移与选择性备份;由于 Velero 把资源清单与 PV 快照放在对象存储和云快照中,它可以在新集群中重建一切,而这是 etcd 备份做不到的。
  2. 恢复保真度:Velero 会尽最大可能还原对象,但会主动改写那些"绑定旧集群状态"的字段——Pod 的nodeName与 token 卷、Service 的ClusterIP/NodePort、Job 的controller-uid、PV/PVC 的绑定信息、所有对象的元数据与 status——这些改写恰恰是恢复能在全新集群中成功落地的前提。想深入了解每个改写规则,可以继续研读 pkg/restore/actions 目录下各*_action.go文件及其配套的*_action_test.go测试用例。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询