Kubernetes 整群重建与业务接管
机房网络故障扩大后,三个控制面节点和同一存储故障域里的快照同时不可用。团队很快新建了 Kubernetes 集群,却发现它只是“API Server 能回答”:旧备份里的 StorageClass 指向不存在的 CSI driver,ExternalSecret 访问的是旧账户,镜像仓库私网路径没有打通,Ingress 提前接入流量,数据库 Pod 在空卷上自动初始化。新集群健康,业务仍然丢失。
整群重建的目标不是复制一批 YAML,而是在新的故障域重新建立一条可验证的运行链。基础设施、控制面、扩展 API、身份、存储、数据和外部入口都有自己的事实来源;任何一层缺失,都可能让下一层以“对象已创建”的假象继续前进。
先判定是恢复旧控制面还是创建新集群
自管 Kubernetes 若保留了可验证的 etcd snapshot、PKI、kubeadm 配置、加密配置和相同控制面拓扑,可以在隔离主机恢复 etcd 逻辑集群,再启动 API Server。它保留历史 Kubernetes 对象身份和 revision 语义,但不会自动恢复云盘、负载均衡器、KMS、DNS 和镜像。
若控制面 PKI、主机、网络边界或托管集群本体已经不可恢复,正确路径是由 IaC/平台 API 创建新集群,再用 GitOps、对象备份、卷备份和应用原生备份重建工作负载。新集群会产生新的 UID、ServiceAccount token、OIDC issuer、节点身份和外部资产;不能把它伪装成旧控制面的原地恢复。
两条路径不能叠加执行。先恢复 etcd,再向同一集群全量导入同一恢复点,会在 UID、resourceVersion、PV claimRef、Service clusterIP、云资产和控制器字段所有权上制造冲突。事故指挥需要在第一道门禁记录所选路径、恢复点、目标故障域和放弃另一条路径的原因。
重建材料必须离开原集群故障域
能重建集群的材料不应只保存在集群内的 ConfigMap、Secret 或同一云账户。最低集合通常包括:
集群和网络 IaC、锁定的 provider/module 版本、plan 审批记录;Kubernetes minor、容器运行时、CNI、CSI、Ingress/Gateway、DNS、证书和 Operator 兼容矩阵;kubeadm 配置或托管集群声明、控制面 endpoint、Pod/Service CIDR;
自管路径所需的 etcd snapshot、etcd 版本、PKI、EncryptionConfiguration 或 KMS 依赖;CRD 与平台组件的安装制品、镜像 digest、Helm values 和迁移步骤;对象备份索引、卷快照或数据移动仓库、数据库原生恢复点;
外部 DNS、IAM、KMS、对象仓库、镜像仓库和证书的重建入口;业务依赖图、恢复顺序、数据校验、不变量、RPO/RTO 和接管审批人。
材料清单只记录凭证引用、key ID 和证书指纹。私钥、对象仓库 token、kubeconfig、etcd client key 和解密材料进入独立密钥系统,并由恢复身份按需领取。事故文档、聊天和终端历史都不应出现明文。
第一层:在新故障域建立基础设施
先选定目标账户、区域、VPC/VNet、子网、路由、Firewall/Security Group、负载均衡器入口、私有 DNS、KMS 和对象仓库路径。目标区必须能访问恢复仓库和镜像仓库,并具备节点、负载均衡器、磁盘、快照和公网地址配额。
IaC 执行采用固定版本和远端状态锁。灾难期间“先手工点出来再说”会让重建结果无法复现;若必须手工处置,立即把差异导入状态并保留审计证据。
terraform init -lockfile=readonly
terraform validate
terraform plan -out=/secure-incident/cluster-rebuild.plan
terraform show -json /secure-incident/cluster-rebuild.plan \
> /secure-incident/cluster-rebuild-plan.json示例不代表任何云厂商通用命令。plan 的放行条件至少包括:没有删除现存恢复仓库、没有复用源集群 OIDC 身份、目标 CIDR 不与企业网络冲突、节点覆盖卷拓扑、KMS 和对象仓库位于计划故障域、配额能容纳恢复峰值。
反向检查是故意撤销恢复身份对仓库或 KMS 的访问,再执行只读探测。应在基础设施层得到明确的 AccessDenied/TLS/路由错误,而不是等到数据恢复 Job 运行数小时后才发现。恢复权限应只读源仓库、可写新前缀,不应拥有缩短保留期或销毁 key 的能力。
第二层:创建控制面并证明它可管理
托管 Kubernetes 使用云厂商受支持的创建入口和版本;自管 kubeadm 需要在 stacked etcd 与 external etcd 间作明确选择。kubeadm 高可用文档区分两种拓扑,并要求先准备稳定的 controlPlaneEndpoint。外部 etcd 要先建立成员和 TLS,再把 endpoints 与 client 证书交给首个控制面。
下面是新集群 kubeadm 配置骨架,地址和版本必须来自批准的灾备配置:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: <approved-version>
controlPlaneEndpoint: "api.recovery.example.invalid:6443"
networking:
podSubnet: <approved-pod-cidr>
serviceSubnet: <approved-service-cidr>
etcd:
external:
endpoints:
- https://<etcd-a>:2379
- https://<etcd-b>:2379
- https://<etcd-c>:2379
caFile: /etc/kubernetes/pki/etcd/ca.crt
certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key配置中的私钥不能进入 Git 或普通制品库。--upload-certs 产生的 certificate key 能访问敏感控制面材料,只在受控窗口传递并在节点加入后撤销。不要把命令输出粘贴到事故群。
sudo kubeadm init --config /secure/kubeadm-config.yaml --upload-certs
kubectl get --raw='/readyz?verbose'
kubectl get nodes -o wide
kubectl -n kube-system get pods
kubectl auth can-i '*' '*' --as=system:serviceaccount:default:default最后一条预期为 no。控制面门禁是 API Server、scheduler、controller-manager 和 etcd 健康,审计与认证链可用,普通工作负载身份没有管理员权限。此时节点可能因 CNI 尚未安装而 NotReady,不能把它误判成控制面恢复失败。
kubeadm reset 只是 best effort 清理;官方参考明确说明它不会清理 /etc/cni/net.d,external etcd 数据也不会被删除。重复利用节点前必须核对 CNI 配置、iptables/nftables、IPVS、挂载和外部 etcd,避免新集群接入旧状态。
旧控制面路径:etcd 恢复还有配置和身份依赖
恢复旧逻辑控制面时,应使用与 snapshot 格式兼容的 etcd 工具,在断网的恢复主机先检查 hash、revision 和元数据,再创建新的 data directory。所有 API Server 停止写入,旧成员与恢复成员隔离;不要把 snapshot 文件覆盖到正在运行的数据目录。
etcdutl snapshot status /secure/etcd/snapshot.db --write-out=table
etcdutl snapshot restore /secure/etcd/snapshot.db \
--data-dir=/var/lib/etcd-restored \
--name=<member-name> \
--initial-cluster=<member-a>=https://<peer-a>:2380,<member-b>=https://<peer-b>:2380,<member-c>=https://<peer-c>:2380 \
--initial-advertise-peer-urls=https://<this-peer>:2380etcd 3.6 及后续版本的离线 status/restore 使用 etcdutl;具体参数和 revision bump/mark compacted 应按etcd 灾难恢复指南与Kubernetes etcd 操作指南执行。恢复后若 revision 回退,informer 可能保留错误缓存,需按指南提高 revision 并标记已 compact。
API 数据启用了静态 EncryptionConfiguration 或 KMS 时,etcd snapshot 只保存密文和封装数据密钥。缺少原解密 key、KMS endpoint 或授权,API Server 会无法读取 Secret/ConfigMap。所有控制面节点必须使用一致且能读历史密文的配置;Kubernetes 静态加密指南还指出,错误移除旧 key 会让相关对象只能从 etcd 直接删除。解密依赖必须在恢复演练里真实验证。
第三层:平台能力先于业务对象
新集群创建后依次安装 CNI、CoreDNS、CSI、snapshot-controller、Ingress/Gateway、cert-manager、External Secrets、监控、备份控制器、策略引擎和业务 Operator。CRD 先于 CR,webhook 后端健康后再放行依赖它的写入。
kubectl get nodes
kubectl -n kube-system get ds,deploy,pod -o wide
kubectl get crd,apiservice
kubectl get storageclass,csidriver,volumesnapshotclass
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations平台能力的正向冒烟需要创建临时 Pod 做 DNS 和网络解析,创建测试 PVC 完成写读,创建测试快照再恢复为新 PVC,并让工作负载身份访问专用测试 key/仓库前缀。DaemonSet Ready、CRD Established 和 PVC Bound 分别只是局部状态。
反向实验可把测试 ServiceAccount 与云角色解绑。预期只有测试请求得到身份拒绝,CNI/CSI 控制器本身保持健康。若解决方法是授予节点全局云管理员,说明身份边界设计错误。
版本选择要遵循 Kubernetes、CNI、CSI、snapshot sidecar、CRD 和备份产品的交集。开发分支清单不能直接用于灾备;所有镜像固定 digest,并提前镜像到目标故障域可访问的仓库。
第四层:把恢复仓库切成只读并导入索引
恢复控制器连接源仓库时先使用只读身份,防止新集群的调度任务覆盖、过期或删除原恢复点。Velero 的灾难恢复实践使用 BackupStorageLocation.spec.accessMode: ReadOnly 表达这一约束:
kubectl -n velero patch backupstoragelocation <source-bsl> \
--type merge -p '{"spec":{"accessMode":"ReadOnly"}}'
velero backup-location get
velero backup get
velero backup describe <recovery-point> --details仓库中能看到 Backup CR 不等于所有数据可读。还要验证对象清单、卷路径、快照 handle、Kopia/restic 仓库、KMS 和数据库原生归档。跨云或跨区域时,存储原生 snapshot 可能不可见;应提前使用文件级 data mover、复制后的独立仓库或应用原生备份。Velero 的集群迁移说明明确提醒跨平台卷快照的限制。
新集群不要立即启用 Schedule、Prune 或仓库写权限。等业务接管稳定,并确认新恢复点写入独立前缀或新仓库后,再由双人审批切换读写模式。
第五层:先恢复数据,再让应用产生副作用
数据恢复按应用一致性协议分组,而不是按 PVC 名称并行到底。数据库使用原生全量+日志/PITR 或经过 quiesce 的一致性恢复点;文件卷可从 CSI snapshot、snapshot data movement 或文件仓库创建新 PVC。每块恢复卷都应拥有新的 PV/volume handle,并记录源恢复点和校验值。
velero restore create cluster-data-alpha \
--from-backup <recovery-point> \
--include-resources persistentvolumes,persistentvolumeclaims,volumesnapshots.snapshot.storage.k8s.io \
--existing-resource-policy none --wait
velero restore describe cluster-data-alpha --details
kubectl get pvc -A
kubectl get pv -o custom-columns='NAME:.metadata.name,SC:.spec.storageClassName,HANDLE:.spec.csi.volumeHandle,CLAIM:.spec.claimRef.namespace/.spec.claimRef.name'对象恢复工具是否支持上述资源组合取决于卷保护路径;命令必须先在隔离演练校准。恢复后用专用 verifier Pod 只读挂载卷,检查文件校验和、数据库水位、对象数量和 schema。不要让正式 StatefulSet 首次挂载未知卷,因为初始化脚本可能把“空目录”变成看似健康的新数据库。
反向实验是在测试卷中删除业务提交标记,或让数据库日志只恢复到不一致水位。Kubernetes 层仍可能显示 PVC Bound、Pod Running,业务校验必须失败并阻止应用阶段。这证明基础设施绿色状态不能代替数据证据。
容量预算按恢复峰值而不是正常运行计算:源快照克隆、目标卷、data mover 缓存、临时验证卷和回滚卷可能同时存在;对象仓库读请求、跨区流量、节点磁盘、KMS QPS、CSI attach 限额和 API Server QPS 都会影响 RTO。恢复吞吐要用演练实测趋势,不用厂商峰值带宽直接推算。
第六层:恢复应用,但保持入口和自动写入关闭
按依赖图恢复 Namespace、配额、ServiceAccount、RBAC、配置引用、Operator、无状态服务和有状态服务。Secret 优先由目标密钥系统重新签发;从备份取回的长期 token、证书和云密钥应视为可能泄漏并轮换。
恢复资源进入新集群前做转换:移除旧 UID/resourceVersion/status,映射 StorageClass、镜像仓库、节点拓扑、ServiceAccount、入口域名和外部 endpoint;CronJob 设为 suspend,消费者副本为零,Ingress/Gateway 暂不创建或不挂生产 DNS。
kubectl get cronjob -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,SUSPEND:.spec.suspend'
kubectl get deploy,statefulset -A
kubectl get ingress,httproute -A
kubectl get events -A --sort-by=.metadata.creationTimestamp应用门禁由业务不变量决定:关键 API 只读查询正确,数据库 schema 与迁移版本一致,消息消费位点符合恢复策略,缓存可以重建,外部支付/邮件/工单处于 sandbox 或禁止副作用模式,首个受控写入能读回且审计完整。健康检查只证明进程能服务,不证明业务状态正确。
接管流量要分级,并始终保留回切点
先接内部探测和运维流量,再接影子读、少量只读流量、受控写流量,最后恢复队列消费者、定时任务和全部入口。DNS TTL、Global Load Balancer、Gateway 权重和消息消费组都要有明确 owner;同时切换会让失败来源无法定位。
接管证据包
cluster: API / nodes / CNI / CSI / admission
data: recovery point / checksum / commit position / first write
app: dependency probes / error rate / latency / side effects
traffic: route weight / DNS answer / queue lag / rollback target
security: temporary role expiry / secret rotation / audit trail回切不是把 DNS 改回去这么简单。新集群接收写入后,旧集群数据已经落后;除非建立了受验证的反向复制和单写仲裁,否则旧集群只能作为只读取证或重新恢复目标。切流前明确“最后可无损回切时刻”,超过后改用前向修复或新的数据同步方案。
观察窗口内保留源仓库只读、旧入口关闭、恢复卷和事故制品。不要立即销毁旧环境,也不要同时运行两个可写集群。脑裂的第一证据通常是同一业务键出现两个递增水位、两个队列消费者或两个定时任务 owner。
失败时按层回退,不要清空集群重来
基础设施层失败,回退 IaC 变更并保留仓库/KMS;控制面层失败,隔离节点并核对证书、endpoint 和 etcd;平台层失败,停止业务导入并修 CNI/CSI/webhook;数据层失败,删除本次新建的验证 Pod 和恢复 PVC,保留源恢复点;应用层失败,缩容业务并保留数据卷取证;流量层失败,权重回零并停止新写入。
kubectl scale deploy,statefulset -A --all --replicas=0
kubectl get pv,volumesnapshotcontent -o yaml > /secure-incident/rebuild-assets.yaml
kubectl get clusterrolebinding -o yaml > /secure-incident/rebuild-rbac.yaml
kubectl get events -A --sort-by=.metadata.creationTimestamp > /secure-incident/rebuild-events.txtscale --all 是事故止写动作,不适用于依赖特定副本语义的系统组件,执行前应使用批准列表并排除平台 namespace。不要直接 kubeadm reset、删除 CRD、清空 finalizer 或销毁仓库;这些动作会同时删除诊断证据和恢复索引。
清理顺序是停止新调度与流量、撤销临时身份、确认恢复点仍可读、删除应用临时对象、处理 PVC/PV/快照的 Retain 资产、卸载平台控制器、最后销毁基础设施。external etcd、CNI 配置、云盘、负载均衡器、DNS 和对象仓库都不一定随 Kubernetes 对象消失,必须从云资产和账单侧对账。
把整群重建做成可演练的产品
成熟团队为每个集群维护带 owner 的重建版本包:IaC commit、Kubernetes 与插件兼容矩阵、镜像 digest、恢复点索引、依赖图、转换规则、校验脚本、接管步骤和清理清单。包的读取权限与生产管理权限分离,恢复身份短期签发,所有高风险动作进入审计。
演练不能只创建空集群。至少要在独立账户或区域完成控制面创建、仓库只读接入、一个有状态应用的真实数据恢复、故意失败实验、业务写读校验、分级切流模拟和孤儿资产清理。记录每层耗时、最长等待、重试原因、恢复吞吐和人工审批时间,RTO 才能从目标变成容量数据。
长期判断标准包括:源账户完全不可用时仍能取得重建材料;新集群不依赖旧 OIDC/KMS/DNS 才能启动;恢复仓库不会被恢复集群删除;数据校验能阻止空卷应用接管;临时权限和成本在演练后回到基线;版本升级后旧恢复点仍能在受支持路径恢复。整群重建只有在这些证据被周期性重新证明时,才是真正的灾备能力。
重建包的版本与行为依据
重建包必须记录所采用的 Kubernetes 和 Velero 文档版本;升级控制面、etcd 或恢复工具后,按下列行为定义重新验证整群恢复链:
