Kubernetes 基础体系 · 第 42/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes 备份与 Velero:资源、卷、Hook、恢复顺序和演练

Kubernetes 备份不是“把 YAML 导出后重新执行一次”。一个可恢复的应用至少由以下几类状态组成:

  1. Kubernetes 资源状态:Namespace、Deployment、Service、ConfigMap、Secret、PVC、RBAC、CRD 及其自定义资源。
  2. 持久化数据:PVC 背后的卷内容。
  3. 应用内部一致性:数据库 WAL、事务、缓存、跨卷写入顺序。
  4. 集群外依赖:DNS、镜像仓库、云负载均衡、对象存储、外部数据库、密钥管理系统。
  5. 恢复时序:先创建什么,何时挂载卷,何时启动 Pod,何时执行数据库恢复。

Velero 主要负责第 1 类、第 2 类中的部分能力,以及恢复编排。它不是 Kubernetes 控制面的完整灾备系统,也不会自动替代数据库原生备份。


一、先明确备份对象:资源、卷和集群本身

1. Kubernetes 资源不是一个整体文件

Kubernetes API 中的对象具有不同生命周期和依赖关系。例如一个典型应用可能包含:

Namespace
├── ServiceAccount
├── Role / RoleBinding
├── ConfigMap
├── Secret
├── Service
├── Deployment
│   └── Pod
└── PersistentVolumeClaim
    └── PersistentVolume
        └── 后端存储数据

其中有些对象是应用声明,有些对象是控制器生成的状态:

  • Deployment 通常是用户声明;
  • ReplicaSet 通常由 Deployment 创建;
  • Pod 通常由 ReplicaSet 创建;
  • EndpointsEndpointSlice 通常由 Service 控制器生成;
  • PersistentVolume 可能由动态供给器创建;
  • Node、控制器状态、调度结果通常不应作为普通应用资源直接恢复。

因此,备份时不能简单地把所有 kubectl get all 的结果保存下来。all 本身也不包含所有资源类型,例如 Secret、ConfigMap、PVC、RBAC 和自定义资源都可能遗漏。

2. Velero 备份的基本模型

Velero 通常由以下部分组成:

flowchart LR
    CLI[Velero CLI]
    V[Velero Server / Controller]
    API[Kubernetes API Server]
    O[对象存储<br/>Backup/Restore 元数据]
    P[Provider Plugin]
    CSI[CSI Snapshot API]
    FS[Node Agent / 文件系统备份]
    S[云存储或 CSI 后端]

    CLI --> API
    CLI --> V
    V --> API
    V --> O
    V --> P
    P --> S
    V --> CSI
    CSI --> S
    V --> FS
    FS --> O

一次备份通常包含:

  • Backup 自定义资源,用于描述请求;
  • 资源对象的序列化副本,通常存放在对象存储;
  • 与卷快照或文件系统备份相关的元数据;
  • 备份日志、状态和错误信息;
  • 可选的资源清单、统计信息和 Hook 执行结果。

Velero 通过 Kubernetes API 读取资源,而不是直接读取 etcd 文件。卷数据则由不同机制处理:

  • CSI VolumeSnapshot:让 CSI 驱动创建存储系统快照;
  • 云厂商卷快照插件:使用特定云厂商 API;
  • 文件系统备份:从节点上的挂载卷读取文件,并将文件数据上传到备份存储;
  • 应用原生备份:例如数据库将 WAL 或逻辑备份写入对象存储,再由 Velero 备份相关 Kubernetes 资源。

这几种方式的语义不同,不能仅因为都出现在一次 Velero Backup 中,就认为它们提供相同的一致性和恢复速度。


二、Velero 保存什么,不保存什么

1. 通常会保存的对象

Velero 可以备份绝大多数 Kubernetes API 资源,包括:

  • Namespace;
  • Deployment、StatefulSet、DaemonSet、Job、CronJob;
  • Service、Ingress、Gateway API 相关资源;
  • ConfigMap、Secret;
  • ServiceAccount、Role、RoleBinding、ClusterRole、ClusterRoleBinding;
  • PVC、PV 及存储相关元数据;
  • CRD 和自定义资源;
  • VolumeSnapshot、VolumeSnapshotContent 等快照资源,具体取决于安装方式和插件能力。

资源是否被纳入备份,取决于:

  • Velero 的资源选择器;
  • Namespace 过滤;
  • 标签选择器;
  • --include-cluster-resources 等选项;
  • 资源是否被排除;
  • 插件是否支持该资源;
  • 资源是否属于集群级别或命名空间级别。

