LabHub
学习 学习路径 课程

CRD 与 Operator

调谐 — 观测、比较、行动、汇报四拍

在 LabHub 中继续学习

一句话总结

Recon Sale不问“发生了什么事”。只问**“现在想要的状态是什么,实际是什么”**,并明确缩小差异。

对比图: “现在想要的状态是什么,实际是什么” · 只做未完成的部分 · 传递到ReconSile的不是对象,而是对象的键(命名空间/名称) · 4次的边等性。

为什么需要这个?

命令式自动化脚本是“做这个,设置那个,然后执行这个”的列举。如果中间失败,系统就会处于半途而废的状态,再执行时会出现“已存在”错误或产生重复。所以人们会害怕重新执行,可怕的自动化最终不会被使用。

Reconsil颠覆了这个问题。无论在哪个点失败,下一次调用都会从头开始,只做未完成的部分。即使控制器在中间死了又复活也没关系。因为不需要把“做了多少”记在内存里。真正的状态总是在群集中。

怎么行动

实际控制器中请求到达的路径有三个阶段。

API Server --watch--> Informer --> WorkQueue --> Reconciler
                       (로컬 캐시)  (중복 제거,   (사용자 코드)
                                    속도 제어)

这里最重要的见解是传递到ReconSile的不是对象,而是对象的键(命名空间/名称)。不会传递出发生了什么变化、是否生成、是否修改。ReconSile用那个键重新读取最新状态。这个设计强制了对齐性。

ReConSyll一次总结为六个问题。

  1. 现在这个对象想要的状态是什么(desired)
  2. 实际的群集状态是什么(observed)
  3. 两个人的区别是什么(diff)
  4. 为了彻底弥合那个差距(action)
  5. 还有没填写的部分吗(requeue判断)
  6. 如何将观察到的结果反映在status上(status)

**4次的边等性。**不是写“生成”,而是写“确认是否以想要的样子存在,否则调整”。如果已经正确了,什么都不做就是正确答案,到时候子对象的resourceVersion应该保持原样。虽然内容相同,但重新写的话会产生新的watch活动,而该活动会再次唤起reconsail。

**5次重试。**不能把“还没有准备好”全部处理为错误。如果返回错误,工作队列将重新尝试50%,错误日志会累积,该对象的重试时间会延长,响应性会降低。没有依赖对象并不是失败,而是“一会儿再看”。

区分 触发器 等待时间 用途
错误后退 错误返回 指数性增加(自动) 真正的失败重试
延迟Jacquy 结果明示 我指定的值 故意周期检查

百次退出的具有统计学意义的原因是为了防止一个对象的重复失败导致整个队列被饿死。而且成功后,该键的退出计数器会重置。

**6次status和无限循环。**使用status的话,它本身会成为新的watch事件,可以重新调用reconsole。实际控制器是GenerationChangedPredicate阻止这个。generation只有在spec发生变化时才会上升,所以设置这个过滤器的话,会过滤因使用自我status而引起的重新调用。但是如果是需要周期检查的控制器的话,要注意不要让这个过滤器阻止到那个检查为止。

**删除路径和最终化器。**通过所有者参考连接的群集内的子节点由垃圾收集器整理,但群集外的资源(云负载平衡器、外部DB账户)库bernetes不知道。最终化器是保证整理的装置。

삭제 요청 -> deletionTimestamp 설정 (오브젝트는 아직 존재)
          -> 리컨사일 재호출: 정리 수행
          -> 파이널라이저 제거
          -> 그제서야 실제 삭제

核心不是“删除=立即消失”,而是**“删除=删除时间被拍下来,重新启动一次”**。那一次是整理最后的机会。而且整理函数也必须正确——如果试图删除已经不存在的东西时出现错误,最终化器将永远无法退出,导致删除死循环。

对这个实习环境的坦诚说明

在这个脚本中没有执行编译好的Go控制器的手段。所以将Reconciler循环变成Shell脚本Reconciler。informer缓存和工作队列不进行模拟,而是用手实现Reconciler函数的本质:观测→比较→行动→status报告和再队判断,最终化流程。被评分的不是控制器二进制文件,而是判断的准确性和公平性。即使将实际控制器移植到Go中,这四个节拍也保持不变。

在现场相遇的样子

**第一,无限重连。**几乎所有操作员开发者都经历过一次的通关仪式。如果重连使用status,那么使用status会创建一个新的watch事件,该事件会再次调用重连。CPU抓住一个核心,日志每秒积累数百行。解决办法有两种——只有在spec发生变化时才会增加metadata.generation挂上查看的predicate,通过status变更来停止因自我触发,或者只有在最初值发生变化时才使用status。

**第二,status冲突。**试图用从缓存中读取的旧对象更新status时,会遇到conflict。如果控制器同时处理多个资源,概率会提高。重新读取最新对象更新或交给服务器侧apply进行字段单位合并。

第三,看不到刚刚创建的对象。阅读从informer缓存进行,写入到API服务器。所以Create后立即Get的话,可能会一直出现“没有”。在这里加入sleep是最常见的错误答案。正确的答案是不等待——均匀地放置,在下一个reconcycle中自然看到就可以了。这是reconcycle循环的设计哲学得以体现的地方。

第四,被终结器的对象。如果控制器死后删除CR的话deletionTimestamp因为没有被拍下就永远留在Terminating中。因为没有主体可以拆除最终化器。所以在删除操作员时,必须遵守先整理CR而不是控制器的顺序,如果紧急的话,在运行手册上写下用手拆除最终化器的逃生口。

下次实习要做的事情

/root/op/reconcile/reconcile.sh填写后将CR的spec设置为desired,将子ConfigMap读取为actual进行比较,如果没有就创建,如果已经正确就什么都不做。resourceVersion以这个状态为依据进行证明。将status重写为子源路径,在没有依赖对象时将再聚判断留为JSON,制作成用最终化器整理后被删除的流程,最后制作成循环了三个CR的调整报告。