快照做不到的那种恢复
一句话总结
etcd 快照是把整个集群回退到某一时间点的工具,所以不能用于“只恢复这个命名空间”或“恢复到另一个集群”。
这个位置由对象级备份来填补,但导出的内容不能原样放回去,
尤其是如果留下 ownerReferences,恢复出来的对象会悄悄消失。
为什么只靠快照不够
生产环境中实际收到的请求,大多是这样的。“请把昨天下午误删的 shop 命名空间
单独恢复回来。”或者“请在预发布集群中搭建出与生产相同的配置。”这两个请求都无法用 etcd
快照来回答。快照恢复是把整个集群回退到那个时间点,所以在此期间发生的
其他所有变更也会一并消失。不可能为了一个被删除的命名空间,把那一天所有的部署都
回退。而且快照与该集群的身份绑定在一起,无法迁移到另一个集群。
认为这两种方式各有各的位置,会更准确。
| etcd 快照 | 对象备份 | |
|---|---|---|
| 单位 | 整个集群 | 选定的对象 |
| 时间点 | 整体是同一个时间点 | 每个对象是各自导出的时间点 |
| 到另一个集群 | 不行 | 可以 |
| 部分恢复 | 不行 | 可以 |
| 控制平面设置 | 一并恢复 | 不在对象范围内 |
工作原理
对象备份的关键不是导出,而是清理。kubectl get -o yaml 不区分用户写的内容和服务器
填充的内容,会全部导出。其中有些内容不能放回去。
| 要清除的内容 | 为什么 |
|---|---|
metadata.uid |
每个对象都会重新分配。无法填入旧值 |
metadata.resourceVersion |
是用于乐观锁的值,恢复时只会造成冲突 |
metadata.creationTimestamp、generation |
由服务器决定 |
metadata.managedFields |
是字段所有权的历史,在另一个集群中没有意义 |
metadata.ownerReferences |
在恢复的集群中没有拥有该 uid 的所有者 |
status |
由控制器观测后重新写入 |
Service 的 spec.clusterIP、nodePort |
是该集群分配的地址 |
此外,养成同时清除 metadata.namespace 的习惯很有用。这样恢复的目标位置可以通过命令而不是清单来
指定,也才能建立起用同一份备份先在验证用命名空间中搭建一遍的流程。
最危险的是 ownerReferences。Kubernetes 通过这个引用来管理依赖关系,垃圾回收器会
删除失去所有者的对象。而引用不仅包含名称,还包含 uid。如果在恢复的
集群中找不到拥有该 uid 的对象,垃圾回收器就会判断所有者已经消失,
从而删除恢复出来的对象。没有错误,没有告警,大多数情况下连事件都没有。把 ReplicaSet 整个
备份,恢复几秒后悄悄消失的事故就是这样产生的。
恢复也有顺序。命名空间必须先存在,才能把对象放进去,CRD 必须先 注册,才能接受自定义资源。顺序错了,API 服务器会以不认识这个种类为由直接拒绝—— 不是清单有问题,而是这个种类还不存在。
最后,PVC 不是数据。如果备份了 PersistentVolumeClaim,恢复出来的只是一张“请给我这么多 卷”的申请单,里面原有的文件哪里都没有。数据备份是卷快照或应用级备份这另一个问题。如果混淆这一区别, 就会在通过恢复演练之后,在真实事故中丢失数据。
在现场相遇的样子
最常见的事故是恢复之后几秒又消失。原因是上面说的 ownerReferences,但症状 不是“恢复失败”,而是“恢复成功了却没有”,所以找原因要花很长时间。
第二种是没有写下恢复顺序。平时 CRD 已经存在,所以一点问题都没有, 直到在新集群上整体恢复的那一天才第一次遇到。那一天没有时间。
第三种是验证靠人眼来做。只数对象是否存在的话,副本数从 2 减到 1 的 恢复也会看起来成功。所以恢复流程的最后一步,必须始终是由机器对照值。
本实验环境的局限
这个集群里既没有 PersistentVolume,也没有 CSI 驱动,所以无法用实物展示 PVC 与数据的区别。 这一部分用上面的说明来代替。相反,垃圾回收器是真的在运行,所以如果带着过期的所有者引用 去恢复,可以亲眼看到那个对象真的消失——在实验中实测,几秒钟就 消失了。另外,本实验一次都不会用到 etcdctl。这既是为了不与同一课程中的 etcd 实验重复, 也是因为对象备份本来就是不触碰 etcd 的工作。
下一项实验要做什么
创建一个 CRD 和一整套命名空间内容(Deployment、Service、ConfigMap、自定义资源),原样 导出,看里面包含了什么。然后编写剔除必须清除的字段的脚本,把命名空间 整个删除后,再恢复到另一个命名空间。接着确认留有过期所有者引用的对象真的 消失,读取在没有 CRD 时先放入自定义资源所产生的错误,并写下恢复顺序。 最后编写把恢复清单与实际集群进行对照的验证脚本。
参考文档:
- https://kubernetes.io/docs/concepts/architecture/garbage-collection/
- https://kubernetes.io/docs/concepts/overview/working-with-objects/