2. 不应把控制面备份等同于资源备份

Velero 通常不等于 etcd 备份。它不会自动恢复:

  • API Server 的证书和启动参数;
  • Controller Manager、Scheduler 的配置;
  • etcd 集群本身;
  • 节点操作系统、内核、磁盘和网络配置;
  • CNI、CSI、Ingress Controller 的安装状态;
  • 云控制器配置;
  • 集群外的 DNS、负载均衡和 IAM 资源。

如果控制面完全丢失,恢复过程通常分为两层:

  1. 先使用集群发行版、云平台或 etcd 工具恢复一个可用的 Kubernetes 控制面;
  2. 再用 Velero 恢复 Kubernetes 资源和应用数据。

如果 etcd 仍然可用但部分命名空间被误删,Velero 的资源恢复通常更直接。两种灾难的恢复路径不能混为一谈。

3. Secret 会被备份,但这也是安全边界

Secret 是普通 Kubernetes API 资源。只要选择了它,Velero 通常会将其序列化并存储到备份位置。Kubernetes Secret 的 data 字段虽然是 Base64 编码,但不是加密。

生产环境至少需要考虑:

  • 对象存储服务端加密;
  • 传输加密;
  • 备份对象存储的访问控制;
  • 备份介质的租户隔离;
  • 密钥轮换;
  • 不同灾备环境是否共享同一 KMS;
  • 恢复操作者是否拥有读取所有 Secret 的权限;
  • 对象存储版本控制、保留策略和不可变策略。

“备份成功”并不表示“备份可以安全交给任何人读取”。


三、安装和存储位置:对象存储是备份的根

1. Velero 的备份位置

Velero 需要一个 BackupStorageLocation,它描述备份元数据和对象数据存放在哪里。不同云厂商通常需要不同的 provider plugin;S3 兼容对象存储也需要对应插件或兼容配置。

一个抽象配置示意如下:

velero backup-location get
velero plugin get
velero version

预期结果应至少能够确认:

  • Velero Client 与 Server 是否可通信;
  • Backup Storage Location 是否为 Available
  • 所需 provider plugin 是否已经安装;
  • CSI 或文件系统备份相关组件是否存在。

如果备份位置不是 Available,不要继续把“创建 Backup 对象成功”当作备份成功。Velero 的请求对象能创建,只代表 Kubernetes API 接受了请求,不代表数据已经上传完成。

2. 备份对象的状态

创建备份后:

velero backup create shop-prod \
  --include-namespaces shop \
  --wait

--wait 会让 CLI 等待操作结束。之后应检查:

velero backup get shop-prod
velero backup describe shop-prod --details
velero backup logs shop-prod

需要区分几种状态:

  • Completed:任务完成,但仍应检查 warnings;
  • PartiallyFailed:部分资源或卷处理失败;
  • Failed:备份整体失败;
  • InProgress:仍在进行;
  • New:任务尚未真正执行或正在排队。

“Completed 且有 warnings”不能直接视为满足灾备要求。必须判断 warning 涉及的是无关临时资源,还是关键 PVC、Secret、CRD 或数据库数据。


四、PVC、PV 和卷数据:资源备份不等于数据备份

1. PVC 只是声明,不是数据

PersistentVolumeClaim 描述应用请求的存储:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
  namespace: shop
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 100Gi

这个对象本身只包含:

  • 请求容量;
  • 访问模式;
  • StorageClass;
  • 标签和注解;
  • 绑定关系等元数据。

真正的数据位于 PVC 绑定的 PV 后端。仅恢复 PVC,不会凭空产生原来的数据库文件。

2. 三种常见卷保护方式

方式一:CSI VolumeSnapshot

Kubernetes VolumeSnapshot API 由外部快照控制器和 CSI 驱动实现。核心对象包括:

  • VolumeSnapshotClass:定义使用哪个 CSI 驱动和删除策略;
  • VolumeSnapshot:命名空间级别的快照请求;
  • VolumeSnapshotContent:集群级别的实际快照对象。

一个简化的快照对象如下:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: db-data-snap
  namespace: shop
spec:
  volumeSnapshotClassName: csi-fast-snapshot
  source:
    persistentVolumeClaimName: db-data

