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

Kubernetes 灾难恢复:控制面、资源、数据、DNS、依赖和演练

Kubernetes 灾难恢复不是“把 YAML 重新执行一遍”。一个运行中的集群同时包含控制面状态、工作负载资源、持久化数据、网络与 DNS、身份凭据,以及集群外部的数据库、镜像仓库和云服务。不同部分的备份方式、恢复顺序和一致性边界都不同。

如果只备份了 etcd,控制面对象可能可以恢复,但卷中的业务数据、外部 DNS 记录和云负载均衡器不一定存在;如果只备份了 PVC 对象,PVC 所指向的块存储数据仍可能已经丢失。因此,灾难恢复首先要回答三个问题:

  1. 恢复什么状态:控制面、资源对象、数据,还是整个业务系统?
  2. 恢复到哪个时间点:允许丢失多长时间的数据?
  3. 恢复后如何证明系统真的可用:API Server 能响应并不等于业务恢复。

一、先定义恢复目标:RPO、RTO 和一致性

**RPO(Recovery Point Objective,恢复点目标)**表示灾难发生后,业务最多能接受丢失多长时间的数据。例如 RPO 为 15 分钟,意味着恢复结果可以退回到灾难前不超过 15 分钟的状态。

**RTO(Recovery Time Objective,恢复时间目标)**表示从灾难发生到业务达到可用状态允许经过的最长时间。例如 RTO 为 60 分钟,意味着备份、控制面恢复、节点恢复、数据挂载、DNS 切换和验证必须在一小时内完成。

RPO 和备份周期之间存在直接关系,但不是简单的等式。设:

  • BB 是备份周期;
  • DD 是备份延迟;
  • FF 是备份系统或存储系统发生故障导致的额外缺口。

在理想情况下,最坏数据丢失量近似为:

RPOmaxB+D+FRPO_{\max} \approx B + D + F

例如每 15 分钟创建一次数据库备份,但备份需要 4 分钟才能上传到异地对象存储,那么灾难发生在下一次备份刚开始时,最坏丢失时间可能接近 19 分钟;若对象存储同步还可能延迟 10 分钟,则实际目标可能接近 29 分钟。

但是,Kubernetes 对象备份的 RPO 和业务数据的 RPO 不是同一个值

  • Deployment、Service、ConfigMap 等对象通常可以通过 Git、etcd 快照或 Velero 备份恢复;
  • 数据库事务、消息队列偏移量和对象存储对象需要各自的数据保护机制;
  • 一个较新的 Deployment 对象可能引用一个较旧的数据库备份,恢复后业务仍然可能不一致。

因此应定义恢复一致性集合:

S={K,R,D,N,X}S = \{K, R, D, N, X\}

其中:

  • KK:控制面状态,例如 etcd;
  • RR:Kubernetes 资源对象;
  • DD:业务数据;
  • NN:DNS、入口和网络状态;
  • XX:外部依赖,例如数据库、镜像仓库、身份系统和云资源。

一次真正可用的恢复必须让这些状态在时间和依赖关系上相容,而不是只恢复其中一个集合。


二、灾难恢复涉及哪些状态

Kubernetes 中“集群状态”至少有以下几层。

1. 控制面状态

控制面通常包含:

  • kube-apiserver:提供 Kubernetes API;
  • etcd:保存 Kubernetes 对象的权威持久化状态;
  • kube-scheduler:为待调度 Pod 选择节点;
  • kube-controller-manager:根据期望状态创建或修正实际状态;
  • 证书、加密配置、审计配置和控制面静态 Pod 配置。

其中,etcd 保存的是 Kubernetes API 对象,不是所有 Kubernetes 运行时数据。例如:

  • Pod 的期望配置在 etcd 中;
  • Pod 内的文件系统不在 etcd 中;
  • 容器镜像不在 etcd 中;
  • 节点上的 kubelet 本地目录不在 etcd 中;
  • CSI 卷中的数据库文件不在 etcd 中。

2. 资源对象状态

资源对象是 API Server 暴露的对象,例如:

  • Namespace;
  • Deployment、StatefulSet、DaemonSet、Job;
  • Service、Ingress、Gateway;
  • ConfigMap、Secret;
  • PVC、StorageClass;
  • ServiceAccount、Role、RoleBinding;
  • CRD 以及由 CRD 定义的自定义资源。

