Kubernetes 基础体系 · 第 42/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 备份与 Velero:资源、卷、Hook、恢复顺序和演练
Kubernetes 备份不是“把 YAML 导出后重新执行一次”。一个可恢复的应用至少由以下几类状态组成:
- Kubernetes 资源状态:Namespace、Deployment、Service、ConfigMap、Secret、PVC、RBAC、CRD 及其自定义资源。
- 持久化数据:PVC 背后的卷内容。
- 应用内部一致性:数据库 WAL、事务、缓存、跨卷写入顺序。
- 集群外依赖:DNS、镜像仓库、云负载均衡、对象存储、外部数据库、密钥管理系统。
- 恢复时序:先创建什么,何时挂载卷,何时启动 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 创建;Endpoints或EndpointSlice通常由 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 资源。
如果控制面完全丢失,恢复过程通常分为两层:
- 先使用集群发行版、云平台或 etcd 工具恢复一个可用的 Kubernetes 控制面;
- 再用 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 快照能否工作,取决于:
- CSI 驱动实现快照能力;
- 安装了兼容版本的 external snapshotter;
- 存储后端支持快照;
VolumeSnapshotClass的驱动名称正确;- Velero 使用了能够识别并恢复该快照的能力;
- 目标集群能访问该快照所在的存储系统。
Kubernetes Snapshot API 只是接口标准,不保证任意 CSI 驱动都支持跨集群、跨区域或长期保留。
方式二:文件系统备份
文件系统备份通常由节点代理读取挂载后的文件,然后上传到对象存储。它的优势是:
- 不要求后端提供原生快照;
- 更容易跨不同存储类型恢复;
- 可以将数据恢复到另一个支持普通文件写入的卷。
代价包括:
- 需要在节点上读取数据;
- 备份和恢复速度依赖文件数量、网络和对象存储;
- 大量小文件可能很慢;
- 可能消耗节点 CPU、内存和网络;
- 需要处理文件权限、符号链接、稀疏文件、特殊文件和正在变化的文件;
- 不天然提供数据库事务一致性。
Velero 版本中用于文件系统备份的组件名称和安装参数曾经发生变化,不能机械套用旧版本中 restic 的命令。应以当前 Velero 版本的 Node Agent / 文件系统备份安装方式为准。
方式三:数据库原生备份
对于数据库,通常更可靠的边界是:
数据库原生备份 + WAL/日志
↓
对象存储或独立备份系统
↓
Velero 恢复 Kubernetes 资源
↓
Hook 或 Job 触发数据库恢复
例如 PostgreSQL 的基础备份和 WAL、MySQL 的全量备份与 binlog,能够表达数据库自己的恢复语义。Velero 只备份数据库 Pod、PVC 和配置,并不能理解“事务提交点”或“某个 WAL 位置”。
3. 快照的一致性边界
假设应用同时写入两个卷 和 ,快照时刻分别为 和 。如果应用在区间 继续写入,恢复后的状态可能满足:
这不一定是任何真实时刻的应用状态。因此,“每个卷都有快照”并不推出“多卷应用一致”。
要获得应用一致性,至少需要满足以下一种条件:
- 应用在所有相关卷快照期间停止写入;
- 应用先 flush 并冻结文件系统;
- 数据库提供协调备份接口;
- 所有卷使用支持一致性组的后端,并由实现保证组级原子性;
- 使用数据库原生备份和日志恢复语义。
如果只有单个卷且数据库能从 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 失败时让备份失败,而不是悄悄继续;postHook 必须能在preHook 失败、超时或备份失败时仍然完成解除操作,否则应用可能长期处于冻结状态。
实际可用的注解名称、支持的 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:恢复后如何让应用进入可用状态
恢复卷和恢复资源并不等于应用已经正确启动。数据库恢复通常需要:
- 先创建 PVC;
- 将数据恢复到卷;
- 数据库进程启动;
- 执行 crash recovery 或原生恢复;
- 确认数据库可接受连接;
- 才启动依赖服务。
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
注意其中两个异步点:
- Velero 创建了 PVC,不代表 PVC 已经
Bound; - PVC 已经
Bound,不代表数据已经正确恢复; - Pod 已经
Running,不代表应用已经完成数据库恢复; - 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
如果通过快照恢复,目标集群通常需要访问源快照所在的后端。跨区域、跨账号或跨云恢复往往需要先复制快照,或者改用文件系统备份/数据库原生备份。
八、一个端到端的备份与恢复示例
下面假设:
- 应用在
shopNamespace; - 需要备份 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 为
Running且Ready; - 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;
- 执行数据恢复;
- 检查数据库系统表和业务数据;
- 执行数据库迁移校验;
- 再恢复或扩容 Web、Worker 和定时任务;
- 最后切换 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 切换完成时间;
- 最终恢复时间;
- 实际数据时间点;
- 警告和人工操作。
恢复耗时可拆成:
其中每一项分别表示集群、基础设施、资源、卷、应用和流量切换耗时。只测 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、镜像仓库配置
恢复能力:定期隔离演练
最终的验收标准不是“备份对象存在”,而是:
如果任一项不成立,备份只能称为“保存了部分状态”,不能称为满足灾难恢复目标的备份。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:数据库运行在 Kubernetes:Operator、存储、拓扑、备份和责任边界
- 下一篇:Kubernetes 认证:证书、Token、OIDC、Webhook 和用户生命周期
- 延伸:Kubernetes VolumeSnapshot:一致性、Class、恢复、克隆和备份边界
- 延伸:Kubernetes 灾难恢复:控制面、资源、数据、DNS、依赖和演练
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论