CSI 快照能否工作,取决于:

  1. CSI 驱动实现快照能力;
  2. 安装了兼容版本的 external snapshotter;
  3. 存储后端支持快照;
  4. VolumeSnapshotClass 的驱动名称正确;
  5. Velero 使用了能够识别并恢复该快照的能力;
  6. 目标集群能访问该快照所在的存储系统。

Kubernetes Snapshot API 只是接口标准,不保证任意 CSI 驱动都支持跨集群、跨区域或长期保留。

方式二:文件系统备份

文件系统备份通常由节点代理读取挂载后的文件,然后上传到对象存储。它的优势是:

  • 不要求后端提供原生快照;
  • 更容易跨不同存储类型恢复;
  • 可以将数据恢复到另一个支持普通文件写入的卷。

代价包括:

  • 需要在节点上读取数据;
  • 备份和恢复速度依赖文件数量、网络和对象存储;
  • 大量小文件可能很慢;
  • 可能消耗节点 CPU、内存和网络;
  • 需要处理文件权限、符号链接、稀疏文件、特殊文件和正在变化的文件;
  • 不天然提供数据库事务一致性。

Velero 版本中用于文件系统备份的组件名称和安装参数曾经发生变化,不能机械套用旧版本中 restic 的命令。应以当前 Velero 版本的 Node Agent / 文件系统备份安装方式为准。

方式三:数据库原生备份

对于数据库,通常更可靠的边界是:

数据库原生备份 + WAL/日志
        ↓
对象存储或独立备份系统
        ↓
Velero 恢复 Kubernetes 资源
        ↓
Hook 或 Job 触发数据库恢复

例如 PostgreSQL 的基础备份和 WAL、MySQL 的全量备份与 binlog,能够表达数据库自己的恢复语义。Velero 只备份数据库 Pod、PVC 和配置,并不能理解“事务提交点”或“某个 WAL 位置”。

3. 快照的一致性边界

假设应用同时写入两个卷 V1V_1V2V_2,快照时刻分别为 t1t_1t2t_2。如果应用在区间 [t1,t2][t_1,t_2] 继续写入,恢复后的状态可能满足:

Srestore=(V1(t1),V2(t2))S_{\text{restore}} = (V_1(t_1), V_2(t_2))

这不一定是任何真实时刻的应用状态。因此,“每个卷都有快照”并不推出“多卷应用一致”。

要获得应用一致性,至少需要满足以下一种条件:

  1. 应用在所有相关卷快照期间停止写入;
  2. 应用先 flush 并冻结文件系统;
  3. 数据库提供协调备份接口;
  4. 所有卷使用支持一致性组的后端,并由实现保证组级原子性;
  5. 使用数据库原生备份和日志恢复语义。

如果只有单个卷且数据库能从 crash-consistent 状态恢复,恢复后可能是可用的,但这属于数据库的崩溃恢复能力,不是 Velero 自动保证的事务一致性。


五、Backup Hook:在备份前后改变应用状态

1. Hook 的作用

Backup Hook 是 Velero 在备份某个 Pod 时,进入该 Pod 容器执行命令的机制。典型用途:

  • 备份前执行数据库 checkpoint;
  • 暂停写入;
  • 执行 fsfreeze
  • 备份后解除冻结;
  • 导出应用内部状态。

Hook 不是 Kubernetes 生命周期 Hook,也不是容器的 lifecycle.preStop。它由 Velero 备份流程触发,执行对象和失败语义不同。

一个示意性的 Pod 模板如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: shop
spec:
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
      annotations:
        pre.hook.backup.velero.io/command: '["/bin/sh", "-c", "psql -U postgres -c \"CHECKPOINT\""]'
        pre.hook.backup.velero.io/container: postgres
        pre.hook.backup.velero.io/timeout: 2m
        pre.hook.backup.velero.io/on-error: Fail
        post.hook.backup.velero.io/command: '["/bin/sh", "-c", "echo backup-finished"]'
        post.hook.backup.velero.io/container: postgres
spec:
  containers:
    - name: postgres
      image: postgres:16
      env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres
              key: password

这里的关键点是:

  • 命令必须存在于目标容器镜像中;
  • container 指定执行命令的容器;
  • JSON 数组格式避免 shell 解析歧义;
  • on-error: Fail 表示 Hook 失败时让备份失败,而不是悄悄继续;
  • post Hook 必须能在 pre Hook 失败、超时或备份失败时仍然完成解除操作,否则应用可能长期处于冻结状态。