这些对象通常可以通过 API 重新创建,但重新创建不代表语义完全相同。对象中的 status、UID、resourceVersion、生成字段和控制器维护字段通常不应被直接当作用户配置恢复。

3. 数据状态

数据可能存在于多个位置:

  • PersistentVolume 对应的云盘、分布式存储或 NFS;
  • 数据库自身的 WAL、归档日志和备份;
  • 对象存储;
  • 消息队列;
  • 节点本地磁盘;
  • Pod 的 emptyDir 和容器可写层。

其中 emptyDir 和容器可写层一般属于临时数据。Pod 被删除或节点损坏后,它们通常不能恢复。若业务把重要数据写入这些位置,Kubernetes 资源备份无法弥补这个设计缺陷。

4. 集群外状态

Kubernetes 可能只保存了外部资源的引用:

  • ServiceLoadBalancer 状态由云控制器管理;
  • StorageClass 只是声明存储参数,不能替代存储系统中的数据;
  • Secret 可能包含访问外部数据库的凭据,但不包含数据库本身;
  • Ingress 或 Gateway 对象不一定包含云厂商负载均衡器的全部配置;
  • DNS 记录可能由外部 DNS 服务托管;
  • 镜像可能位于独立的镜像仓库。

灾难恢复计划必须明确哪些资源由 Kubernetes 管理,哪些资源由云平台、SaaS 或专用运维系统管理。


三、控制面恢复:etcd 是权威状态,但不是完整业务备份

3.1 etcd 快照保存什么

etcd 快照是某一时刻 etcd 键空间的持久化副本。对于使用 etcd 的 Kubernetes 集群,它通常包含 Kubernetes API 对象,因此可以恢复:

  • Namespace;
  • Deployment、Service、Secret、ConfigMap;
  • CRD 和自定义资源;
  • PVC 对象;
  • Lease 等控制器协调数据。

它不能恢复:

  • PV 后端卷中的业务数据;
  • 节点本地文件;
  • 容器镜像;
  • kubelet 的运行时缓存;
  • 外部 DNS 和数据库;
  • 未写入 etcd 的临时运行状态。

etcd 快照还与集群身份、加密配置和版本兼容性有关。生产环境不能只保留一个 snapshot.db 文件,而应同时保存:

  • etcd 版本或兼容矩阵;
  • Kubernetes 版本;
  • etcd 启动参数;
  • peer/client 证书;
  • --encryption-provider-config 对应的加密配置和密钥;
  • 控制面静态 Pod 清单;
  • 快照校验值;
  • 快照的创建时间和来源集群标识。

如果 Kubernetes Secret 使用了静态数据加密,恢复 etcd 数据但丢失加密配置,API Server 可能无法正确读取这些对象。反过来,只有加密配置而没有对应的密钥,也无法解密旧数据。

3.2 生产快照示例

在执行快照前,需要确认:

  • 使用的 etcdctl 与 etcd 版本兼容;
  • 客户端证书具有读取权限;
  • 磁盘空间足够;
  • 快照文件会被复制到独立故障域;
  • 备份过程不会只写入与 etcd 相同的磁盘或节点。

示例:

export ETCDCTL_API=3

etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
  --key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
  endpoint health

etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
  --key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
  snapshot save /var/backups/etcd-snapshot.db

etcdutl snapshot status /var/backups/etcd-snapshot.db
sha256sum /var/backups/etcd-snapshot.db

endpoint health 成功只能说明客户端能够访问当前端点,不代表快照已复制到异地,也不代表快照可以恢复。etcdutl snapshot status 用于检查快照元数据和状态;具体命令名称和参数会随 etcd 版本变化,必须以所使用版本的工具文档为准。较新的 etcd 版本推荐使用 etcdutl 执行快照恢复相关操作,不能假设所有发行版中的 etcdctl 都提供相同子命令。

3.3 etcd 恢复的核心步骤

假设原控制面已经损坏,恢复流程的关键不是“把快照复制回去”,而是建立一个新的、只有一个初始成员身份的 etcd 数据目录,然后让 API Server 连接它。

典型流程如下:

  1. 停止或隔离旧的 API Server、Controller Manager 和 Scheduler,避免旧控制面继续写入;
  2. 准备与目标 etcd 版本兼容的工具;
  3. 从异地存储下载快照;
  4. 校验 SHA-256 或对象存储校验信息;
  5. 将快照恢复到新的数据目录;
  6. 使用恢复后的 etcd 启动参数启动 etcd;
  7. 启动 API Server;
  8. 检查 API 对象、控制器、节点和工作负载;
  9. 恢复数据卷并进行应用级验证。

