Kubernetes 基础体系 · 第 41/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
数据库运行在 Kubernetes:Operator、存储、拓扑、备份和责任边界
数据库运行在 Kubernetes,并不是把数据库进程改成一个 Deployment 就完成了容器化。Kubernetes 能够管理 Pod、网络、卷和调度,但数据库还需要处理日志、选主、复制、故障恢复、版本升级、备份一致性和数据删除。这些职责之间如果没有明确边界,系统通常会出现一种危险状态:Pod 看起来是 Running,但数据库已经丢失仲裁、写入被阻塞,或者恢复出来的数据无法使用。
本文使用当前 Kubernetes 稳定 API 的概念说明这条边界。示例主要使用 apps/v1 的 StatefulSet、storage.k8s.io/v1 的 StorageClass、policy/v1 的 PodDisruptionBudget,以及 Kubernetes 的拓扑调度能力。Operator、CSI 快照和 Velero 的具体字段、插件行为会随版本和云厂商变化,涉及这些部分时会明确指出实现差异。
一、先建立正确的系统模型
一个运行在 Kubernetes 中的数据库至少包含以下状态:
- 身份状态:节点叫什么、是否仍然是原来的节点。
- 数据状态:表文件、索引、事务日志、控制文件等。
- 复制状态:主库、备库、同步位点、仲裁成员。
- 控制状态:期望运行几个实例、当前谁是主库、是否正在升级。
- 备份状态:某个备份对应的时间点、日志范围和恢复方式。
Kubernetes 原生对象主要描述第 1、4 类状态,并通过 Pod、PVC、Service 等对象间接承载其他状态。数据库自身负责第 2、3、5 类的语义。
因此,下面这种等式并不成立:
Pod 已恢复 + PVC 已挂载 = 数据库已恢复
更准确的关系是:
其中任一条件不成立,Kubernetes 的 Ready 也不能自动代表数据库可用。一个只执行 TCP 端口检查的探针,可能在数据库仍处于恢复、只读或未加入复制组时返回成功。
1. Kubernetes 管理什么,数据库管理什么
Kubernetes 原生控制器通常处理:
- Pod 是否存在;
- 容器是否重启;
- PVC 是否绑定;
- Service 是否拥有匹配的 Endpoint;
- 节点故障后的重新调度;
- 滚动更新和副本数。
数据库需要处理:
- WAL、redo log 或 binlog 的持久化;
- 崩溃恢复;
- 主备复制;
- 选主和脑裂防护;
- 复制延迟;
- 数据库版本升级;
- 逻辑备份、物理备份和时间点恢复;
- 数据库角色变化后的连接路由。
Operator 的价值,就是把后一组数据库知识编码为 Kubernetes 控制器,使它能够根据数据库状态创建或修改前一组 Kubernetes 对象。
二、Operator:不是“更强的 StatefulSet”
1. Operator 的组成
Operator 通常由以下部分构成:
- CRD:定义数据库集群的声明式 API,例如
PostgresCluster或MySQLCluster。 - 自定义资源(CR):用户提交的具体集群期望状态。
- Controller:监听 CR、Pod、PVC、Service 等事件,并持续执行调谐。
- 数据库管理逻辑:初始化、加入集群、选主、扩缩容、升级、备份和故障修复。
- RBAC 权限:允许 Operator 读写相关 Kubernetes 对象。
- Webhook 或校验逻辑:在 API 接受资源前检查参数。
Operator 的核心不是一组 YAML,而是一个持续运行的控制循环:
控制器读取用户希望的状态和当前观察到的状态,计算差异,然后执行动作。执行后再次读取状态,直到系统逐渐收敛。
例如,用户声明:
apiVersion: database.example.io/v1
kind: ExampleDatabaseCluster
metadata:
name: orders
spec:
instances: 3
storage:
size: 500Gi
backup:
enabled: true
这段资源不能直接提交到普通 Kubernetes 集群,除非名为 ExampleDatabaseCluster 的 CRD 已经安装。它只是说明 Operator API 的形状,不是 Kubernetes 内置 API,也不是所有数据库 Operator 通用的配置。
一个实现可能据此创建:
- 3 个带稳定身份的数据库 Pod;
- 3 个 PVC;
- 一个客户端 Service;
- 一个只读副本 Service;
- 复制用户和初始化配置;
- 备份 CronJob 或数据库原生备份任务;
- 监控和告警对象;
- 用于安全升级的 PDB。
2. 调谐不是一次性安装脚本
假设主库 Pod 被节点故障删除,调谐过程可能是:
- Kubernetes 根据 StatefulSet 或 Operator 逻辑重新创建 Pod。
- Pod 挂载原有 PVC。
- 数据库启动并执行崩溃恢复。
- Operator 发现该实例尚未成为可用副本。
- Operator 根据数据库状态让它追赶主库,或者重新初始化为备库。
- 只有复制状态满足条件后,Operator 才把它加入读流量或标记为可用。
如果 Operator 只观察 Pod 是否为 Running,就无法完成第 3 到第 6 步。因此生产环境应查看 Operator 的自定义资源状态、事件、数据库角色和复制位点,而不应只看:
kubectl get pods
常用的第一轮诊断是:
kubectl get events -n database --sort-by=.lastTimestamp
kubectl describe pod -n database orders-0
kubectl get pvc -n database
kubectl get <数据库CRD类型> -n database orders -o yaml
kubectl logs -n database deploy/<operator-deployment>
其中 <数据库CRD类型> 和 Operator Deployment 名称取决于具体实现,不能假设所有 Operator 都使用相同命名。
3. Operator 的边界
Operator 并不会自动解决所有问题:
- 它不能把单副本数据库变成跨可用区高可用数据库;
- 它不能让没有跨区域复制的集群抵抗区域级故障;
- 它不能证明快照具备事务一致性;
- 它不能替代备份恢复演练;
- 它不能消除错误删除 PVC 的风险;
- 它不能保证数据库客户端永远连接到正确角色,除非它同时维护了角色感知的 Service 或连接配置。
Operator 自身也是生产组件。它的升级、权限、故障恢复和兼容性需要单独管理。Operator 宕机通常不会立即停止已经运行的数据库,但会使故障修复、主从切换、扩缩容和升级停止,直到控制器恢复。
三、StatefulSet:稳定身份,不是数据库高可用
StatefulSet 是 Kubernetes 用来管理有状态 Pod 的工作负载 API。它提供的核心能力包括:
- 稳定、可预测的 Pod 名称;
- 稳定的网络身份;
- 通过
volumeClaimTemplates为每个 Pod 创建独立 PVC; - 可配置的创建和删除顺序;
- 滚动更新策略。
例如:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: demo-db
namespace: database
spec:
serviceName: demo-db
replicas: 3
selector:
matchLabels:
app: demo-db
template:
metadata:
labels:
app: demo-db
spec:
containers:
- name: db
image: example/database:1.0
ports:
- name: db
containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/database
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-rwo
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
name: demo-db
namespace: database
spec:
clusterIP: None
selector:
app: demo-db
ports:
- name: db
port: 5432
targetPort: db
serviceName 关联一个 Headless Service。对于合适的 DNS 配置,Pod 可以获得类似 demo-db-0.demo-db.database.svc 的稳定地址。demo-db-0 被删除后重新创建,名称仍然相同,PVC 通常也仍然对应它。
但是,StatefulSet 不理解“主库”“只读副本”“复制位点”这些数据库概念。它可以保证 demo-db-0 这个 Pod 身份稳定,却不会自动判断:
demo-db-0是否真的拥有最新数据;demo-db-1是否可以提升为主库;- 删除
demo-db-0会不会破坏法定人数; - 滚动更新是否会同时中断过多副本;
- 客户端应该连接哪个实例。
因此,直接用 StatefulSet 运行数据库更适合测试、单实例服务或由数据库团队自行实现完整控制逻辑的场景。生产集群通常由 Operator 生成或管理 StatefulSet,而不是把 StatefulSet 当作数据库 Operator。
创建、更新与删除的风险
StatefulSet 的有序行为是 Kubernetes 工作负载语义,不等于数据库安全语义。默认的 OrderedReady 会倾向于按序创建和更新 Pod,但“Pod Ready”必须由正确的探针定义。如果探针只检查进程存在,Kubernetes 可能在数据库尚未完成复制初始化时继续更新下一个实例。
删除 StatefulSet 也不必然删除 PVC。PVC 是否保留、PV 的回收行为和 Operator 的 finalizer 共同决定数据是否被删除。生产删除前应分别检查:
kubectl get statefulset,pvc,pv -n database
kubectl get storageclass fast-rwo -o yaml
不要仅因为 StatefulSet 已删除,就推断数据已经删除或一定安全保留。
四、存储:PVC 解决“挂载什么”,不解决“数据是否一致”
1. 从 Pod 到磁盘的对象链
数据库使用的持久化路径通常经过以下对象:
Pod
-> volumeMount
-> PVC
-> PV
-> StorageClass / CSI Driver
-> 云盘、网络存储或本地磁盘
- PVC 是工作负载对存储容量和访问模式的请求。
- PV 是实际可分配的存储资源。
- StorageClass 描述动态供应所使用的 CSI 驱动、参数和回收策略。
- CSI Driver 将 Kubernetes 的挂载请求翻译成云盘、SAN、分布式块存储等后端操作。
PVC 绑定成功只代表 Kubernetes 找到了满足请求的卷,不代表该卷具备数据库需要的 IOPS、延迟、吞吐或故障域属性。
常见访问模式包括:
ReadWriteOnce:通常表示可由单个节点读写;它不是“只能有一个 Pod”这一绝对语义。ReadOnlyMany:多个节点可只读挂载,是否支持取决于驱动。ReadWriteMany:多个节点可读写,常见于文件存储;并不自动适合数据库。ReadWriteOncePod:更严格地限制为单个 Pod 使用,支持情况取决于 CSI 驱动和集群版本。
ReadWriteMany 也不等于数据库共享磁盘集群。多个数据库实例同时写同一数据目录,除非数据库明确支持这种架构并使用相应的锁和一致性协议,否则可能导致文件损坏。
2. StorageClass 的延迟绑定与拓扑
一个重要字段是:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-rwo
provisioner: csi.example.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Retain
WaitForFirstConsumer 表示 PVC 不一定在创建时立即绑定,而是等待调度器已经知道 Pod 的节点约束后,再选择合适拓扑中的存储。对于具有可用区限制的云盘,这可以避免卷在 zone-a 创建,而 Pod 被调度到 zone-b。
但这不是跨区复制。单个云盘通常仍然属于一个故障域。要抵抗 zone-a 故障,需要数据库副本分别位于不同 zone,并且每个副本拥有自己的卷。
3. PVC 扩容不是数据库扩容
当 PVC 的容量请求从 100Gi 改为 200Gi 时,可能发生两个阶段:
- CSI 驱动扩展底层卷;
- 文件系统在线扩展或在重新挂载时扩展。
kubectl patch pvc data-demo-db-0 -n database \
--type merge \
-p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
kubectl get pvc data-demo-db-0 -n database -w
PVC 进入 FileSystemResizePending 等状态时,说明底层卷已经变化,但文件系统尚未完成扩展。即使文件系统已扩容,数据库的表空间、配额和内部存储管理也可能需要额外操作。容量扩大通常不能自动缩小;直接修改 PVC 使其变小往往不被支持,强行处理可能破坏数据。
4. 存储性能必须按数据库工作负载验证
数据库关心的不是“磁盘大小”一个数字,而是:
- 随机读写延迟;
- IOPS;
- 吞吐;
- fsync 或等价持久化语义;
- 突发性能耗尽后的行为;
- 节点或区域故障时的可访问性;
- 快照和克隆是否影响前台 I/O。
例如,事务提交通常要等待日志持久化。如果存储对 fsync 的实现只是缓存确认,而不是可靠落盘,数据库即使返回提交成功,也可能在节点或存储故障后丢失最近事务。这个问题不能通过 Kubernetes 的 ReadinessProbe 或 PVC 状态发现,需要数据库级别的故障测试和供应商存储语义验证。
五、拓扑:副本数量不等于故障域数量
1. 拓扑的三个层次
数据库拓扑至少要同时考虑:
- Pod 拓扑:Pod 位于哪个节点、可用区或区域。
- 卷拓扑:PVC 对应的 PV 能在哪些节点挂载。
- 数据库复制拓扑:哪些实例互为副本,谁能成为主库,复制是否同步。
只把 Pod 分散到不同节点而把所有数据放在同一个共享存储故障域中,并不能提供完整的故障隔离。
Kubernetes 常用标签包括:
kubernetes.io/hostname
topology.kubernetes.io/zone
topology.kubernetes.io/region
这些标签由集群或云平台提供,具体值和可靠性应在目标环境中验证。
2. 使用反亲和性和拓扑分布
下面的片段要求相同数据库集群的 Pod 尽量分散到不同 zone:
spec:
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: demo-db
topologyKey: topology.kubernetes.io/zone
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: demo-db
两者关注点不同:
podAntiAffinity可以表达“不能与同类 Pod 处于同一拓扑域”;topologySpreadConstraints表达跨拓扑域的数量平衡。
如果集群只有两个 zone,却要求三个副本严格分散,那么 DoNotSchedule 可能使第三个 Pod 长期处于 Pending。这是约束正确生效的表现,不是调度器故障。
可以检查原因:
kubectl describe pod demo-db-2 -n database
在事件中通常能看到不满足的反亲和性、拓扑或卷约束。
3. 仲裁与故障域的算例
假设数据库使用多数派提交,集群有 个投票成员,法定人数为:
当 时,。如果三个成员分别在 zone-a、zone-b、zone-c:
- zone-a 故障:剩余 2 个成员,仍满足 ;
- 任意两个 zone 同时故障:只剩 1 个成员,不满足仲裁;
- 三个 Pod 都在 zone-a:zone-a 故障时直接剩 0 个成员。
因此,“3 副本”只有结合故障域分布才有意义。还要注意非投票只读副本可能不计入仲裁;数据库 Operator 的 instances: 3 也未必表示三个投票成员,必须查看具体数据库语义。
4. PDB 不能防止所有中断
PodDisruptionBudget 用于限制自愿驱逐,例如节点排空。示例:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: demo-db
namespace: database
spec:
minAvailable: 2
selector:
matchLabels:
app: demo-db
它不保证:
- 云平台不会发生强制节点故障;
- 数据库一定可以在剩余副本上写入;
- 误删 Pod 不会发生;
- 版本升级一定安全;
- Operator 一定理解数据库角色。
PDB 还是一个调度和驱逐层约束,不能替代数据库的选主和同步复制机制。
六、备份:复制、快照和逻辑备份不是同一件事
1. 复制不是备份
复制通常追求降低故障恢复时间和提高可用性。备份则追求在误删、逻辑损坏、勒索、错误升级或全局配置错误后回到历史状态。
如果错误 SQL 被主库执行并同步到所有副本,复制会把错误传播到所有副本。这个场景中,复制不能替代备份。
2. RPO 和 RTO
- RPO(Recovery Point Objective):最多可以接受丢失多久的数据。
- RTO(Recovery Time Objective):从故障发生到服务恢复允许花费多久。
如果目标是 RPO 15 分钟,备份周期不能简单地设为 1 小时。对于时间点恢复,还需要连续归档事务日志。可以粗略表示为:
如果日志归档中断,即使完整备份任务显示成功,也可能无法恢复到期望时间点。
3. Kubernetes 资源备份与数据库数据备份
一个 Kubernetes 备份工具可能备份:
- Namespace、Service、Secret、ConfigMap;
- StatefulSet 或 Operator CR;
- PVC/PV 的引用关系;
- CSI VolumeSnapshot;
- Pod volume 中的文件。
这些对象备份并不自动等价于数据库一致性备份。恢复 Kubernetes YAML 后,数据库可能仍然缺少:
- 尚未刷盘的事务;
- 归档日志;
- 数据库系统表中的角色信息;
- 备份元数据;
- 加密密钥;
- 需要按顺序恢复的集群配置。
因此必须区分:
| 备份方式 | 主要内容 | 一致性来源 | 典型恢复用途 |
|---|---|---|---|
| 逻辑备份 | 表、模式、对象 | 数据库导出协议 | 跨版本、部分对象恢复 |
| 物理备份 | 数据目录或数据库原生备份格式 | 数据库备份工具与日志 | 快速整库恢复 |
| 日志归档 | WAL、redo、binlog 等 | 数据库日志机制 | 时间点恢复 |
| 存储快照 | 卷块或文件系统状态 | 存储系统 | 快速克隆或卷级恢复 |
| Kubernetes 资源备份 | API 对象 | Kubernetes API | 重建控制面资源 |
4. 一个可执行的逻辑备份例子
以 PostgreSQL 客户端为例,备份命令可以在具有数据库网络访问权限和对象存储上传权限的 Job 中执行:
pg_dump \
--host=orders-rw.database.svc \
--port=5432 \
--username=backup \
--format=custom \
--file=/backup/orders-$(date -u +%Y%m%dT%H%M%SZ).dump \
orders
前置条件包括:
orders-rw是数据库 Operator 或管理员维护的可写入口;backup用户拥有需要导出的对象权限;PGPASSWORD或 Secret 注入了凭据;/backup位于可靠的备份存储,而不是只挂载在当前 Pod 的临时目录;- 备份文件生成后被上传到集群外的对象存储。
custom 格式支持 pg_restore 选择对象恢复,但具体选项和锁行为仍受 PostgreSQL 版本影响。备份完成后必须验证退出码、文件大小、校验和,并在独立环境执行一次恢复,而不是只验证 Job 为 Completed。
对于 MySQL、MongoDB、Redis 等数据库,备份命令、快照语义和恢复步骤完全不同,不能把 pg_dump 替换成一个不同容器镜像后就认为方案成立。
5. Velero 的角色和限制
Velero 常用于备份 Kubernetes 资源,并结合 CSI 快照或文件系统数据移动能力保护卷。典型命令形式如下:
velero backup create orders-cluster \
--include-namespaces database \
--wait
velero backup describe orders-cluster --details
velero backup logs orders-cluster
这些命令的前提是 Velero 已安装、对象存储位置已配置,并且集群安装了与存储后端匹配的插件或 CSI 支持。不同 Velero 版本对 CSI 快照、数据移动和备份位置的选项不同,不能假定某个云厂商的参数适用于所有环境。
Velero 的资源备份通常包含 Kubernetes 对象;卷数据是否被保护取决于配置和能力:
- 使用 CSI VolumeSnapshot 时,需要可用的
VolumeSnapshotClass和兼容的 CSI 驱动; - 使用文件系统数据移动时,需要节点代理、权限和相应的数据移动组件;
- 只备份 PVC 对象而没有卷数据,恢复时可能得到一个空卷或无法绑定的引用;
- 只恢复卷而没有 Secret、Service 和 Operator CR,数据库可能无法按原拓扑启动。
CSI VolumeSnapshot 相关 API 由 Kubernetes CSI snapshotter 生态提供,并非所有能力都由 Kubernetes 核心 API 单独保证。其 snapshot.storage.k8s.io/v1 API、驱动支持和跨集群恢复能力必须在目标版本中核实。
6. 快照的一致性边界
在数据库仍持续写入时对卷做快照,可能得到崩溃一致状态,也可能依赖存储驱动的具体实现。即使数据库能通过 WAL 或 redo log 启动恢复,也不能因此推断:
- 所有事务都存在;
- 快照跨多个卷具有同一时间点;
- 外部对象存储中的文件与数据库状态一致;
- 备份满足业务的逻辑一致性要求。
应用一致快照通常需要:
- 调用数据库或 Operator 的备份接口;
- 暂停或协调写入;
- 刷新并固定日志;
- 创建相关卷快照;
- 记录备份元数据;
- 恢复后应用日志并验证数据库。
Velero Hook 可以在备份或恢复前后执行命令,但 Hook 只是执行机制,不会自动理解数据库事务。Hook 的注解格式、超时和失败处理应以所用 Velero 版本文档为准。若 Hook 失败却被忽略,备份任务可能看起来完成,实际却未达到一致性要求。
七、恢复不是“先恢复 Pod”
数据库恢复是一个有依赖顺序的状态转换过程:
flowchart TD
A[确认故障范围与恢复目标] --> B[准备目标集群与存储]
B --> C[恢复 Secret、ConfigMap、Service 和 Operator]
C --> D[恢复数据库 CR 或工作负载]
D --> E[恢复 PVC 或从快照创建卷]
E --> F[数据库实例启动并执行崩溃恢复]
F --> G[应用物理备份或逻辑备份]
G --> H[重放 WAL、redo 或 binlog]
H --> I[验证一致性、角色和复制状态]
I --> J[切换客户端入口并观察]
关键路径是:身份和凭据先于数据库启动,数据卷先于数据恢复,日志先于时间点确认,流量切换最后执行。
一个典型恢复错误是先恢复 Service,再让客户端连接到一个正在恢复的数据库。客户端可能执行写操作,导致恢复目标被污染。更安全的做法是:
- 恢复期间暂时不暴露写入口;
- 使用独立恢复 Namespace;
- 恢复后验证数据库版本、系统表、表数量、关键业务校验和;
- 确认角色是主库还是只读副本;
- 最后修改 DNS、Service selector 或连接配置。
如果使用 Velero 恢复资源,应检查:
velero restore create orders-restore \
--from-backup orders-cluster \
--wait
velero restore describe orders-restore --details
velero restore logs orders-restore
恢复后还要检查:
kubectl get pods,pvc,svc -n database
kubectl get events -n database --sort-by=.lastTimestamp
Completed 只表示 Velero 的恢复流程完成,不表示数据库业务恢复成功。业务验证必须包括实际连接、读写测试、复制状态、备份日志连续性和关键数据校验。
八、故障路径与诊断顺序
场景一:Pod 长期 Pending
可能原因包括:
- PVC 尚未绑定;
- PV 与 Pod 不在同一 zone;
- 节点无足够 CPU 或内存;
- 反亲和性要求无法满足;
ReadWriteOnce卷仍挂载在旧节点;- 节点缺少 CSI 驱动或挂载权限。
诊断顺序:
kubectl describe pod db-0 -n database
kubectl get pvc -n database
kubectl describe pvc data-db-0 -n database
kubectl get pv
kubectl get csidriver
不要先删除 PVC“重试”。如果根因是拓扑不匹配,删除 PVC 可能触发新卷创建;如果根因是旧节点仍持有卷,强行删除可能造成双挂载或数据损坏。
场景二:Pod Running 但数据库不可用
检查:
- Readiness 探针是否只检查端口;
- 数据库是否正在 crash recovery;
- 复制是否延迟或断开;
- 主库是否已经变更;
- Service selector 是否仍指向旧角色;
- 网络策略是否阻断了 Operator 或数据库节点间通信。
kubectl get endpointslice -n database -l kubernetes.io/service-name=orders-rw
kubectl describe pod orders-0 -n database
kubectl logs orders-0 -n database --previous
然后使用数据库原生客户端查询角色和复制状态。Kubernetes 只能告诉你端点和容器状态,不能替代数据库状态查询。
场景三:删除一个副本后集群停止写入
这通常表示数据库保护机制生效:剩余成员不满足同步提交或仲裁条件。需要确认:
- 故障的是非投票副本还是投票成员;
- 剩余成员是否位于同一故障域;
- 复制位点是否落后;
- Operator 是否在等待安全选主;
- 是否存在网络分区而不是单纯 Pod 删除。
贸然修改副本数、删除数据目录或强制提升某个实例,可能制造脑裂。应优先使用数据库 Operator 提供的故障处理流程,并保存事件、Operator 日志和数据库日志。
场景四:恢复后数据库启动但数据不完整
常见原因:
- 只恢复了 PVC 对象,没有恢复卷内容;
- 快照创建时没有数据库一致性保证;
- 缺少 WAL、redo 或 binlog;
- 恢复了错误的时间点;
- Secret 或加密密钥不匹配;
- 多个卷没有使用同一时间点;
- 数据库版本与备份格式不兼容。
应记录恢复点和日志范围,而不是只看 Pod 是否 Ready。对关键表执行行数、校验和、业务账务平衡或事件序列验证,才能判断恢复是否达到业务要求。
九、责任边界:谁对什么结果负责
责任边界应写成可验证的接口,而不是一句“平台负责,DBA 负责”。
| 领域 | Kubernetes/平台通常负责 | 数据库团队通常负责 |
|---|---|---|
| Pod 生命周期 | 调度、重启、节点替换 | 启动参数、优雅关闭、探针语义 |
| 身份与卷 | StatefulSet、PVC、CSI 挂载 | 数据目录布局、卷使用方式 |
| 拓扑 | 节点标签、调度约束、PDB | 副本角色、仲裁、同步策略 |
| 高可用 | 提供网络和故障域 | 选主、复制、脑裂防护 |
| 备份基础设施 | 对象存储、Velero、快照插件 | 一致性、日志归档、恢复点 |
| 安全 | RBAC、Secret 存储、网络策略 | 数据库账户、权限、加密配置 |
| 升级 | 镜像拉取、工作负载更新机制 | 版本兼容、迁移、回滚和数据格式 |
| 业务恢复 | 提供目标集群资源 | 数据完整性、业务校验和切流 |
云厂商还可能对块存储的多可用区能力、快照一致性、备份保留、跨区域复制和 IOPS 计费负责,但这些能力必须以具体产品文档和测试结果为准,不能由 Kubernetes API 名称推断。
十、生产设计中的几个取舍
1. Operator 还是自建 StatefulSet
选择 Operator 的成本包括 CRD、控制器、权限和升级依赖;收益是将数据库故障处理、复制和备份流程自动化。选择原生 StatefulSet 的成本则是团队必须自行实现这些数据库语义。
如果团队不能清楚回答“主库故障后谁提升、如何防止双主、PVC 如何重建、备份如何恢复”,直接使用 StatefulSet 通常只是把复杂度转移到了人工操作。
2. 单区低延迟还是跨区高可用
跨 zone 副本可以降低单区故障影响,但通常会引入更高网络延迟、复制延迟和存储成本。同步复制要求提交等待远端确认,可能降低写入性能;异步复制提高可用性或性能,却允许故障时丢失最近事务。
应先由业务定义可接受的 RPO/RTO,再选择同步或异步复制、跨区范围和备份频率,而不是先指定“必须三副本”。
3. 快照恢复速度还是逻辑迁移能力
卷快照往往适合同版本、同存储体系内的快速恢复;逻辑备份更适合跨版本、跨引擎或只恢复部分表,但通常恢复时间更长。实际方案常常同时保留:
- 定期完整物理备份;
- 连续日志归档;
- 必要的逻辑导出;
- Kubernetes 资源备份;
- 独立恢复环境。
这不是重复建设,而是分别应对硬件故障、逻辑错误、版本迁移和集群重建。
十一、上线前必须验证的闭环
数据库在 Kubernetes 上是否可靠,最终取决于故障和恢复演练,而不是 YAML 是否通过校验。至少应验证以下闭环:
- 删除一个数据库 Pod,确认实例能否重新挂载原 PVC 并完成恢复。
- 驱逐节点,确认 PDB、拓扑和数据库仲裁行为符合预期。
- 模拟一个 zone 不可用,确认剩余副本和客户端入口状态。
- 让备份任务失败,确认告警能够发现,而不是只看 CronJob 存在。
- 在隔离 Namespace 恢复一次完整备份和日志。
- 验证恢复后的数据库角色、关键数据和应用读写。
- 测试错误 SQL 后从历史备份恢复到指定时间点。
- 测试 Operator 停止一段时间后,已有数据库和后续调谐分别表现如何。
- 记录每一步耗时,得到真实 RTO,而不是文档中的估算值。
Kubernetes 为数据库提供了声明式资源、稳定身份、调度、存储和故障重建能力;Operator 将数据库控制逻辑接入这些能力;CSI 和 Velero 提供存储与资源保护的基础设施。但数据一致性、复制安全、备份有效性和恢复正确性仍然属于数据库系统本身。只有把这几层的状态和责任分开建模,数据库运行在 Kubernetes 才不会退化为“容器重启成功,却无法证明数据安全”。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 存储拓扑:Zone、Local PV、延迟绑定和故障恢复
- 下一篇:Kubernetes 备份与 Velero:资源、卷、Hook、恢复顺序和演练
- 延伸:StatefulSet:稳定身份、顺序、PVC、更新和分布式系统边界
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论