实际可用的注解名称、支持的 Hook 类型和参数会随 Velero 版本变化,部署前应使用对应版本文档和 CLI 校验。不要把某个旧版本博客中的注解当成 Kubernetes 标准 API。

2. Hook 不能自动制造一致性

假设 Hook 执行顺序如下:

pre Hook
  ↓
卷快照或文件系统备份
  ↓
post Hook

只有在下面条件成立时,Hook 才能帮助形成一致备份:

  • pre 命令确实阻止了所有相关写入;
  • 所有相关 Pod 和卷都被覆盖;
  • Hook 成功状态被正确处理;
  • 备份机制实际等待 Hook 完成;
  • post 命令确实解除冻结;
  • 数据库或文件系统支持相应操作;
  • 没有绕过该 Pod 直接写入同一卷的其他进程。

例如只对主数据库 Pod 执行 checkpoint,但另一个 sidecar 仍在写同一卷,不能称为完整协调。又例如只冻结文件系统,却没有让数据库完成事务日志落盘,结果仍可能依赖数据库自身恢复。

3. Hook 的失败风险

Hook 命令常见失败表现:

velero backup describe shop-prod --details
velero backup logs shop-prod

可能看到:

  • 容器名称错误;
  • 命令不存在;
  • 权限不足;
  • Hook 超时;
  • Pod 在执行期间重启;
  • pre 成功但快照失败,post 没有按预期执行;
  • 只在某个副本执行,其他副本仍在写入。

生产 Hook 必须具备幂等性。比如解除冻结命令重复执行不能破坏状态;导出命令重复执行不能覆盖唯一备份;停止写入后即使备份失败,也应能安全恢复服务。


六、Restore Hook:恢复后如何让应用进入可用状态

恢复卷和恢复资源并不等于应用已经正确启动。数据库恢复通常需要:

  1. 先创建 PVC;
  2. 将数据恢复到卷;
  3. 数据库进程启动;
  4. 执行 crash recovery 或原生恢复;
  5. 确认数据库可接受连接;
  6. 才启动依赖服务。

Velero 提供 Restore Hook 能在恢复流程中执行命令或使用相应的初始化机制,但它不是通用工作流引擎。恢复 Hook 是否在 Pod 可执行、何时执行,以及失败是否阻止恢复,都应结合 Velero 版本验证。

更可控的方式通常是使用恢复阶段的 Job 或独立运维流程:

apiVersion: batch/v1
kind: Job
metadata:
  name: postgres-restore-check
  namespace: shop
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: check
          image: postgres:16
          command:
            - /bin/sh
            - -c
            - |
              pg_isready -h postgres -U postgres

这个 Job 的职责不是“假装数据已经恢复”,而是把恢复验证变成显式状态:

  • Job 成功:数据库端点能够响应;
  • Job 失败:不要继续暴露上层服务;
  • Job 日志:提供可诊断证据;
  • Job 可以扩展为校验表数量、迁移版本、应用版本和数据时间点。

如果使用 Restore Hook,必须确认:

  • 被恢复的 Pod 是否已经达到执行 Hook 的状态;
  • Hook 是否需要网络、Secret 和 Service;
  • 数据库是否处于恢复模式;
  • Hook 失败是否会被记录为 Restore warning 或 failure;
  • 恢复流程是否会因为 Deployment 自动拉起多个副本而产生并发访问。

七、恢复顺序:资源依赖和控制器并发

1. 为什么顺序重要

资源恢复不是任意排列。例如:

Namespace
  ↓
CRD
  ↓
自定义资源
  ↓
StorageClass / VolumeSnapshotClass
  ↓
PVC / PV / VolumeSnapshot
  ↓
ConfigMap / Secret / ServiceAccount
  ↓
Service
  ↓
Deployment / StatefulSet
  ↓
Pod

这只是概念依赖图,不是所有集群都严格使用这一条固定顺序。真实恢复还受到 Kubernetes 控制器异步调谐影响:

  • 创建 Deployment 后,ReplicaSet 可能立即创建 Pod;
  • 创建 PVC 后,动态供给器可能异步创建 PV;
  • 创建 Service 后,EndpointSlice 要等待 Pod Ready;
  • 创建 CRD 后,自定义资源才能被 API Server 接受;
  • 创建 StatefulSet 后,Pod 可能在卷尚未绑定时进入 Pending。

因此,恢复控制器只能尽量安排提交顺序,不能把整个集群变成同步事务。

2. Velero 的恢复过程

一次恢复大致经历:

sequenceDiagram
    participant U as 运维人员
    participant V as Velero
    participant A as API Server
    participant C as Kubernetes 控制器
    participant S as 存储后端
    participant P as 应用 Pod

    U->>V: 创建 Restore
    V->>A: 创建 Namespace/CRD/配置资源
    V->>S: 请求快照恢复或下载文件数据
    S-->>V: 卷恢复完成或进入异步状态
    V->>A: 创建 PVC/PV/相关快照引用
    A->>C: 触发调谐
    C->>S: 绑定并挂载卷
    C->>P: 创建应用 Pod
    P-->>C: Readiness / 状态更新
    V->>P: 执行 Restore Hook(若配置)
    V-->>U: Restore 状态和 warnings

注意其中两个异步点:

  1. Velero 创建了 PVC,不代表 PVC 已经 Bound
  2. PVC 已经 Bound,不代表数据已经正确恢复;
  3. Pod 已经 Running,不代表应用已经完成数据库恢复;
  4. Pod Ready,也不代表业务数据完整。

3. CRD 必须先于自定义资源

如果备份中包含 Operator 管理的资源,例如:

apiVersion: database.example.io/v1
kind: DatabaseCluster

恢复前必须确保对应的 CRD 已经存在。否则 API Server 会拒绝该对象,错误通常类似:

the server could not find the requested resource

恢复一个 Operator 应用时,通常先安装:

  • CRD;
  • Operator Deployment;
  • RBAC;
  • Webhook 和证书;
  • StorageClass 或快照能力;

再恢复该 Operator 的自定义资源。否则即使资源对象被恢复,控制器不存在,也不会产生实际数据库实例。

Velero 可以备份 CRD,但是否应该由同一次应用恢复负责,取决于集群治理方式。平台级 CRD 往往应由集群安装清单或 GitOps 管理,应用备份只负责恢复自定义资源。

4. StorageClass 不是数据本身

恢复 PVC 时,storageClassName 只表示“应使用哪种存储配置”。目标集群可能:

  • 没有同名 StorageClass;
  • 同名 StorageClass 的 CSI 驱动不同;
  • 参数不同;
  • 可用区不同;
  • 不能访问源集群快照;
  • 默认 StorageClass 发生变化。

因此,跨集群恢复时必须检查:

kubectl get storageclass
kubectl get volumesnapshotclass
kubectl get csidrivers
kubectl get csinodes

如果通过快照恢复,目标集群通常需要访问源快照所在的后端。跨区域、跨账号或跨云恢复往往需要先复制快照,或者改用文件系统备份/数据库原生备份。


八、一个端到端的备份与恢复示例

下面假设:

  • 应用在 shop Namespace;
  • 需要备份 Deployment、Service、ConfigMap、Secret、PVC;
  • Velero 已正确安装;
  • 对象存储位置可用;
  • CSI 驱动和快照能力已经验证;
  • 生产中已决定 Secret 的保护策略。

1. 创建带标签的应用

apiVersion: v1
kind: Namespace
metadata:
  name: shop
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
  namespace: shop
data:
  APP_MODE: production
---
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  namespace: shop
type: Opaque
stringData:
  POSTGRES_PASSWORD: change-me
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
  namespace: shop
  labels:
    backup-tier: critical
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: shop
  labels:
    app: web
    backup-tier: critical
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: example/web:1.0
          envFrom:
            - configMapRef:
                name: web-config
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: shop
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

应用后检查:

kubectl apply -f shop.yaml
kubectl -n shop get deploy,pod,svc,pvc
kubectl -n shop wait --for=condition=available deployment/web --timeout=120s
kubectl -n shop get pvc db-data

预期:

  • Deployment 为 Available
  • Pod 为 RunningReady
  • PVC 为 Bound
  • Service 有对应 EndpointSlice。

2. 选择备份范围

按 Namespace 备份:

velero backup create shop-prod \
  --include-namespaces shop \
  --wait

按标签选择关键资源:

velero backup create shop-critical \
  --include-namespaces shop \
  --selector backup-tier=critical \
  --wait

第二种命令只会选择带有该标签的对象。它可能不会包含应用依赖的 Service、Secret、ServiceAccount 或 ConfigMap,除非这些对象也带有标签。因此标签备份必须经过依赖审计,不能看到“关键 PVC 已被选中”就认为应用可恢复。

检查结果:

velero backup describe shop-prod --details
velero backup logs shop-prod