单成员恢复示例:

etcdutl snapshot restore /var/backups/etcd-snapshot.db \
  --data-dir=/var/lib/etcd-restored \
  --name=restored-etcd \
  --initial-cluster=restored-etcd=https://127.0.0.1:2380 \
  --initial-advertise-peer-urls=https://127.0.0.1:2380

这段命令的含义是:

  • 从快照生成一个新的 etcd 数据目录;
  • 为恢复后的成员指定新的成员名;
  • 用新的初始集群配置启动一个单成员集群;
  • 使用 --data-dir 指定恢复数据的位置,而不是覆盖原数据目录。

实际参数必须与 etcd 启动参数一致,尤其是:

  • --listen-client-urls
  • --advertise-client-urls
  • --listen-peer-urls
  • --initial-advertise-peer-urls
  • TLS 证书和密钥;
  • 认证、自动压缩和存储路径配置。

不能把上述示例直接用于三成员生产集群而不修改。多成员恢复需要明确每个成员的新地址和初始集群配置;如果只恢复一个成员,原有其他成员仍然运行,可能产生错误的成员关系或脑裂风险。恢复期间必须隔离旧集群,禁止两个控制面同时对外提供写服务。

3.4 为什么恢复后要关注 revision

etcd 中的 revision 是键值空间的单调递增版本号。Kubernetes 控制器和 informer 会根据 watch 机制观察对象变化。恢复快照后,etcd 的逻辑历史可能回到了较旧的 revision。

考虑如下时间线:

  1. 在 revision 100 创建了 Deployment;
  2. 在 revision 105 更新了 Deployment;
  3. informer 已经观察到了 revision 105;
  4. 灾难后从 revision 100 的快照恢复;
  5. API Server 重新使用原来的对象 UID 和旧资源历史。

此时,部分客户端可能持有“已经看到 revision 105”的缓存,而恢复后的 etcd 只知道 revision 100。若恢复实现没有正确推进或隔离旧的历史,客户端可能出现 watch 错误、缓存不一致或控制器无法按预期重新同步。

因此,恢复 etcd 时必须使用该版本支持的 revision bump 或等效机制,使恢复后的逻辑 revision 高于灾难前客户端可能观察到的 revision,并通常配合 compaction 清理恢复前的历史。具体参数和行为与 etcd 版本有关,不能凭空套用某个版本的命令。

这里的原则是:

  • 物理快照时间点决定对象内容;
  • 恢复后的逻辑 revision决定客户端如何识别这是一段新的历史;
  • revision bump 不能恢复快照之后产生的业务数据;
  • revision bump 也不能代替控制器、应用和卷数据的恢复。

3.5 证书和加密配置是控制面恢复的一部分

控制面常见的恢复失败表现包括:

  • kube-apiserver 无法连接 etcd;
  • TLS 报 certificate signed by unknown authority
  • etcd 端报客户端证书无权限;
  • Secret 读取时报解密错误;
  • kubelet 因集群 CA 或客户端证书变化无法重新注册;
  • Controller Manager 使用过期或错误的 service-account signing key。

诊断时应按数据流检查,而不是只看 kubectl get nodes

kubectl
  -> kube-apiserver
      -> etcd
      -> authentication / authorization
      -> admission webhooks
      -> controller and scheduler
          -> kubelet
              -> container runtime
                  -> CNI / CSI / image registry

先检查 API Server 到 etcd 的 TLS 和健康状态,再检查 API Server 的认证、授权和准入配置,最后检查 kubelet、CNI、CSI 和业务应用。若 API Server 根本无法启动,kubectl 的错误通常只是表象,真正原因应查看控制面节点上的静态 Pod 日志或 systemd 日志。


四、资源恢复:对象有依赖顺序,不是任意 YAML 的集合

4.1 为什么 kubectl get -A -o yaml 不是完整备份

下面的命令可以用于临时检查或导出部分对象:

kubectl get deploy,sts,ds,svc,ingress,configmap,secret,pvc \
  --all-namespaces -o yaml > resources.yaml

