Kubernetes 备份工具选型与互操作证据
凌晨的恢复演练已经进行到第三个小时。备份平台显示 Completed,Kubernetes 对象也回来了,但数据库 Pod 卡在 CrashLoopBackOff:恢复出的 PVC 只包含复制过程中的文件状态,没有事务 checkpoint;依赖的 Operator CRD 尚未安装,旧集群的云身份又不能在新账号换取令牌。团队换了第二套工具导入卷快照,才发现 snapshot handle 只能在源存储账号读取。此时再争论哪款产品“功能更多”已经没有意义,真正缺少的是一套能在采购前暴露断点的恢复证据。
架构选型应从故障域和恢复合同反推工具组合:源集群、源存储账号、KMS 或网络控制面失效后,哪些副本仍可读取,谁能重建对象,谁能恢复应用事务,谁负责验证和接管。任何候选都要在相同样本、相同故障注入和相同业务断言下接受隔离恢复;只有证据链闭合,许可价格、操作界面和生态偏好才有比较意义。
先把失败拆成七个可验证问题
同一条“备份成功”消息可能只覆盖 API 对象、单卷快照或文件仓库中的一种。选型会议先为目标应用填写恢复合同,空白项必须变成补充工具或淘汰条件,不能留给事故现场猜测。
application: checkout
failureScenario: source-cluster-and-storage-account-lost
protectionObjects:
kubernetes: [CRD, Namespace, RBAC, Secret, ConfigMap, StatefulSet, Service]
volumes: [orders-data, ledger-data]
applicationState: [database-checkpoint, schema-version]
external: [kms-key, image-registry, message-queue, dns]
rpo:
evidence: last-restored-business-watermark
objectiveRef: '<approved-slo-reference>'
rto:
start: incident-declared
end: business-read-write-and-identity-check-passed
consistency: application
portability:
target: different-cluster-account-and-csi-driver
license:
productionUseApproved: false
restoreDuringSubscriptionLapseTested: false
exit:
inventoryExported: false
oldRecoveryPointReadableWithoutController: false七个问题分别对应七类证据:
| 决策面 | 必须回答的问题 | 能判定的证据 |
|---|---|---|
| 保护对象 | CRD、工作负载、PVC、应用 checkpoint、外部依赖和密钥分别由谁恢复 | 对象清单 digest、卷/快照 ID、checkpoint、外部资产引用与 owner |
| RPO | 故障切点前最近哪个恢复点真正通过恢复 | 业务水位、复制延迟、失败/部分成功记录,可重新计算的数据损失窗口 |
| RTO | 从宣布事故到业务读写、身份和依赖恢复用了多久 | 基础设施、对象、卷、应用、入口各阶段时间和最终业务 SLI |
| 一致性 | 单卷、多卷和数据库处于什么一致性等级 | hook/冻结日志、checkpoint/WAL 位点、恢复后完整性查询 |
| 可移植性 | 源集群、账号、区域、CSI driver 或产品消失后能否读取 | 第二集群导入、StorageClass 映射、新 PVC 校验和镜像/身份重映射 |
| 许可 | 技术可运行是否也允许生产、规模、支持与灾备使用 | 当前合同、节点/容量计量、功能 entitlement、支持矩阵与法务确认 |
| 退出 | 停止续费或卸载后,历史恢复点、索引、密钥和删除责任是否仍可用 | 资产导出、独立读取实验、双轨恢复、凭证撤销和费用归零记录 |
RPO 不是调度间隔。计划每小时备份,但最近一次可恢复点因仓库复制失败停在更早的业务水位,实测 RPO 就从故障切点追溯到那个水位。RTO 也不是 Restore Job 的运行时间;等待网络、集群、CSI、镜像、身份、数据库校验和流量切换的时间都必须计入。
把候选工具放回它实际持有的对象
下面的比较不是功能排行榜。每个工具只在自己能产生可恢复证据的对象域内得分,缺口必须由明确的第二条链补齐。
| 工具路径 | 主要保护对象 | RPO / RTO 的主要约束 | 一致性入口 | 可移植性边界 | 许可与退出检查 |
|---|---|---|---|---|---|
| Velero | Kubernetes API 对象;CSI snapshot、FSB 或 snapshot data movement 保护卷 | 调度、仓库复制、data mover 吞吐;恢复还受对象顺序和卷绑定影响 | backup hook、存储快照、应用原生动作 | 原生 snapshot 受 provider/账号/区域约束;FSB/data mover 仓库通常更易跨集群 | 开源本体不消除 provider 插件与存储责任;旧 restic 恢复兼容必须在升级前处理 |
| Veeam Kasten | Policy 驱动的应用对象、卷、RestorePoint 与导出目录 | Policy 排队、快照/导出吞吐、导入和应用恢复阶段 | hook 与 Kanister Blueprint 等应用动作 | portable export 必须带实际数据;只导出 snapshot reference 仍依赖源后端 | 核对当前节点计量、版本支持和功能 entitlement;验证许可变化时历史点读取与导出 |
| K8up | PVC 文件、应用命令 stdout 或 PreBackupPod 产物进入 restic 仓库 | 文件扫描与对象存储吞吐;恢复前必须先有目标 PVC | 应用命令或独立 dump Pod;普通 PVC 文件复制不是数据库一致 | restic 仓库便于跨集群,但 workload、RBAC、Service 需另一路径重建 | 开源不等于零退出成本;必须保管仓库密码、兼容 restic 版本与 CR 清单 |
| Kanister | Blueprint 定义的应用级 backup/restore/delete 动作及 Artifact 引用 | 取决于数据库工具、Job 资源、仓库吞吐与外部调度 | 它的核心就是显式应用工作流 | Artifact 格式与目标环境由 Blueprint 决定,不提供统一整应用迁移目录 | Apache-2.0;退出时仍要保留 Blueprint、工具镜像、Artifact、密钥和可执行 restore |
| VolSync | ReplicationSource/ReplicationDestination 驱动的 PVC 复制或 restic 数据 | 复制周期、网络、快照和 mover 容量;不含应用对象重建时间 | Direct 复制不提供应用一致性,需停写、快照或外部 hook | 可跨集群传输 PVC 数据,但应用清单、Secret、CRD 和身份另行恢复 | operator 主体 AGPL-3.0,API 目录另有 Apache-2.0;退出要验证 mover 数据格式、仓库和 PVC 接管 |
| 托管 Kubernetes 备份 | 由云产品明确定义的集群对象和卷范围 | 服务配额、区域支持、归档层级、目标集群创建与云任务排队 | 通常只承诺产品文档声明的级别,数据库协议仍由应用负责 | 受云项目、区域、IAM、StorageClass、KMS 和支持资源限制 | 核对当前区域、计费、保留、跨项目恢复和导出能力;不能把云订阅当作永久读取器 |
Velero 1.18 是理解升级风险的一个具体基线:新 File System Backup 使用 Kopia 路径,旧 restic 路径只能恢复、不能继续写;后续发行线还会进一步停止旧路径恢复。因此,候选方案如果有旧 restic 恢复点,必须在兼容版本中完成恢复、重备或保留隔离读取环境。版本号首次影响行为时,应查阅 Velero File System Backup 与对应升级文档,而不是把“格式看起来都是文件仓库”当作互操作承诺。
Kasten、Kanister 和 VolSync 的对象名不能互换:Kasten Policy 产生运行 Action 和 RestorePoint;Kanister Blueprint 定义阶段,ActionSet 才触发一次动作,输出是 Artifact 引用;VolSync 的 ReplicationSource 与 ReplicationDestination 只表达 PVC 数据复制。能把这些对象画进同一恢复图,不代表它们能彼此导入。
常见组合以及组合接口
组合不是“多装几个控制器”,而是为每个缺口定义交接物:
可以采用以下组合,但每一种都要给交接字段和失败 owner:
Velero + 数据库原生备份:Velero 保存对象与卷,数据库工具保存事务位点。恢复点索引必须把 Backup UID、snapshot ID、数据库 backup ID 与镜像 digest 绑定在一起。Kasten + Kanister Blueprint:Kasten 负责 Policy、RestorePoint、导出与目录;Blueprint 执行数据库动作。验收要同时看到 Kasten Action 成功、Artifact 可读和数据库查询通过。
GitOps + K8up:Git 重建声明对象,K8up 恢复 PVC 或 dump。Git 中不能保存恢复密钥;恢复编排必须先建立 CRD、Operator、身份和 PVC,再放行业务。GitOps/对象备份 + VolSync:前者恢复应用清单,VolSync 提供异地 PVC 数据。两边必须共享同一恢复点 ID,并在切换前停止旧写入者。CSI 快照 + 独立数据移动:快照用于同后端快速恢复,数据移动副本用于账号、区域或后端丢失。容量模型要同时包含快照占用、仓库占用和恢复缓存。
不要假定两个工具都使用 restic、Kopia、CSI 或对象存储就具备互操作性。仓库布局、索引、加密密钥、标签、锁、压缩、chunk 格式和生命周期都可能不同。互操作成立的唯一证据,是受支持的导出/导入合同或真实的跨工具恢复实验。
正向实验:用同一恢复探针比较候选方案
实验在专用集群和隔离对象存储前缀中进行。操作者需要测试 Namespace 的对象管理权限、只读查看 CRD/StorageClass/VolumeSnapshotClass 的权限,以及由安全 owner 签发的短期仓库身份。恢复身份与日常备份身份分开;实验不能使用生产 bucket、KMS key 或消息端点。
先创建一个不依赖特定备份产品的恢复探针。它把期望数据写入 PVC,并用 ConfigMap 保存校验值:
export LAB_NS=backup-choice-lab
export STORAGE_CLASS='<approved-storage-class>'
kubectl create namespace "$LAB_NS"
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: source-data
namespace: ${LAB_NS}
spec:
accessModes: [ReadWriteOnce]
storageClassName: ${STORAGE_CLASS}
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: recovery-contract
namespace: ${LAB_NS}
data:
recovery-point: rp-alpha
expected-sha256: pending
---
apiVersion: batch/v1
kind: Job
metadata:
name: seed-data
namespace: ${LAB_NS}
spec:
template:
spec:
restartPolicy: Never
containers:
- name: seed
image: busybox:1.36
command: [sh, -c, "printf 'order=alpha\\ncheckpoint=42\\n' > /data/probe.txt; sha256sum /data/probe.txt"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: source-data
EOF
kubectl -n "$LAB_NS" wait --for=condition=complete job/seed-data --timeout=120s
kubectl -n "$LAB_NS" logs job/seed-data预期日志包含 probe.txt 的 SHA-256。把该值写入 recovery-contract,然后分别通过候选工具创建恢复点。每次运行都保存这些字段:工具与版本、Kubernetes/CSI 版本、恢复点 ID、对象清单 digest、仓库/快照引用、开始与结束时刻、源数据 SHA-256、应用 checkpoint、权限主体和费用标签。
候选工具完成备份后,不在原 Namespace 原地恢复。创建新的隔离 Namespace,并按产品支持的方式恢复对象与 PVC,最后运行同一个读取 Job:
export RESTORE_NS=backup-choice-restore
kubectl create namespace "$RESTORE_NS"
# 这里由候选工具把数据恢复为 ${RESTORE_NS}/restored-data。
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: verify-data
namespace: ${RESTORE_NS}
spec:
template:
spec:
restartPolicy: Never
containers:
- name: verify
image: busybox:1.36
command: [sh, -c, "cat /data/probe.txt; sha256sum /data/probe.txt; grep -qx 'checkpoint=42' /data/probe.txt"]
volumeMounts:
- name: data
mountPath: /data
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: restored-data
EOF
kubectl -n "$RESTORE_NS" wait --for=condition=complete job/verify-data --timeout=120s
kubectl -n "$RESTORE_NS" logs job/verify-data通过证据不是某个产品状态为绿色,而是:恢复出的 ConfigMap 恢复点 ID 与索引一致;restored-data 为 Bound;日志同时出现 order=alpha、checkpoint=42 和原 SHA-256;恢复主体无法删除源恢复点;隔离 Namespace 没有连接生产入口。将“事件宣布到校验 Job 完成”的总时长记录为这次实验 RTO 样本,将故障切点与 checkpoint=42 对应业务水位之差记录为 RPO 样本。
反向实验:让快照引用在目标集群失效
反例用于识别“目录可见但数据不可携带”。在第二个隔离集群或不同存储账号中,只导入对象清单和 snapshot reference,不复制真实卷数据,也不给源账号读取权限。然后尝试从该引用创建 PVC。
kubectl -n "$RESTORE_NS" get volumesnapshot,volumesnapshotcontent
kubectl -n "$RESTORE_NS" describe pvc restored-data
kubectl -n "$RESTORE_NS" get events --sort-by=.lastTimestamp预期证据是 VolumeSnapshot 可能存在,但动态供应失败、PVC 长期 Pending,事件指向 snapshot handle、权限、区域、driver 或 VolumeSnapshotClass 不兼容。这个失败证明原生快照不满足当前跨账号/跨后端可移植性;它不证明 CSI 快照技术无效。同一候选若启用受支持的数据导出或 data mover 后再次恢复成功,才说明“快速本地快照 + 独立可移植副本”的组合闭环。
再增加一个一致性反例:让写入 Job 在文件级备份期间不断覆盖两个相关文件,不执行冻结或 checkpoint。恢复后比较两者的 generation:
kubectl -n "$RESTORE_NS" exec deploy/checker -- sh -c \
'printf "left="; cat /data/left.gen; printf "right="; cat /data/right.gen'若 generation 不同,文件都能读取却不是同一业务状态。候选工具只能被记录为 crash/file consistency,除非加入应用 hook、原生 dump、事务日志或经过验证的组快照协议。禁止把这次失败通过扩大重试次数“修好”。
清理实验而不误删恢复证据
先导出实验报告与产品对象状态,再删恢复出的工作负载。确认 PVC 的 reclaim policy、VolumeSnapshotContent 的 deletionPolicy、对象仓库保留与产品删除动作后,才能清理底层数据。
kubectl -n "$LAB_NS" get all,pvc,configmap -o yaml > lab-source-inventory.yaml
kubectl -n "$RESTORE_NS" get all,pvc,configmap -o yaml > lab-restore-inventory.yaml
kubectl delete namespace "$RESTORE_NS" --wait=true
kubectl delete namespace "$LAB_NS" --wait=true删除 Namespace 不保证删除 provider snapshot、Kasten RestorePointContent、Kanister Artifact、K8up/restic snapshot、VolSync repository 或对象存储版本。逐一执行对应产品的保留/删除动作,观察后端资产和仓库容量,再撤销临时 IAM、RoleBinding、receive string、PSK、repository password 副本与网络入口。不可变对象仍在保留期内时,报告应记录预计释放条件,而不是反复重试删除。
权限、容量与费用会改变选择结果
备份控制器常需要读取 Secret、PV、CRD 和集群级对象,恢复身份还需要创建或更新这些对象。把日常备份与灾难恢复绑定为同一个长期 cluster-admin,会让一个仓库凭证或控制器漏洞同时获得读取生产数据和重建集群的能力。较稳妥的结构是:
| 身份 | 常驻能力 | 按需能力 | 禁止能力 |
|---|---|---|---|
| 备份执行身份 | 读取批准对象、写指定仓库前缀、创建指定快照 | 无 | 删除不可变副本、修改 KMS 策略、恢复到生产 |
| 恢复执行身份 | 平时禁用或只读库存 | 经审批读取恢复点、创建目标资源、使用恢复 KMS | 删除源恢复点、修改保留策略 |
| 删除/保留身份 | 查看容量与保留 | 双人审批后删除到期点 | 创建工作负载、读取业务 Secret |
| 租户操作者 | 操作本 Namespace 的策略与状态 | 发起受限恢复申请 | 读取其他租户仓库、使用 privileged mover |
Kasten 的受支持安装可能要求官方定义的集群级权限;这应被视为产品架构事实,而不是通过随意删减 Role 来制造“最小权限”。K8up、VolSync 等即使 CR 是 namespaced,cluster-wide operator 或共享 repository 密码仍可能跨越 Namespace。真正隔离要同时拆分仓库路径、密码、云身份、网络出口和审计。
容量模型至少包含:源数据变化率、快照增量、仓库去重后占用、对象版本/WORM 放大、data mover 缓存、并发读取源盘 IOPS、网络出口、恢复临时卷和失败重试。可用下面的关系做第一轮预算:
backup_window >= changed_bytes / min(source_read, node_cpu, network, repository_write)
restore_window >= restore_bytes / min(repository_read, network, target_write) + object_reconcile + application_validation
peak_space = snapshots + repository_versions + mover_cache + restore_staging + safety_margin公式里的吞吐必须来自目标规模的演练,不能使用厂商峰值。若实测恢复窗口超过 RTO,可以增加并发,但要同时观察 API Server 限流、节点 CPU/内存、源卷延迟、对象存储请求限额和目标 CSI 队列。费用评估则包含存储、版本保留、跨区复制、读取/取回、网络出口、临时计算、商业节点许可和演练人力。
升级与退出必须在采购前演练
升级门禁不是“新版本能安装”,而是旧恢复点仍能读、新旧 CRD 能转换、插件/driver/工具镜像兼容、删除语义没有改变。每次升级先在隔离环境恢复一个旧点和一个新点,比较对象 diff、数据 checksum、应用查询、权限和总时长。CRD 先后顺序由具体产品文档决定;禁止直接删除 CRD,因为这可能连同策略、恢复点索引或 finalizer 一起丢失。
退出实验采用双轨而非一次卸载:
冻结新的长期保留点进入旧平台,只保留短过渡期写入。导出策略、恢复点目录、对象清单、仓库位置、密钥引用和审计记录。用目标方案为同一业务创建新恢复点,分别恢复旧、新两条链。
验证旧平台停服或许可不可用时,合同允许且技术上仍能读取的恢复路径。等最老合规恢复点过期或迁移完成后,停止旧 controller,保留可审计库存。删除旧 CR 前处理 finalizer 与底层资产,撤销身份、网络、仓库写入和商业订阅。
以“没有活跃工作负载、没有孤儿快照/对象版本、没有可用凭证、没有持续账单”作为退出证据。
如果第 4 步做不到,采购合同、归档格式和隔离恢复环境就是架构组成,而不是法务附件。能持续生成备份却无法在许可、版本或厂商退出后读取历史点的方案,不满足长期恢复要求。
形成选择结论
最终决策文档不写“某产品功能最全”,而写每个灾难场景由哪组能力闭环:
需要整应用对象、策略目录、委派恢复与商业支持时,可评估 Kasten,并以实际许可、portable export 和退出读取实验为门禁。需要通用 Kubernetes 对象恢复,并在 CSI 快照、FSB 与 data mover 之间组合时,可评估 Velero;跨故障域和旧仓库兼容需要单独证明。需要轻量 PVC/restic 备份或应用 dump,且对象声明由 GitOps 重建时,可评估 K8up;必须保管仓库密码并补齐工作负载恢复。
需要数据库或应用自定义动作时,可用 Kanister 表达 backup/restore/delete;它不能单独替代调度、整应用目录和对象恢复。需要 PVC 异步复制、预热灾备卷或 restic 数据路径时,可评估 VolSync;必须补齐应用对象、一致性协议和单写者切换。已深度使用单一云且恢复目标不跨产品边界时,可评估托管备份,但仍要验证资源覆盖、IAM、区域、费用和外部依赖。
选择只有在正向恢复、稳定失败的反例、清理、升级和退出实验都留下证据后才成立。产品状态页是诊断入口,业务可恢复性才是结论。
实施时查阅的产品入口
Velero 版本化文档与 Restore Reference。Veeam Kasten 文档。K8up 架构与对象规格