重点确认:

  • PVC 是否被纳入;
  • 卷是通过快照还是文件系统备份;
  • 是否有 PartiallyFailed
  • 是否有资源被排除;
  • Hook 是否成功;
  • Secret、CRD 和 RBAC 是否符合预期。

3. 模拟删除应用资源

测试环境中可以删除 Namespace:

kubectl delete namespace shop
kubectl get namespace shop

Namespace 删除是异步的。只有当它真正消失后,恢复结果才不会受到旧对象残留影响:

kubectl wait --for=delete namespace/shop --timeout=180s

生产事故中不要直接照搬删除 Namespace 的测试步骤。演练应使用隔离的恢复 Namespace 或独立目标集群,避免误删真实资源。

4. 创建恢复任务

恢复到原 Namespace:

velero restore create shop-restore \
  --from-backup shop-prod \
  --wait

恢复到另一个 Namespace:

velero restore create shop-restore-test \
  --from-backup shop-prod \
  --namespace-mappings shop:shop-restore \
  --wait

恢复到不同 Namespace 时,以下内容需要特别检查:

  • Secret 中是否写死原 Namespace;
  • Service DNS 名称是否被应用配置固定;
  • NetworkPolicy 是否引用原命名空间;
  • PVC 的 StorageClass 是否在目标集群存在;
  • 外部系统中的回调地址是否仍指向旧服务;
  • Ingress、Gateway 或云负载均衡是否会产生真实流量。

检查恢复:

velero restore get
velero restore describe shop-restore --details
velero restore logs shop-restore

kubectl -n shop get all,cm,secret,pvc
kubectl -n shop get events --sort-by=.lastTimestamp

对于 PVC:

kubectl -n shop get pvc
kubectl -n shop describe pvc db-data

对于 Pod:

kubectl -n shop get pod -o wide
kubectl -n shop describe pod <pod-name>

Pending 通常要从以下方向排查:

  • PVC 未绑定;
  • StorageClass 不存在;
  • CSI 驱动未运行;
  • 快照无法访问;
  • 节点没有合适拓扑;
  • 访问模式不满足;
  • 目标卷容量小于源卷;
  • 云平台配额不足。

九、恢复验证不能只看 Pod 是 Running

一个完整的恢复验证至少包含四层。

1. 资源层验证

kubectl -n shop get deploy,rs,pod,svc,pvc
kubectl -n shop get cm,secret,sa,role,rolebinding

验证对象是否存在、引用关系是否正确。特别检查:

  • Deployment 的镜像版本;
  • 环境变量来源;
  • Secret key 是否完整;
  • Service selector 是否匹配 Pod 标签;
  • PVC 是否绑定到了预期卷。

2. 存储层验证

不要只执行:

kubectl -n shop get pvc

PVC Bound 只表示声明与 PV 建立了绑定。还要在应用层检查数据:

kubectl -n shop exec deploy/postgres -- \
  psql -U postgres -c 'SELECT count(*) FROM orders;'

如果是文件数据,应检查:

  • 关键目录是否存在;
  • 文件大小和权限;
  • 最近一个已知文件的内容;
  • 应用能否创建新文件;
  • 应用能否读取恢复数据。

3. 应用层验证

对 Service 发起真实请求:

kubectl -n shop run curl \
  --rm -it --restart=Never \
  --image=curlimages/curl:8.10.1 \
  -- curl -fsS http://web/healthz

预期应得到成功响应,而不是只看到 Pod 为 Running

4. 业务层验证

业务验证需要检查数据语义,例如:

  • 订单数量是否在允许范围;
  • 最新订单时间是否符合备份时间点;
  • 数据库迁移版本是否正确;
  • 消息队列是否不会重复消费;
  • 应用是否能创建一笔测试订单并查询;
  • 外部回调是否被指向演练环境。

如果备份点是 10:00,而恢复后数据库只包含 09:58 的事务,这可能是备份机制的正常边界;如果业务要求恢复到 10:00,就必须使用数据库日志或更细粒度的备份方案。


十、恢复顺序的实际设计

1. 先恢复平台依赖,再恢复业务对象

一个更稳妥的顺序通常是:

目标集群
  ↓
网络插件、CSI、Ingress、DNS、监控基础设施
  ↓
StorageClass / VolumeSnapshotClass
  ↓
CRD
  ↓
Operator 和平台控制器
  ↓
Namespace、RBAC、Secret、ConfigMap
  ↓
PVC 和卷数据
  ↓
数据库 StatefulSet / Deployment
  ↓
数据库恢复与校验
  ↓
Service、Ingress、上层应用
  ↓
流量切换

Velero 可以帮助恢复其中很多 Kubernetes 对象,但不会替你安装目标集群的 CNI、CSI 或 DNS,也不会自动创建所有云平台外部资源。

2. 数据库应与无状态应用分阶段恢复

如果数据库和 Web 应用同时恢复,Deployment 可能在数据库尚未完成恢复时立即启动。此时可能出现:

  • Web 应用执行数据库迁移;
  • 多个副本同时连接恢复中的数据库;
  • 应用把“空数据库”当成初始化数据库;
  • 启动脚本覆盖恢复的数据;
  • Readiness 检查过于宽松,导致流量提前进入。

一种可控流程是:

  1. 先恢复数据库资源和卷;
  2. 将数据库副本数设置为 1;
  3. 执行数据恢复;
  4. 检查数据库系统表和业务数据;
  5. 执行数据库迁移校验;
  6. 再恢复或扩容 Web、Worker 和定时任务;
  7. 最后切换 DNS 或入口流量。

如果必须使用 Velero 恢复全部资源,可以在恢复后立即暂停上层控制器,或者事先设计恢复专用的副本数和启动开关。例如通过 ConfigMap 控制应用是否启动业务消费者,但这种机制必须由应用明确支持,不能只修改一个不会被应用读取的变量。


十一、备份调度、保留和恢复点目标

定时备份示意:

velero schedule create shop-hourly \
  --schedule="0 * * * *" \
  --include-namespaces shop \
  --ttl 168h

这里:

  • 0 * * * * 表示每小时整点;
  • --ttl 168h 表示让 Velero 在对象生命周期上保留约七天;
  • 实际删除是否及时,还受对象存储、插件和后端快照清理状态影响。

需要区分两个概念:

  • RPO(Recovery Point Objective):最多允许丢失多长时间的数据;
  • RTO(Recovery Time Objective):从故障开始到业务恢复需要多长时间。

如果 RPO 是 15 分钟,小时级 Velero 快照显然不满足要求。提高调度频率也不一定解决数据库一致性和带宽问题。常见组合是:

Kubernetes 资源:Velero 每小时
卷快照:按存储后端能力定期执行
数据库日志:持续归档
跨区域复制:按灾备等级执行

如果备份使用 TTL 自动清理,必须验证:

  • 备份元数据和卷快照是否一起清理;
  • 删除失败是否产生孤儿快照;
  • 对象存储生命周期规则是否早于 Velero TTL;
  • 不可变备份策略是否阻止了预期清理;
  • 备份保留时间是否覆盖调查、演练和合规要求。

十二、常见误解和对应故障表现

误解一:Completed 等于所有数据已备份

错误原因可能包括:

  • 某个非关键资源失败;
  • 关键 PVC 被跳过;
  • 快照创建成功但数据复制未完成;
  • Hook warning 被忽略;
  • 只备份了资源,没有卷数据。

诊断时要看:

velero backup describe <backup> --details
velero backup logs <backup>
kubectl get volumesnapshot -A
kubectl get volumesnapshotcontent

误解二:VolumeSnapshot 就是跨云备份

快照通常依赖源存储后端。删除源存储、失去云账号、跨区域恢复或切换云厂商时,快照可能不可用。

跨环境恢复前必须验证:

  • 快照复制能力;
  • 目标 CSI 驱动兼容性;
  • 快照所在区域和账号权限;
  • 加密密钥是否可用;
  • 快照能否创建新 PVC;
  • 目标后端是否支持相同容量和访问模式。

误解三:恢复 Deployment 就会恢复数据库

Deployment 只能创建容器。它不保证:

  • 原来的 PVC 数据存在;
  • 数据库配置和密码正确;
  • WAL 或 binlog 可用;
  • 数据库恢复到指定时间点;
  • 数据库与应用版本兼容。

如果数据库是核心状态,必须把数据库恢复流程作为独立的可验收步骤。

误解四:Pod Running 就表示服务恢复

Pod 可能处于 Running,但:

  • Readiness 为失败;
  • 容器正在反复重启;
  • 应用连不上数据库;
  • Service 没有 EndpointSlice;
  • Ingress 指向旧地址;
  • DNS 仍然指向故障环境;
  • 业务表为空或版本不兼容。

完整判断需要同时观察:

kubectl -n shop get pod
kubectl -n shop get endpointslice
kubectl -n shop get events --sort-by=.lastTimestamp
kubectl -n shop logs deploy/web

十三、灾备演练:把“有备份”变成“可恢复”

1. 演练至少覆盖三类场景

场景 A:误删 Namespace

目标是验证应用级恢复:

  • Namespace 被删除;
  • 集群控制面和 CSI 仍然可用;
  • 从最近备份恢复到原 Namespace 或测试 Namespace;
  • 验证资源、PVC、应用和业务数据。

场景 B:目标集群完全重建

目标是验证跨集群恢复:

  • 创建新的 Kubernetes 集群;
  • 安装 CNI、CSI、Ingress、Velero 和 provider plugin;
  • 配置相同或可访问的 Backup Storage Location;
  • 验证 CRD、StorageClass、快照后端和密钥;
  • 执行恢复;
  • 验证 DNS 和外部依赖。

场景 C:存储后端不可用

目标是验证备份边界:

  • 假设源快照不能访问;
  • 尝试用文件系统备份或数据库原生备份恢复;
  • 记录哪些资源可以恢复、哪些数据无法恢复;
  • 判断是否需要跨区域复制和第二种备份介质。

2. 演练记录应包含时间和证据

不要只记录“恢复成功”。至少测量:

  • 发现故障时间;
  • 开始恢复时间;
  • 控制面可用时间;
  • CRD 和基础设施可用时间;
  • PVC Bound 时间;
  • 数据库可连接时间;
  • 业务探针成功时间;
  • DNS 切换完成时间;
  • 最终恢复时间;
  • 实际数据时间点;
  • 警告和人工操作。

恢复耗时可拆成:

TRTO=Tcluster+Tinfra+Tresource+Tvolume+Tapp+TtrafficT_{\text{RTO}} = T_{\text{cluster}} + T_{\text{infra}} + T_{\text{resource}} + T_{\text{volume}} + T_{\text{app}} + T_{\text{traffic}}

其中每一项分别表示集群、基础设施、资源、卷、应用和流量切换耗时。只测 velero restore create --wait 的时间,无法代表业务 RTO。

3. 演练必须验证失败路径

至少故意测试:

  • 删除或禁用目标 StorageClass;
  • 使用不存在的 VolumeSnapshotClass;
  • 让数据库恢复 Hook 超时;
  • 让某个 Secret 缺少 key;
  • 让镜像仓库不可用;
  • 让目标集群缺少 CRD;
  • 让对象存储权限不足;
  • 恢复到同名 Namespace 和映射 Namespace;
  • 恢复过程中应用 Pod 被调度到不同拓扑区域。

通过这些故障可以判断团队是否能从以下信息定位问题:

velero restore describe <restore> --details
velero restore logs <restore>
kubectl get events -A --sort-by=.lastTimestamp
kubectl describe pvc -A
kubectl describe pod -A
kubectl get volumesnapshot -A

十四、生产取舍:不同备份方法的边界

方法 主要优点 主要边界
Kubernetes 资源备份 能恢复声明、配置和控制器 不包含卷数据和集群控制面
CSI 快照 通常恢复快,适合大卷 依赖后端、驱动、区域和快照兼容性
文件系统备份 后端依赖较少,跨存储更灵活 速度、资源消耗和一致性较弱
数据库原生备份 理解事务、WAL、binlog 和时间点恢复 需要单独设计、验证和维护
etcd 备份 可恢复控制面状态 不等同于应用卷数据备份,恢复边界更大
跨区域对象存储复制 可降低单区域损失风险 需要权限、加密密钥、费用和恢复演练

可靠的方案通常不是“只选 Velero”,而是明确分层:

集群控制面:etcd / 云平台控制面备份
Kubernetes 资源:Velero 或 GitOps 清单
卷数据:CSI 快照、文件系统备份或数据库原生备份
密钥:KMS / Secret 管理系统的灾备
外部依赖:DNS、负载均衡、IAM、镜像仓库配置
恢复能力:定期隔离演练

最终的验收标准不是“备份对象存在”,而是:

可恢复=资源可重建卷可挂载数据达到目标一致性依赖可连接业务验证通过\text{可恢复} = \text{资源可重建} \land \text{卷可挂载} \land \text{数据达到目标一致性} \land \text{依赖可连接} \land \text{业务验证通过}

如果任一项不成立,备份只能称为“保存了部分状态”,不能称为满足灾难恢复目标的备份。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。