但它不适合作为完整生产备份,原因包括:

  • 资源类型不完整,可能遗漏 CRD、自定义资源、Webhook、RBAC 和存储对象;
  • statusresourceVersion、UID 等字段不适合原样恢复;
  • 列表输出不能天然表达 CRD 必须先于自定义资源创建;
  • Secret 只是当前时刻的凭据,可能已在外部系统轮换;
  • PV 的对象和后端卷数据是两个不同层面;
  • 某些对象由云控制器、Operator 或其他控制器生成,手工恢复可能与控制器冲突;
  • 使用 --all-namespaces 不能导出集群级资源,因为集群级资源没有 namespace。

更可靠的做法是同时保留:

  1. 以 Git 管理的声明式配置;
  2. etcd 快照;
  3. 资源级备份工具产生的对象备份;
  4. 数据存储自己的备份;
  5. 云资源和 DNS 的独立导出。

4.2 资源恢复的依赖图

资源恢复通常遵循以下依赖关系:

flowchart TD
    A[恢复控制面和 API Server] --> B[Namespace]
    B --> C[CRD]
    C --> D[Operator 和 Webhook]
    B --> E[ServiceAccount 与 RBAC]
    B --> F[ConfigMap 与 Secret]
    B --> G[StorageClass]
    G --> H[PV/PVC]
    F --> I[Deployment/StatefulSet/DaemonSet]
    H --> I
    E --> I
    I --> J[Service]
    J --> K[Ingress/Gateway]
    K --> L[外部 DNS 与负载均衡]

这不是所有集群都必须严格遵循的唯一顺序,但它表达了重要因果关系:

  • 没有 CRD,自定义资源无法创建;
  • 没有 Operator,自定义资源可能长期停留在未就绪状态;
  • 没有 Secret、ServiceAccount 或 RBAC,Pod 可能启动但无法访问依赖;
  • 没有 PVC 或后端数据,StatefulSet 可能创建成功但应用无法工作;
  • 没有 Service,Ingress 的后端没有可达目标;
  • 没有外部 DNS,用户即使看到入口地址也无法通过域名访问。

恢复后不要只检查对象是否存在,应检查 status.conditions、EndpointSlice、Pod 就绪状态、卷挂载状态和应用健康检查。

4.3 Velero 资源和卷备份的边界

Velero 可以对 Kubernetes 资源进行备份和恢复,并可结合 CSI 快照或数据移动能力处理卷数据;具体能力依赖 Velero 版本、插件、CSI 驱动、云厂商和存储后端,不能把“启用了 Velero”理解为所有卷都自动具备异地可恢复性。

一个典型的资源备份命令如下:

velero backup create app-backup \
  --include-namespaces=orders \
  --snapshot-volumes \
  --wait

velero backup describe app-backup --details
velero backup logs app-backup

前置条件包括:

  • Velero Server 已安装并能访问对象存储;
  • 对应云平台或 CSI 插件已正确配置;
  • StorageClass 和 CSI 驱动支持所使用的快照能力;
  • 对象存储凭据本身也被安全保管;
  • 快照或数据移动结果确实位于灾难域之外。

恢复时:

velero restore create --from-backup=app-backup --wait
velero restore describe <restore-name> --details
velero restore logs <restore-name>

<restore-name> 是 Velero 实际返回的恢复对象名称,应使用命令输出中的名称,而不是假设它固定。恢复结果应重点查看:

  • Restore 是否完成;
  • 哪些资源被跳过;
  • 哪些资源发生冲突;
  • PVC 是否绑定到正确的 PV;
  • 快照是否能在目标集群中还原;
  • 自定义资源是否在 CRD 之后恢复;
  • Webhook 是否在 API 请求阶段阻塞恢复。

资源恢复和卷恢复的顺序也可能不同于创建顺序。例如,PVC 对象可以先恢复,但后端快照尚未完成时,Pod 仍然无法挂载卷。对于数据库,卷快照还可能不是应用一致性快照。

4.4 Hook 解决的是应用一致性,不是魔法

数据库通常需要:

  1. 暂停写入或进入备份模式;
  2. 刷新 WAL、日志或事务;
  3. 创建数据库原生备份或一致性快照;
  4. 备份完成后恢复写入。

Velero Hook 可以在备份或恢复前后执行容器命令。例如,资源可以带有类似以下注解:

metadata:
  annotations:
    pre.hook.backup.velero.io/command: '["/bin/sh", "-c", "psql -U postgres -c \"SELECT pg_start_backup(''velero'')\""]'
    post.hook.backup.velero.io/command: '["/bin/sh", "-c", "psql -U postgres -c \"SELECT pg_stop_backup()\""]'
    pre.hook.backup.velero.io/timeout: 10m

