Day 12:灾难恢复 + 生产事故注入
Day 1-11 都在「建」—— 装 K8s、Cilium、CoreDNS、Harbor、ArgoCD、监控、日志、备份。Day 12 来「破」+「救」,完成闭环。这一天的坑都是 SRE / 平台工程师 / CKA-CKS 的核心考点:
- etcd 单节点损毁 → 直接 snapshot restore 会失败,正解是
member remove/add - etcd 多 member 同时 down → quorum 失败才轮到 snapshot restore
- control plane 全挂 → 光有 etcd snapshot 不够,还要
/etc/kubernetes/pki+manifests - Cilium DaemonSet 误删 → 节点自动
node.cilium.io/agent-not-readytaint,新 Pod 全 Pending kubeadm reset不彻底 → 残留 CNI 配置 / iptables / etcd data,下次 join 必失败
整篇按「注入 → 现象 → 恢复 → 真坑」走,最后给 postmortem + chaos 工具链。
所有动作必须在演练集群上做。关键写操作(snapshot restore / etcd member remove)生产环境必须 pair work。
SRE 黄金 3 步
- 止血:隔离故障组件、流量切走、副本下线,先停止扩散
- 诊断:看 events / logs / metrics,不要急着改
- 修复:优先回滚到「已知好状态」(snapshot / 上一版本镜像),不要边修边推断
三件事不要做:
- 不要
kubectl delete --force当第一反应,先describe看 events - 不要单人删 etcd / 删 PV,关键写操作必须 pair work
- 不要先回滚再保留现场,先
kubectl get -o yaml > backup.yaml再动手
A. etcd 单 cp 节点损毁 → member remove/add
A.1 注入前先打 snapshot
在 etcd 健康的 cp-1 上跑 snapshot,没 snapshot 不要做这个实验:
ETCD_POD=$(kubectl get pod -n kube-system -l component=etcd \
--field-selector=spec.nodeName=k8s-cp-1 -o name | head -1)
kubectl exec -n kube-system $ETCD_POD -- sh -c "ETCDCTL_API=3 etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save /tmp/snapshot.db"
# Pod 临时存储,Pod 重启会丢,必须拷出来
kubectl cp -n kube-system ${ETCD_POD#pod/}:/tmp/snapshot.db \
/root/etcd-backups/snapshot-$(date +%Y%m%d-%H%M).db
# 验证完整性 —— 损坏的 snapshot 在 restore 时才报错就晚了
ETCDCTL_API=3 etcdctl --write-out=table snapshot status /root/etcd-backups/snapshot-*.db
生产做法:CronJob 每 30min snapshot,rsync 到 S3 / 异地 NFS,保留 ≥ 7 天滚动。Pod 临时文件随 Pod 重建消失,必须拷出来。
A.2 注入 cp-2 灾难 + 观察影响
模拟磁盘故障 + 数据丢,只在 cp-2 一台上做:
ssh m2 '
mv /etc/kubernetes/manifests/etcd.yaml /tmp/
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
mv /var/lib/etcd /var/lib/etcd.broken-$(date +%s)
'
# 立刻在 cp-1 验证
etcdctl --endpoints=https://10.0.24.31:2379,https://10.0.24.29:2379,https://10.0.24.32:2379 endpoint health
# 10.0.24.29 is unhealthy: ... connection refused
# 10.0.24.31 is healthy
# 10.0.24.32 is healthy
kubectl create ns day12-test # ✅ 2/3 quorum 仍可写
观察:失去 1 个 etcd member 后 raft quorum 还是 2/3,apiserver 继续可读写;cp-2 apiserver CrashLoopBackOff,per-node HAProxy 自动把 cp-2:6443 标 down,客户端 failover 到 cp-1/cp-3;用户感知几乎为零 —— 3 CP HA 的全部意义在此。
A.3 为什么不能直接 snapshot restore
新手第一反应:「在 cp-2 上 etcdctl snapshot restore,恢复后加回集群」—— 这是错的。
错误: cp-2 数据丢 → 跑 restore → 起 etcd → join 现有 cluster
→ ❌ member ID mismatch / cluster ID mismatch
根因: restore 出来的是"独立新 cluster", member-id / cluster-id
跟存活的 2 个 member 完全对不上, etcd 拒绝它加入
正解是把 cp-2 当全新成员加进现有集群,从 leader 拉数据:
- 在 cp-1 跑
etcdctl member remove <cp-2 id>,从 raft 配置删死成员 - 在 cp-2 清空
/var/lib/etcd - 在 cp-1 跑
etcdctl member add k8s-cp-2 --learner,告诉集群「即将有新成员加入」 - 改 cp-2
etcd.yaml:--initial-cluster-state=existing+ 完整 3-node cluster list - 把
etcd.yaml放回 manifests/,kubelet 自动起 etcd,从 leader sync - sync 完后
member promote把 learner 转 voter
关键认知:snapshot restore 是给「整个 cluster 全死」用的,单节点损毁用 member add。这两个场景处理路径完全不同,分不清就越救越坏。
A.4 完整恢复命令
# cp-1 上 etcdctl member list 拿到 cp-2 id, 然后:
etcdctl member remove c14422e68fa9c73e
etcdctl member add k8s-cp-2 --peer-urls=https://10.0.24.29:2380 --learner
# 输出 ETCD_INITIAL_CLUSTER=... 抄下来用在下一步
# cp-2 上改 manifest:
ssh m2 '
rm -rf /var/lib/etcd && mkdir -p /var/lib/etcd
vim /tmp/etcd.yaml # 改 --initial-cluster-state=existing + --initial-cluster
mv /tmp/etcd.yaml /etc/kubernetes/manifests/
mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
'
# learner sync 完后 promote 成 voter
etcdctl member promote <new-cp-2-id>
etcdctl --endpoints=<3-cp-list> endpoint health # ✅ 3/3
实测从注入到 3/3 恢复 约 2 分钟。
A.5 真坑:3 个常见 fail 点
--initial-cluster-state=existing漏了。默认是new(kubeadm init 生成),re-join 没改启动直接panic: cluster ID mismatch。任何 re-join / restore 场景必须existing,只有从 0 建集群才是new。- etcd ghost member。中途失败但
member add已跑,etcdctl member list 会看到unstarted的 ghost。必须先member remove删 ghost 再重试,不能直接重试 add。 kubeadm reset不彻底。残留必清 —— 做成full-reset.sh:
kubeadm reset -f
rm -rf /etc/cni/net.d /var/lib/etcd /root/.kube /etc/kubernetes
iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X
ipvsadm --clear 2>/dev/null || true
systemctl restart containerd
B. etcd 全 member down → snapshot restore
A 是 1/3 死,B 是 3/3 全死 —— 这才是 snapshot restore 的正确场景。
B.1 触发条件
- 整机房断电、机柜级灾难、3 个 cp 节点的盘同时损毁
kubectl get nodes完全卡死,apiserver 全不可达- worker 上的业务 Pod 还在跑(kubelet 进 standalone 模式继续运行已知 Pod,但无法新建 / 调度)
B.2 完整 restore 流程
前提:异地有 snapshot。snapshot 跟集群同机房就是 0 备份(机房断电同时 snapshot 也丢 —— 真实事故)。3-2-1 备份强制:3 份副本、2 种介质(盘 + 对象存储)、1 份异地。
# 1. 选 cp-1 当新 cluster 种子, 从异地拉 snapshot
scp snapshot.db m1:/root/
# 2. cp-1 跑 restore 产出全新 data dir
ssh m1 '
ETCDCTL_API=3 etcdctl snapshot restore /root/snapshot.db \
--name k8s-cp-1 \
--initial-cluster k8s-cp-1=https://10.0.24.31:2380 \
--initial-cluster-token etcd-cluster-recovered-$(date +%s) \
--initial-advertise-peer-urls https://10.0.24.31:2380 \
--data-dir /var/lib/etcd-restore
mv /var/lib/etcd /var/lib/etcd.old-$(date +%s)
mv /var/lib/etcd-restore /var/lib/etcd
'
# 3. 改 cp-1 etcd.yaml: --initial-cluster-state=new + 只含 cp-1 的 cluster list
# + --initial-cluster-token 必须跟 restore 时同一个, 否则 etcd 拒起
# 4. cp-2 / cp-3 走 full-reset.sh + kubeadm join 重新加入
# (在 cp-1 上 kubeadm token create --print-join-command)
# 5. 验证: kubectl get nodes + etcdctl endpoint health
时间点损失 = 距上次 snapshot 的时间。snapshot 每 30 分钟 → 最坏丢 30 分钟数据。这个 RPO 必须写进 SLA。
C. control plane 全挂 → /etc/kubernetes 备份
snapshot 只救 etcd 数据。重建 control plane 还需要这些:
| 文件 | 作用 |
|---|---|
/etc/kubernetes/pki/ | CA / apiserver / front-proxy / etcd / sa 所有证书 |
/etc/kubernetes/manifests/*.yaml | etcd / apiserver / cm / scheduler static pod 定义 |
/etc/kubernetes/*.conf | admin.conf / kubelet.conf 等 kubeconfig |
/var/lib/etcd 或 snapshot | 集群状态 |
备份(每个 cp 节点每天 1 次):
TS=$(date +%Y%m%d)
tar czf /backup/cp-$(hostname)-${TS}.tar.gz \
/etc/kubernetes/pki /etc/kubernetes/manifests /etc/kubernetes/*.conf
aws s3 cp /backup/cp-*.tar.gz s3://k8s-backup/cp/
为什么必须备 PKI:如果 PKI 丢了,所有 kubelet / kube-proxy / 已签发 ServiceAccount token 全部失效。等同于「重装集群再把业务搬回去」,不是恢复。
D. worker 节点全挂 → workload rebalance
worker 全挂 cp 还活着时 K8s 调度器自带处理 —— 但默认 toleration 有 10 分钟空窗:worker 5min 没心跳 → Node-not-ready taint → 再过 5min(tolerationSeconds=300)才驱逐 Pod。
对核心 stateless + 多副本业务显式设短:
spec:
tolerations:
- {key: node.kubernetes.io/not-ready, operator: Exists, effect: NoExecute, tolerationSeconds: 30}
- {key: node.kubernetes.io/unreachable, operator: Exists, effect: NoExecute, tolerationSeconds: 30}
副作用:网络抖动也触发驱逐。只给可重启的 stateless 业务设。
配 PodDisruptionBudget 保最小可用:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: {name: app-pdb}
spec:
minAvailable: 2
selector: {matchLabels: {app: web}}
真坑:PDB 只防「自愿驱逐」(drain / 调度器主动 evict)。节点突然下电、kubelet 失联是非自愿驱逐,PDB 不生效。PDB 防不了机器故障,只防 ops 手贱 drain。
E. Harbor 数据丢 → 镜像复制 + Velero
Harbor 挂或数据丢,全集群拉镜像失败,要重启的 Pod 卡 ImagePullBackOff。
双 Harbor 互相复制:主 Harbor UI → Replications → New Rule,destination 填备 Harbor,trigger 选 Event Based(push 时立刻同步)。主挂时切 DNS(harbor.internal 指向备 IP),业务 0 改动。
Velero 备份 Harbor namespace:
velero backup create harbor-daily-$(date +%Y%m%d) \
--include-namespaces harbor \
--snapshot-volumes --ttl 720h
velero restore create --from-backup harbor-daily-20260524
真坑:Harbor PV 含 registry storage + DB volume,备份必须 --snapshot-volumes,否则 restore 出来是空仓库。看 velero backup describe 里 Volume Snapshots Completed: X 才算真备上。
F. ArgoCD 凭据失效 → 跨集群同步停
ArgoCD 跨集群同步靠 cluster-secret(SA token + kubeconfig)。token 过期或 CA 轮换后 sync 全 fail。业务无影响(已部署的还在跑),但失去推送新版本能力。
症状:UI 上 application 全 OutOfSync + Unknown,argocd-server log 报 certificate signed by unknown authority。
恢复:
# 在目标集群创建新 SA
kubectl --context=target-cluster create sa argocd-manager -n kube-system
kubectl --context=target-cluster create clusterrolebinding argocd-manager-cluster-admin \
--clusterrole=cluster-admin --serviceaccount=kube-system:argocd-manager
# argocd CLI 重新 add
argocd cluster add target-cluster --name target --kubeconfig ~/.kube/config
预防:SA token 用 K8s 1.22+ bound token(projected token 自动轮换);Prometheus alert argocd_cluster_connection_status == 0。
G. postmortem:5 Whys + 时间线 + 改进项
事故救完不写 postmortem 等于白救。4 段式模板:
# 事故报告:2026-05-24 etcd cp-2 损毁
## 摘要
- 影响:cp-2 etcd 完全损毁,2/3 quorum 维持,用户感知 < 5s
- 时间:14:32 注入 → 14:34 恢复
- 严重等级:P2 (有备份, 无数据丢失, 无客户影响)
## 时间线 (精确到秒)
- 14:32:00 cp-2 /var/lib/etcd 被误删 (root cause)
- 14:32:15 Prometheus alert etcd_server_has_leader 触发
- 14:33:00 开始 member remove
- 14:34:10 3/3 恢复
## 5 Whys
1. 数据丢? → 被 rm -rf
2. 为什么被 rm? → 运维 ssh 错节点跑了清理脚本
3. 为什么 ssh 错? → 终端没有节点 prompt 区分
4. 为什么没 prompt? → bash PS1 没配
5. 为什么没配 PS1? → 装机模板缺这一步
根因: 装机模板缺 PS1 + 装机流程没 review
## 改进项 (必须带 owner + 截止日)
- [ ] 装机模板加 PS1 配置 @yuanjun 2026-05-31
- [ ] etcd snapshot CronJob 每 30min + 异地 S3 @ops 2026-06-07
- [ ] runbook 加「ssh 节点前确认 hostname」 @yuanjun 2026-05-26
3 原则:blameless(写「模板缺 PS1」不写「张三 ssh 错」,目的是让人敢报告事故)+ 每个 Action 必带 owner + 截止日(没 owner 等于没做)+ 下次复盘时回查上次 action(没闭环就是走过场)。
H. chaos engineering 工具链
手动注入做不出规模,生产级 chaos 用工具:
| 工具 | 特点 |
|---|---|
| Chaos Mesh (CNCF, PingCAP) | K8s 原生 CRD,dashboard 友好,国内主流 |
| LitmusChaos (CNCF) | 实验库丰富,chaos hub 共享,Argo Workflows 集成 |
| chaoskube (Zalando) | 轻量,只做 Pod kill,入门用 |
| Gremlin (商业) | 企业级,网络 / IO / 进程注入 |
Chaos Mesh 入门:
helm install chaos-mesh chaos-mesh/chaos-mesh \
-n chaos-mesh --create-namespace --version 2.7.0
cat <<EOF | kubectl apply -f -
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata: {name: pod-failure-test, namespace: chaos-mesh}
spec:
action: pod-failure
mode: one
duration: '30s'
selector: {labelSelectors: {app: web}}
EOF
支持的故障类型:Pod kill / network delay / network loss / IO delay / DNS error / time skew / kernel panic / JVM exception。
生产引入 3 原则:先有可观测再注入(没 Prometheus / alert 就做 chaos 等于自残);从最低风险开始(先杀单个 Pod,跑稳了再杀整个 Deployment / Node);月度固定窗口(on-call 全员就位,事后必写 postmortem)。
面试常见题
Q1:etcd snapshot save / restore 完整流程,什么时候用哪个?
snapshot save 任意 healthy member 都能跑(带 ca/cert/key 三个证书);snapshot restore 是「重建新 cluster」操作,参数含 --name / --initial-cluster / --initial-cluster-token / --initial-advertise-peer-urls / --data-dir。
分场景:
- 单节点 etcd 损毁 →
member remove+member add+ 从 leader sync,不用 restore - 多数节点挂、quorum 失败 →
snapshot restore重建 cluster,其他节点 kubeadm reset 后重 join - 时间点回滚 / 测试 →
restore到新 data dir 启动新 cluster 测试
深问:restore 最常见 fail 原因?—— --initial-cluster-token 跟 etcd.yaml 不一致;data-dir 没清空;权限不对。
Q2:control plane 全挂数据从哪恢复?
不是只恢复 etcd,要 3 样:etcd 数据(snapshot restore)+ /etc/kubernetes/pki/(CA + 所有证书,丢了所有 kubelet token 都失效相当于重装)+ /etc/kubernetes/manifests/(static pod 定义)。生产策略是 cp 节点每天 tar 上传异地 S3,配合 etcd snapshot CronJob 才算完整。
深问:只有 etcd snapshot 没 PKI 能恢复吗?—— 可以但要重装:新节点 kubeadm init 起空集群,把老 snapshot restore 进去。但 kubelet 全部要 rejoin(新 CA 签的 token),所有 SA token 全部要重新签,业务 Pod 要重启。
Q3:Velero 备份策略怎么定?资源 / PV / Hook 怎么搭?
3 个层次:资源备份(默认 --include-namespaces)+ PV 数据(加 --snapshot-volumes 走 CSI snapshot;或 restic / kopia 文件级)+ 应用一致性(pre/post hook,备份前 kubectl exec 让 DB FLUSH TABLES WITH READ LOCK)。
apiVersion: velero.io/v1
kind: Schedule
metadata: {name: daily, namespace: velero}
spec:
schedule: "0 2 * * *"
template:
includedNamespaces: ["*"]
excludedNamespaces: ["kube-system", "kube-public"]
snapshotVolumes: true
ttl: 720h
hooks:
resources:
- name: mysql-pre
pre:
- exec: {container: mysql, command: ["mysqldump", "..."]}
真坑:没加 --snapshot-volumes 备出来是空 PV;restic 备份大 PV(> 100G)巨慢,CSI snapshot 才能秒级;backup TTL 别太长,对象存储费用会爆。
Q4:postmortem 5 Whys 怎么写?
连续追问 5 次「为什么」,从现象追到系统性根因:
Why 1: 数据为什么丢? → 被 rm -rf
Why 2: 为什么被 rm? → ssh 错节点
Why 3: 为什么 ssh 错? → 终端 prompt 没区分节点
Why 4: 为什么 prompt 没区分? → 装机模板缺 PS1
Why 5: 为什么模板缺? → 装机流程没 review
根因: 装机流程缺 review 环节
3 原则:blameless(追系统问题不追人,「张三手贱」不是根因,「系统允许张三手贱」才是)+ 追到可改进的层(追到「人会犯错」就停,再追下去没意义)+ 5 不是硬指标(有时 3 个 why 够,有时要 7 个,关键是追到能改的层)。
深问:blameless 等于不追责吗?—— 不一样。blameless 是公开 postmortem 不点名,管理上该谈话还谈话。文化目的是让人敢报告事故。
Q5:chaos engineering 工具链怎么选?什么时候引入?
入门 / 单点故障 → chaoskube;中等规模 / K8s 原生 → Chaos Mesh;企业级 / 跨平台 → LitmusChaos + Argo Workflows;预算够 + 要支持 → Gremlin。
引入时机 —— 必须先满足三件事:可观测(Prometheus + AlertManager 全链路告警上线)+ 可恢复(备份策略 + restore 流程演练过)+ on-call 团队(故障第一时间有人接)。没这三样做 chaos 等于「自己注入故障、自己看不见、自己救不了」,反向破坏可用性。
深问:chaos 跟单元测试 / 集成测试有什么区别?—— 单测在 deterministic 环境验证已知逻辑;chaos 在 production 验证未知交互。chaos 找「设计时没想到的失效模式」,单测找「设计时想到的 bug」,互补不可替代。
下一步
Day 12 结束,14 天 K8s 实战的「破 + 救」闭环完成。Day 13 / 14 收尾:把 Day 0-12 的所有自动化(装机、HA、CNI、监控、备份、灾难恢复)做成 Ansible / Terraform 模板,新机房 3 小时内拉起一个生产级集群。