原生快照、数据移动与应用一致性
凌晨的恢复窗口里,平台团队把 Namespace、StatefulSet、Service 和 Secret 全部重新创建,kubectl get 看起来和故障前几乎一样。Pod 却一直停在 Pending:恢复出来的 PV 仍引用原集群的卷句柄;勉强挂载成功的数据库又因为数据卷和 WAL 卷来自不同时间点而拒绝启动。随后团队发现,Secret 中保存的是旧凭证,而外部 KMS 密钥已经轮换。所谓“集群备份成功”,实际只恢复了一部分声明。
另一次事故更隐蔽。存储后台存在可用快照,恢复 PVC 也达到 Bound,应用健康检查全部转绿,但订单序列出现缺口。快照记录的是文件系统可崩溃恢复的瞬间,并没有让数据库完成 checkpoint,也没有冻结持续写入。存储系统正确执行了自己的合同,错误发生在团队把卷级时间点副本当成了应用事务恢复点。
先确认丢失的是哪一种状态
Kubernetes 中至少有五类状态会在灾难后以不同方式丢失。API 对象保存在集群状态库中,包括 Deployment、StatefulSet、RBAC、CRD 和自定义资源;PVC 只是对卷的引用,真实字节位于块存储、文件系统或外部存储;数据库、队列等应用还拥有日志位置、事务边界和成员身份;证书、KMS、云 IAM、DNS 与负载均衡器可能位于集群之外;备份索引、加密密钥和对象存储版本又属于恢复系统自身。
这五类状态没有共同事务。etcd 快照能够回退 Kubernetes API 中持久化的对象,却不会回退云磁盘或外部数据库;CSI 快照能创建后端卷副本,却不会恢复 Namespace、ServiceAccount 或 CRD;Git 和 Helm 能重新生成声明,却不知道 PVC 中应当出现哪些字节。恢复设计必须先把每个业务对象映射到状态位置,再为它指定恢复点、恢复动作和验收者。
Git / Helm / IaC --------> desired resources and infrastructure
|
etcd snapshot -----------> Kubernetes API objects
|
CSI snapshot -----------> backend volume recovery point
file data mover ---------> portable bytes in a backup repository
|
application protocol ----> transaction/checkpoint consistency
|
KMS / IAM / DNS ---------> external identity and traffic state事故现场先收集四组事实,不要立即执行恢复:最后健康的业务时点、对象和卷当前身份、外部系统已经发生的变更、可用恢复点及其依赖。若无法回答某个恢复点使用哪把密钥、引用哪个 volume handle、对应哪个数据库位置,它还不能进入恢复队列。
在可销毁集群建立发现基线
实验应使用可重建且没有生产路由的集群。操作者需要读取目标 Namespace、PV/PVC、StorageClass、CSI driver、VolumeSnapshot API 和事件;安装 snapshot controller、CRD 或 cluster-scoped RBAC 时才使用临时管理员,日常验证改用只读身份。先固定 kubectl context,并把集群 UID、Kubernetes 版本和存储驱动记入实验记录。
kubectl config current-context
kubectl cluster-info
kubectl version
kubectl auth can-i get persistentvolumes
kubectl auth can-i get volumesnapshotcontents.snapshot.storage.k8s.io
kubectl api-resources | grep -E 'persistentvolume|volumesnapshot|volumegroupsnapshot'
kubectl get csidrivers.storage.k8s.io
kubectl get storageclass
kubectl get crd | grep snapshot.storage.k8s.io
kubectl -n kube-system get deploy,pod | grep -E 'snapshot|csi'预期证据不是每条命令都有输出,而是能够明确区分三种结果:API 尚未安装、API 已存在但 driver 不支持、driver 与控制器组合可用。Kubernetes 的 VolumeSnapshot 概念文档说明三类快照对象依赖 CRD、集群级 snapshot-controller 和 CSI driver 的 external-snapshotter;仅能创建 CR 并不证明后端已经生成快照。
生产集群优先使用发行版、云厂商或 CSI driver 供应方声明支持的组件组合。手工安装上游 CRD 与 controller 时要固定 release tag 或 commit,保存镜像 digest、RBAC diff 和驱动兼容矩阵;不要直接从开发分支执行远程 YAML。升级 Kubernetes minor 也不自动升级 driver、sidecar 和 CRD stored version,这些组件共同构成一项存储能力。
用一个小对象建立完整恢复合同
在测试 Namespace 中创建一个 PVC 和写入 Pod,写入带随机内容的文件,同时保存 SHA-256、PVC UID、PV 名称、CSI driver、volume handle 和 StorageClass。若应用有数据卷与日志卷,应把两块卷作为同一个业务恢复单元记录,不能只保存 label selector。
kubectl create namespace backup-lab
kubectl -n backup-lab apply -f pvc-and-writer.yaml
kubectl -n backup-lab wait --for=condition=Ready pod/fixture-writer --timeout=120s
kubectl -n backup-lab exec fixture-writer -- sha256sum /data/fixture.bin
kubectl -n backup-lab get pvc fixture-data -o yaml
kubectl get pv "$(kubectl -n backup-lab get pvc fixture-data -o jsonpath='{.spec.volumeName}')" -o yamlpvc-and-writer.yaml 应固定测试镜像 digest、资源限制和专用 ServiceAccount,不放真实业务数据。预期得到一个 Bound PVC、明确的 PV/driver/handle 以及可重复计算的文件摘要。Pod 未就绪时先读 PVC Event、provisioner 日志和拓扑约束,不能把写入失败继续包装成快照实验。
接下来根据存储能力选择保护动作:支持 CSI snapshot 时创建 VolumeSnapshot,等待 status.readyToUse=true,再从它创建一个全新的恢复 PVC;不支持 snapshot 或需要跨存储迁移时,用文件级数据移动器把字节写入独立仓库;数据库等有状态应用先执行产品支持的 checkpoint、停写或备份协议,再触发卷快照或数据移动。恢复永远写入新 PVC,并在隔离 Pod 中校验,不覆盖正在挂载的源卷。
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: fixture-point
namespace: backup-lab
spec:
volumeSnapshotClassName: <supported-snapshot-class>
source:
persistentVolumeClaimName: fixture-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fixture-restored
namespace: backup-lab
spec:
storageClassName: <restore-storage-class>
dataSource:
name: fixture-point
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: [ReadWriteOnce]
resources:
requests:
storage: <at-least-restore-size>kubectl -n backup-lab wait --for=jsonpath='{.status.readyToUse}'=true \
volumesnapshot/fixture-point --timeout=300s
kubectl -n backup-lab get volumesnapshot fixture-point -o yaml
kubectl get volumesnapshotcontent -o yaml
kubectl -n backup-lab apply -f restored-pvc.yaml
kubectl -n backup-lab get pvc fixture-restored -w
# 只把 fixture-restored 挂到隔离读取 Pod,再计算 /data/fixture.bin 的 SHA-256。正向结果需要同时出现:VolumeSnapshot 可用、对应 VolumeSnapshotContent 能定位后端 handle、恢复 PVC 为新 PV、新 Pod 读取的摘要与记录一致。readyToUse=true 只是链路中段,不是最终判据。
反例要让错误停在能够解释的位置
第一组反例故意引用一个不存在或 driver 不匹配的 VolumeSnapshotClass。预期快照保持未就绪,Event 指向 class 或 driver,不应出现可用后端副本。恢复 class 后重新创建测试对象,确认 controller 能继续调谐。这个实验验证 API 对象、snapshot-controller、sidecar 和 driver 的职责链。
第二组反例在持续写入时直接创建单卷快照,不执行应用 checkpoint。恢复卷可能完成文件系统重放,但业务断言、数据库校验或日志位置应暴露差异。随后按照数据库官方协议冻结写入、确认 checkpoint 位置,再创建新恢复点并重复校验。两轮的差异说明“可崩溃恢复”与“应用一致”不是严重度高低,而是不同合同。
第三组反例撤销数据移动器访问仓库的临时凭证。预期任务明确失败,仓库中不得留下被标为完整的索引;恢复权限后要从新的 run ID 重试并校验对象数量、字节数和校验和。若任务状态成功但仓库缺对象,工具的完成语义或监控还不能承担恢复责任。
快照、数据移动和应用协议怎样配合
CSI 快照通常在同一存储系统内快速创建时间点副本,RPO 可以较小,恢复速度也高,但故障域和可移植性取决于后端。源账号、区域、KMS 或存储控制平面一起丢失时,只有同域快照可能不可用。文件级数据移动会读取卷或快照克隆,把内容写入对象仓库,速度受扫描文件数、读取带宽、压缩、加密和网络限制,却能形成跨集群、跨存储的独立副本。
应用协议负责选择有意义的时点。它可能是停止写入、等待事务收敛、执行 checkpoint、冻结文件系统或调用 Operator 的备份动作。hook 只是执行载体,不提供数据库语义;超时、Pod 重启、leader 切换或部分成员成功时,应让恢复点失败或显式降级。所有冻结动作都要有异常解冻和最大暂停时间,否则备份系统会成为业务故障源。
Volume Group Snapshot 能让同一 driver 支持的一组卷进入共同的存储时间点,但仍然是存储层的 crash consistency。数据库事务、外部队列和跨 driver 卷不会自动加入同一原子边界。启用前要核对目标 Kubernetes 发行版、CRD、controller、sidecar 和 driver capability 的完整组合,并把 selector 实际命中的 PVC UID 集保存下来。
删除策略决定恢复窗口和长期账单
PV 的 persistentVolumeReclaimPolicy 管原卷在 PVC 删除后的命运,VolumeSnapshotContent.spec.deletionPolicy 管快照对象删除后的后端副本,两者互不替代。原卷与快照都为 Delete 时,一次误删可能同时缩短原数据和恢复点寿命;都为 Retain 时,容易留下持续计费且失去 owner 的孤儿资产。
清理实验前先记录后端 snapshot ID 和策略。确认恢复验证完成后,删除隔离读取 Pod 和恢复 PVC,观察新 PV 是否按预期回收;再删除测试 VolumeSnapshot,检查 Content、finalizer、controller/sidecar Event 和后端库存。Retain 需要显式归档或人工删除后端副本,Delete 需要证明 CSI DeleteSnapshot 已完成。对象卡在 Terminating 时先恢复控制器、凭证或后端连接,不要批量移除 finalizer。
kubectl -n backup-lab delete pod fixture-reader --ignore-not-found
kubectl -n backup-lab delete pvc fixture-restored --ignore-not-found
kubectl -n backup-lab delete volumesnapshot fixture-point --ignore-not-found
kubectl get volumesnapshotcontent
kubectl -n backup-lab get events --sort-by=.lastTimestamp
# 对照存储后台确认 snapshot handle 的保留或删除结果后,才删除 backup-lab。
kubectl delete namespace backup-lab权限和凭证按动作拆分
应用团队可以在自己的 Namespace 创建备份请求,但不必读取所有 cluster-scoped VolumeSnapshotContent;存储管理员维护 SnapshotClass、driver 和后端凭证;备份控制器读取选定资源并写仓库;恢复审批者决定何时把数据导入目标。安装者、日常备份者、恢复者和删除者不应共用一个 cluster-admin 身份。
etcd client 证书可读取包含 Secret 与 RBAC 在内的完整集群状态,快照本身应按根级敏感资产加密。CSI SnapshotClass 可能通过 Secret 引用向 driver 传递后端凭证;对象仓库凭证应使用工作负载身份或短期 Secret,限制 bucket/prefix 和操作,并分开写入、恢复、删除权限。轮换凭证后要执行一次小型备份和恢复,证明新身份可用且旧身份已撤销。
审计至少关联 request UID、操作者、目标 Namespace/PVC UID、恢复点 ID、仓库对象版本、KMS key、恢复目标和删除动作。日志与海报不得包含真实证书、Token、内部 endpoint 或未脱敏对象内容。
容量和成本从恢复目标反推
容量预算不能只看源数据总量。CSI 快照的增量块、快照链和写时复制会增加源存储压力;文件级移动受小文件枚举、压缩比、去重索引、加密 CPU 和跨区带宽影响;恢复还需要同时容纳源卷、恢复卷、临时克隆、缓存和日志。演练时记录备份扫描时间、有效字节、读取/上传吞吐、快照创建延迟、恢复 PVC 就绪时间和业务校验时间。
RPO 决定备份频率和变更数据量,RTO 决定恢复并发、预热资源和跨区域复制。保留代数、对象存储请求、跨区流量、KMS 调用、日志索引、控制器资源和人工演练都是成本项。托管产品与云存储价格会变化,实施时在目标账号的官方计费页和成本估算器核算,不把示例数字当预算。
恢复并发也不是越高越好。大量 PVC 同时恢复会争用 CSI controller、云 API 配额、节点挂载、对象仓库和网络出口;大量小文件会让数据移动器受元数据操作而不是带宽限制。容量实验应逐级增加并发,观察队列年龄、错误率、API throttling、CPU/内存、对象请求和完成时间的拐点。
升级和退出要保住恢复点的可读性
升级 Kubernetes、CRD、snapshot-controller、external-snapshotter、CSI driver 或备份工具前,先冻结一组黄金恢复点,在隔离集群验证旧恢复点、新写恢复点和删除策略。检查 CRD 的 served/storage version、conversion、driver capability、镜像 digest 和 SnapshotClass 参数。只看 controller Pod Ready 无法证明旧 snapshot handle 仍可导入。
迁移存储或备份工具时先双写一段恢复点,分别恢复同一业务 fixture,比较数据摘要、应用断言、RPO、RTO 和资源成本。旧系统停止新任务后,仍要保留读取旧恢复点所需的插件、凭证、KMS key、索引格式和运行手册,直到保留期结束或旧数据完成受控转换。
退出完成需要核对集群对象、后端快照、对象仓库、临时卷、CRD/finalizer、ServiceAccount、RoleBinding、Secret、KMS grant、云 IAM、日志索引和账单。删除 Helm release 只移除了部分协调组件;孤儿快照和未撤销凭证仍可能长期存活。
按缺失证据选择下一项工具
API 对象整体丢失且集群由 kubeadm 或自建 etcd 承载时,进入 Kubernetes 控制面 etcd 快照与恢复,重点处理工具版本、证书、成员身份、隔离恢复和 revision 回退。PVC 的存储级副本进入 CSI VolumeSnapshot 与卷恢复;需要跨存储、跨区域或独立仓库时进入文件系统备份与数据移动器;数据库、多卷应用和持续写入需要应用一致性与 Hook;恢复点必须抵御账号失陷或勒索删除时,再处理仓库隔离、不可变性和删除治理。
无论采用哪条路径,结束信号都相同:恢复点能在隔离目标被发现,依赖的身份和密钥可用,数据与业务断言通过,RPO/RTO 有测量值,清理动作不会误删仍需保留的副本。