这个示例仅用于说明 Hook 机制,实际 PostgreSQL 备份命令、权限、版本和双引号转义必须按数据库部署方式调整。Hook 的失败处理、超时和重试策略必须经过演练;如果备份命令失败却仍继续创建卷快照,得到的可能是不可恢复或逻辑不一致的数据。

更稳妥的数据库恢复往往是:

  • 先恢复数据库服务;
  • 用数据库原生备份恢复数据;
  • 重放 WAL 或增量日志;
  • 通过数据库自身的健康检查确认一致性;
  • 最后启动依赖数据库的 Kubernetes 工作负载。

五、数据恢复:PVC、PV 和卷内数据必须分开理解

5.1 PVC 不是数据

PersistentVolumeClaim 是 Kubernetes 中的请求对象,通常包含:

  • 存储容量;
  • 访问模式;
  • StorageClass;
  • 标签选择器;
  • 数据源或快照引用。

它不等于卷内数据。即使 PVC 对象恢复成功,以下情况仍可能导致数据不可用:

  • 云盘快照不在目标区域;
  • 目标集群没有相同的 CSI 驱动;
  • StorageClass 参数在新集群中不存在;
  • 卷拓扑限制导致节点无法挂载;
  • 数据库文件系统损坏;
  • 快照发生在未完成事务的时刻;
  • 原卷使用了目标集群无法识别的加密密钥。

5.2 文件系统快照与应用一致性

块存储快照通常保证存储系统层面的崩溃一致性,但“崩溃一致性”不一定等同于“应用一致性”。

例如数据库在写入数据页和 WAL 时发生快照:

  • 如果 WAL 和数据页都能按数据库规则重放,数据库可能可以恢复;
  • 如果快照跨越多个卷,而不同卷的快照时间不一致,跨卷事务可能无法重建;
  • 如果应用有缓存或异步写入,存储完成不代表业务逻辑已经提交。

因此,关键数据库应优先使用:

  • 数据库原生全量备份;
  • 增量备份或 WAL 归档;
  • 事务一致性快照;
  • 经过验证的恢复时间点。

卷快照适合缩短恢复时间,但不应自动替代数据库备份。

5.3 恢复卷时的安全顺序

对 StatefulSet 或数据库工作负载,常见安全顺序是:

  1. 恢复或创建目标 Namespace;
  2. 恢复 StorageClass、CSI 配置和密钥;
  3. 恢复 PVC 与后端卷;
  4. 确认 PVC 为 Bound
  5. 确认卷可被目标节点挂载;
  6. 以单实例或维护模式启动数据库;
  7. 执行数据库一致性检查和日志重放;
  8. 验证数据;
  9. 再启动应用副本和流量入口。

可以使用以下命令观察卷状态:

kubectl get pvc -n orders
kubectl get pv
kubectl describe pvc database-data -n orders
kubectl get volumeattachments.storage.k8s.io

如果 PVC 是 Bound 但 Pod 仍处于 ContainerCreating,应查看 Pod 事件和 CSI 控制器日志;Bound 只说明 Kubernetes 已完成对象级绑定,不代表底层卷挂载成功。


六、DNS、Service 和入口:恢复了对象不等于用户能访问

6.1 集群内部 DNS

Kubernetes 集群内部 DNS 通常由 CoreDNS 提供。Service 的 DNS 名称通常遵循:

<service>.<namespace>.svc.<cluster-domain>

例如:

orders-api.orders.svc.cluster.local

实际集群域不一定是 cluster.local,应以 kubelet、CoreDNS 和集群配置为准。

恢复后,内部 DNS 可能失败的原因包括:

  • CoreDNS Deployment 或 ConfigMap 未恢复;
  • CoreDNS Pod 未调度或无法访问上游;
  • Service 存在但没有 EndpointSlice;
  • 应用仍绑定旧的 Service 名称;
  • NetworkPolicy 阻止 Pod 访问 DNS;
  • kubelet 使用的集群 DNS 地址发生变化;
  • 恢复时自定义 DNS 插件或 NodeLocal DNSCache 未部署。

诊断示例:

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get svc -n kube-system kube-dns
kubectl get endpointslice -n orders -l kubernetes.io/service-name=orders-api
kubectl run dns-test --rm -it --restart=Never \
  --image=busybox:1.36 -- nslookup orders-api.orders.svc.cluster.local

nslookup 成功只能证明 DNS 查询路径可用;还需要确认 Service 端口、Endpoint 地址、网络策略和应用协议都正确。

6.2 外部 DNS 和入口

外部访问通常经过:

客户端
  -> 公网 DNS
  -> Ingress / Gateway / LoadBalancer
  -> Service
  -> Pod
  -> 外部数据库或其他依赖

恢复 Kubernetes 对象后,外部 DNS 仍可能指向:

  • 已销毁的公网负载均衡器;
  • 旧集群的 IP;
  • 已过期的临时地址;
  • 错误的 CNAME;
  • 受 DNSSEC、证书或 CDN 配置影响的旧入口。

DNS 切换时间受 TTL、递归解析器缓存和客户端缓存影响。即使权威 DNS 已修改,仍可能有一段时间的流量到达旧集群。因此应采用以下任一策略:

  • 预先使用稳定的全局入口,再把后端切换到新集群;
  • 保留旧入口直到缓存过期;
  • 使用低 TTL 的灾备域名;
  • 通过流量管理系统按权重或健康状态切换。

低 TTL 会增加 DNS 查询量,但不能保证所有客户端立即刷新;DNS 不是强一致的即时切换机制。

6.3 Service IP 和域名稳定性

如果通过重建集群恢复资源,Service 的 clusterIP 可能变化。集群内部不应把 ClusterIP 写死在应用配置中,应使用 Service DNS 名称。

对外部 DNS 而言,应尽量让记录指向稳定的入口,而不是直接指向某个 Pod IP。Pod IP、节点 IP 和临时 LoadBalancer IP 都可能在恢复后改变。

Ingress TLS Secret、Gateway 证书引用和外部证书自动签发也必须验证。若 cert-manager、ACME DNS-01 所需的 DNS 凭据或外部 DNS API 无法访问,入口对象可能存在,但 HTTPS 仍不能恢复。


七、外部依赖:Kubernetes 恢复后最容易被忽略的故障路径

工作负载能启动,不表示业务已恢复。以下依赖必须单独纳入恢复设计。

1. 镜像仓库

恢复节点需要拉取镜像。如果私有镜像仓库不可用,Pod 会出现:

ErrImagePull
ImagePullBackOff

需要保留:

  • 镜像仓库地址;
  • 镜像和标签或摘要;
  • imagePullSecrets
  • 仓库 CA 证书;
  • 跨区域或离线镜像复制能力。

生产环境应优先使用不可变镜像摘要,而不是只依赖可被覆盖的 latest 标签。

2. CNI 和网络

没有 CNI,Pod 可能长期停留在 PendingContainerCreating 或网络未就绪状态。恢复 CNI 时要同时确认:

  • CNI DaemonSet;
  • 节点内核模块和 sysctl;
  • Pod CIDR 与 Service CIDR;
  • 云安全组、路由和网络 ACL;
  • NetworkPolicy 控制器;
  • 加密隧道或跨节点组件。

不能因为 API Server 能访问,就认为业务网络已经恢复。

3. CSI 和存储

CSI 恢复需要:

  • CSI Controller;
  • CSI Node 插件;
  • 云平台或存储系统凭据;
  • KMS 密钥;
  • StorageClass;
  • 拓扑、区域和节点权限;
  • 快照或数据移动组件。

CSI 插件本身常常依赖云 API。如果目标区域无法访问云 API,PVC 对象恢复后仍然无法挂载。

4. 身份认证与权限

API Server 的认证可能依赖 OIDC、LDAP、Webhook 或云 IAM。控制面恢复后,如果身份系统不可用,管理员可能无法登录;如果 ServiceAccount token signing key 发生变化,应用也可能无法调用 API。

应保留并验证:

  • OIDC issuer 和 CA;
  • API Server 的认证配置;
  • ServiceAccount signing key;
  • RBAC 对象;
  • 外部身份系统的管理员路径;
  • 灾备环境的最小权限凭据。

5. 数据库、消息队列和云服务

应用配置中的连接字符串、Secret 和 Service 不等于外部服务已经恢复。必须验证:

  • DNS 能解析外部服务;
  • 网络路由和防火墙允许连接;
  • TLS 证书链正确;
  • 数据库用户、权限和 schema 存在;
  • 消息队列的 topic、分区和消费位点正确;
  • 云 API 凭据仍有效;
  • 外部限流、配额和回调地址已切换。

灾难恢复中的一个典型失败是“应用 Pod 全部 Ready,但所有请求返回 500”,原因是数据库恢复到了不匹配的时间点,或应用使用了新版本 schema 连接旧数据库。


八、恢复流程:从控制面到业务验证

一次完整恢复可以分为六个阶段。

阶段一:隔离故障域

先确认旧集群是否仍可能接收写入。若旧集群和新集群同时运行:

  • 两个集群可能同时消费消息;
  • 两边可能同时写入数据库;
  • 外部 DNS 可能把流量分散到两个版本;
  • 控制器可能对同一外部资源产生竞争。

因此需要通过网络隔离、入口切换、数据库只读或租约机制,明确哪一个集群是恢复后的主集群。

阶段二:恢复控制面

恢复 etcd、控制面配置、证书和加密密钥,启动 API Server,并检查:

kubectl get --raw='/readyz?verbose'
kubectl get nodes
kubectl get namespaces
kubectl get crd

readyz 成功表示 API Server 的就绪检查通过,但不表示所有节点、存储和业务已经正常。

阶段三:恢复基础设施资源

按依赖顺序恢复:

  1. Namespace;
  2. CRD;
  3. CNI、CSI、CoreDNS;
  4. Operator 和 admission webhook;
  5. RBAC、ServiceAccount、ConfigMap、Secret;
  6. StorageClass、PV、PVC;
  7. 工作负载;
  8. Service、Ingress、Gateway。

恢复 Webhook 时尤其要小心。一个已恢复但后端不可达的 ValidatingWebhookConfigurationMutatingWebhookConfiguration,可能使后续所有资源创建请求都超时或失败。恢复前应确认 webhook 的服务、证书和网络路径可用,必要时根据组织的应急流程暂时调整其失败策略;这会降低校验保护,必须有明确的恢复后复原步骤。

阶段四:恢复数据

对每个有状态应用记录:

  • 数据备份来源;
  • 备份时间;
  • 恢复点;
  • 需要的密钥;
  • 恢复命令;
  • 数据一致性检查;
  • 是否允许只读启动;
  • 是否需要 WAL 或增量日志重放。

恢复数据库后先执行应用级查询,而不是立刻开放全部流量。例如检查订单数量、最近一笔交易、关键表约束和消息积压。数据库进程“正常运行”只说明进程存活,不说明业务数据正确。

阶段五:恢复流量入口

先在内部用固定测试请求验证:

kubectl run curl-test --rm -it --restart=Never \
  --image=curlimages/curl:8.10.1 -- \
  curl -fsS http://orders-api.orders.svc.cluster.local:8080/healthz

再检查:

  • Service 是否有正确的 EndpointSlice;
  • Ingress 或 Gateway 是否获得地址;
  • TLS 证书是否匹配域名;
  • 外部 DNS 是否指向新入口;
  • 负载均衡器健康检查是否通过;
  • 从真实网络位置访问是否成功。

最后才进行 DNS 或流量管理切换。

阶段六:业务验收和解冻写入

恢复验收不应只使用 Kubernetes 健康状态,而应包括:

  • 登录和权限;
  • 创建、查询和更新一条测试业务数据;
  • 异步消息生产与消费;
  • 文件上传和下载;
  • 数据库事务;
  • 定时任务;
  • 外部回调;
  • 监控、日志和告警;
  • 备份任务能否在新集群继续执行。

如果灾难期间启用了只读模式,应在确认双写、重复消费和数据冲突风险后再恢复写入。


九、常见误区和对应的失败表现

误区一:etcd 快照就是整个集群备份

失败表现:API 对象恢复,但 PVC 无法挂载,镜像拉取失败,外部域名仍指向旧入口。

根因:etcd 只保存 Kubernetes API 状态,不保存卷数据、节点环境、镜像仓库和外部 DNS。

误区二:备份了 PVC 就备份了数据库

失败表现:PVC 为 Bound,数据库却启动失败、数据校验失败或出现事务不一致。

根因:PVC 是资源对象;卷快照也未必是数据库应用一致性备份。

误区三:所有 YAML 原样重新 apply

失败表现:

  • metadata.resourceVersion 冲突;
  • webhook 或 Operator 反复修改对象;
  • ClusterIP 或云资源字段冲突;
  • CRD 尚未存在导致自定义资源创建失败;
  • status 字段被错误带入。

根因:声明式配置和控制器生成状态没有被区分。恢复时应清理服务器生成字段,并按依赖关系分阶段应用。

误区四:Pod 为 Running 就算恢复

失败表现:Pod 状态正常,但请求全部超时或返回 5xx。

根因:应用可能尚未连接数据库、消息队列、对象存储或身份系统。Running 只描述容器进程状态,Ready 也只是就绪探针定义的局部条件。

误区五:DNS 改了就立即切流

失败表现:一部分用户访问新集群,另一部分仍访问旧集群;双写造成数据分叉。

根因:DNS 缓存不会立即失效,且旧入口可能仍然可写。切流前必须处理旧集群的写入能力和幂等问题。

误区六:恢复演练只验证命令能执行

失败表现:演练报告显示“备份成功、恢复成功”,真正灾难时却找不到证书、KMS 密钥或管理员凭据。

根因:演练只验证了工具流程,没有验证依赖、时间目标和业务结果。


十、灾难演练:验证恢复能力而不是验证备份文件存在

灾难演练应在独立环境、隔离网络或临时恢复区域进行,避免污染生产数据。最少需要验证以下内容:

  1. 从异地位置取得 etcd 快照并校验;
  2. 使用记录的版本和参数恢复控制面;
  3. API Server 可以读取加密 Secret;
  4. CRD、自定义资源和 Operator 能按顺序恢复;
  5. PVC 能绑定并挂载;
  6. 数据库能完成一致性恢复;
  7. CoreDNS、Service、Ingress 或 Gateway 正常;
  8. 镜像仓库、CNI、CSI、身份系统和外部数据库可访问;
  9. 外部 DNS 或流量系统可以切换;
  10. 真实业务流程通过;
  11. 实际耗时满足 RTO;
  12. 实际数据丢失量满足 RPO。

演练记录至少应包含:

  • 备份创建时间;
  • 快照复制完成时间;
  • 恢复开始和结束时间;
  • 每个阶段的阻塞点;
  • 恢复后对象数量;
  • PVC 和数据库恢复点;
  • DNS 切换时间;
  • 业务验证结果;
  • 未恢复的非关键资源;
  • 下一次演练前必须修正的问题。

可以为恢复过程定义验收条件:

TrestoreRTOT_{\text{restore}} \leq RTO

tdisastertdataRPOt_{\text{disaster}} - t_{\text{data}} \leq RPO

其中 TrestoreT_{\text{restore}} 是从恢复开始到业务验收通过的时间,tdisastert_{\text{disaster}} 是灾难发生时间,tdatat_{\text{data}} 是恢复后业务数据对应的最新一致时间点。若控制面 10 分钟恢复,但数据库只能恢复到 3 小时前,则整体 RPO 仍是 3 小时,而不是 10 分钟。


十一、版本、实现和生产边界

Kubernetes API 版本会随版本演进。备份对象恢复到不同版本的集群时,必须检查:

  • API group/version 是否仍受支持;
  • CRD 的 conversion webhook 是否可用;
  • 字段是否已弃用或改变语义;
  • admission webhook 的 API 是否兼容;
  • StorageClass、Ingress、Gateway 和 CSI 能力是否一致;
  • 云厂商插件是否支持目标 Kubernetes 版本。

kubectl、Velero、etcd、CSI 驱动和云厂商插件都有各自的版本兼容矩阵。本文命令只表示当前常见接口和流程,不能替代目标版本的官方文档。特别是 etcd 快照恢复参数、Velero 卷数据能力、CSI 快照支持和云负载均衡行为,都属于实现或厂商相关部分。

规范保证、常见实现和经验建议应明确区分:

  • Kubernetes API 对象通过 API Server 持久化,使用 etcd 是常见实现;
  • PVC 与 PV 的对象关系由 Kubernetes 管理,但卷后端数据由存储实现管理;
  • CoreDNS 是常见集群 DNS 实现,但 DNS 组件可以被替换;
  • Velero 的资源备份、Hook 和卷保护能力依赖安装版本与插件;
  • 数据库应用一致性必须由数据库机制或经过验证的 Hook 保证,不能由 Kubernetes 自动推断。

灾难恢复的最终目标不是让集群“看起来像原来”,而是在明确的 RPO 和 RTO 内,恢复一个能够安全接收请求、正确读写数据、连接所有必要依赖,并经过业务验证的系统。只有控制面、资源、数据、DNS、外部依赖和演练结果共同成立,Kubernetes 集群才算真正具备灾难恢复能力。


系列导航与关联阅读

官方资料

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