TT Lab
开始
学习 学习路径 课程

CRD 与 Operator

删了父对象,子对象却还在 - 属主引用与删除传播

在 TT Lab 中继续学习

目标

亲手创建并确认所有者引用的四个字段各自决定什么,用对象证明 --cascade 三种方式的差别,然后制作诊断卡在 Terminating 的对象的报告。

为什么重要

Operator 创建的资源不会独自存在。一个父对象消失时,挂在它下面的对象也应该一并清理,而 Kubernetes 中承担这件事的并不是引用计数。取而代之的是,子对象中有指向父对象的一行 ownerReferences,垃圾回收器沿着这个引用进行清理。所以如果把引用写错,故障会悄无声息地发生——uid 错了,刚创建的子对象几秒钟就会消失;跨越命名空间,则只会留下一条警告事件,子对象就被清理了。反过来,终结器是在清理完成之前扣住删除的机制,所以如果没有能做这项清理的控制器,对象就会永远停留在 Terminating。运维人员实际遇到的界面,往往就是这两者之一,而这两种情况都需要找出原因的流程。

步骤

  1. 在 /root/op-ownership/site-crd.yaml 中编写 CRD sites.own.labhub.io——group 为 own.labhub.io,kind 为 Site,复数形式为 sites,只有一个版本 v1,schema 中只有 spec.region(string)。创建命名空间 op-own,通过 /root/op-ownership/site-alpha.yaml 应用 Site alpha(region 为 kr-east),然后把该对象的 metadata.uid 用一行保存到 /root/op-ownership/alpha-uid.txt。
  2. 在 /root/op-ownership/alpha-good.yaml 中编写 ConfigMap alpha-good(命名空间 op-own),并在 ownerReferences 中写入 Site alpha——apiVersion 为 own.labhub.io/v1,kind 为 Site,name 为 alpha,uid 是第 1 步得到的实际值。在 /root/op-ownership/alpha-bad.yaml 中编写内容相同的 ConfigMap alpha-bad,但 uid 只写 00000000-0000-0000-0000-000000000000。两者都应用后稍等片刻,把 kubectl -n op-own get cm 的输出保存到 /root/op-ownership/gc-uid.txt。
  3. 创建命名空间 op-own-remote,并在 /root/op-ownership/alpha-remote.yaml 中编写 ConfigMap alpha-remote——命名空间是 op-own-remote,而 ownerReferences 指向(用实际 uid)位于 op-own 中的 Site alpha。应用之后稍等片刻,把 kubectl -n op-own-remote get events 的输出保存到 /root/op-ownership/xns-event.txt。
  4. 通过 /root/op-ownership/site-beta.yaml 创建 Site beta(region 为 kr-west),把它的子对象 ConfigMap beta-1 和 beta-2 放入 /root/op-ownership/beta-children.yaml 这一个文件(用 --- 连接),用实际 uid 设置所有者引用并应用。然后用 kubectl -n op-own delete site beta --cascade=background 删除,等子对象消失之后,把 kubectl -n op-own get cm 的输出保存到 /root/op-ownership/cascade-background.txt。
  5. 通过 /root/op-ownership/site-gamma.yaml 创建 Site gamma(region 为 jp-east),把子对象 ConfigMap gamma-1 和 gamma-2 放入 /root/op-ownership/gamma-children.yaml,用实际 uid 设置后应用。用 kubectl -n op-own delete site gamma --cascade=orphan 删除之后,把 kubectl -n op-own get cm gamma-1 -o jsonpath='{.metadata.ownerReferences}' 的结果,以 gamma-1-ownerrefs=<값, 비어 있으면 none> 一行(占位符为值,为空则写 none)保存到 /root/op-ownership/cascade-orphan.txt。
  6. 通过 /root/op-ownership/site-delta.yaml 创建 Site delta(region 为 us-west),并通过 /root/op-ownership/delta-child.yaml 创建 ConfigMap delta-1——在所有者引用中,除了实际 uid,再加入 blockOwnerDeletion: true,并给这个 ConfigMap 自身添加终结器 own.labhub.io/hold。然后执行 kubectl -n op-own delete site delta --cascade=foreground --wait=false,稍后把 kubectl -n op-own get site delta -o json 保存到 /root/op-ownership/foreground-stuck.json。这个 Site 要一直保留到本实验结束。
  7. 通过 /root/op-ownership/site-epsilon.yaml 创建带有终结器 own.labhub.io/drain 的 Site epsilon(region 为 eu-west),并把子对象 ConfigMap epsilon-data 用实际 uid 设置后,通过 /root/op-ownership/epsilon-child.yaml 应用。执行 kubectl -n op-own delete site epsilon --wait=false,把刚执行之后的对象保存到 /root/op-ownership/finalizer-pending.json,做完清理工作(亲自删除子对象 ConfigMap)之后,在 /root/op-ownership/epsilon-cleanup.txt 中至少用一行写明清理了什么。最后摘掉终结器,让 Site 真正消失。
  8. 创建 /root/op-ownership/stuck-report.sh——在 op-own 中,(1) 把打上了 deletionTimestamp 的 Site 打印为 STUCK Site/<이름> finalizers=<쉼표로 이은 목록>,(2) 把带有 blockOwnerDeletion 为 true 的所有者引用的 ConfigMap 打印为 BLOCKER ConfigMap/<이름> owner=<종류>/<이름> finalizers=<목록>(占位符依次为名称、用逗号连接的列表、类型、列表),把两类合并排序后只输出到标准输出。只要有一条 STUCK 行,就必须以非 0 的退出码结束;没有的话,打印 NONE 并以 0 结束。把输出保存到 /root/op-ownership/stuck-report.txt,并在 /root/op-ownership/diagnosis.txt 中用人能读懂的语句写明谁在阻止什么、为什么(其中必须包含阻塞的子对象的名称及其终结器的名称)。

参考

创建将要成为父对象的类型

在 /root/op-ownership/site-crd.yaml 中编写 CRD sites.own.labhub.io——group 为 own.labhub.io,kind 为 Site,复数形式为 sites,只有一个版本 v1,schema 中只有 spec.region(string)。创建命名空间 op-own,通过 /root/op-ownership/site-alpha.yaml 应用 Site alpha(region 为 kr-east),然后把该对象的 metadata.uid 用一行保存到 /root/op-ownership/alpha-uid.txt。

在所有者引用中,名称是给人读的值,真正的钥匙是 uid。同名对象被删除后再重新创建,uid 就会不同,这个性质是后续步骤的关键。uid 请用 kubectl get … -o jsonpath 取出。

uid 写错,垃圾回收器就会删除子对象

在 /root/op-ownership/alpha-good.yaml 中编写 ConfigMap alpha-good(命名空间 op-own),并在 ownerReferences 中写入 Site alpha——apiVersion 为 own.labhub.io/v1,kind 为 Site,name 为 alpha,uid 是第 1 步得到的实际值。在 /root/op-ownership/alpha-bad.yaml 中编写内容相同的 ConfigMap alpha-bad,但 uid 只写 00000000-0000-0000-0000-000000000000。两者都应用后稍等片刻,把 kubectl -n op-own get cm 的输出保存到 /root/op-ownership/gc-uid.txt。

垃圾回收器是靠 uid 而不是引用中的名称来查找父对象的。找不到,就把它看作“父对象已经消失的子对象”并清理掉。由于这种行为,如果控制器缓存了 uid,等父对象被重新创建后再使用,刚创建的子对象就会立刻消失。等待时,与其使用固定的 sleep,不如用一个循环一直运行到对象消失,更安全。

不存在跨命名空间的所有权

创建命名空间 op-own-remote,并在 /root/op-ownership/alpha-remote.yaml 中编写 ConfigMap alpha-remote——命名空间是 op-own-remote,而 ownerReferences 指向(用实际 uid)位于 op-own 中的 Site alpha。应用之后稍等片刻,把 kubectl -n op-own-remote get events 的输出保存到 /root/op-ownership/xns-event.txt。

所有者引用只在同一个命名空间内建立,只有集群范围的对象才能拥有命名空间范围的子对象。违反规则时,API 服务器不会阻止 apply——之后由垃圾回收器处理,并把痕迹留在事件中。请原样读出事件的原因(REASON)这个词。

background 先删除父对象

通过 /root/op-ownership/site-beta.yaml 创建 Site beta(region 为 kr-west),把它的子对象 ConfigMap beta-1 和 beta-2 放入 /root/op-ownership/beta-children.yaml 这一个文件(用 --- 连接),用实际 uid 设置所有者引用并应用。然后用 kubectl -n op-own delete site beta --cascade=background 删除,等子对象消失之后,把 kubectl -n op-own get cm 的输出保存到 /root/op-ownership/cascade-background.txt。

background 是默认值。API 服务器会立即删除父对象,子对象的清理交给垃圾回收器。所以命令很快返回,而子对象会短暂保留——这个时间差就是“明明删了却还能看到”这种错觉的来源。

orphan 保留子对象,只切断联系

通过 /root/op-ownership/site-gamma.yaml 创建 Site gamma(region 为 jp-east),把子对象 ConfigMap gamma-1 和 gamma-2 放入 /root/op-ownership/gamma-children.yaml,用实际 uid 设置后应用。用 kubectl -n op-own delete site gamma --cascade=orphan 删除之后,把 kubectl -n op-own get cm gamma-1 -o jsonpath='{.metadata.ownerReferences}' 的结果,以 gamma-1-ownerrefs=<값, 비어 있으면 none> 一行(占位符为值,为空则写 none)保存到 /root/op-ownership/cascade-orphan.txt。

orphan 不会删除子对象。取而代之的是,垃圾回收器会把子对象上的那个所有者引用摘掉——如果不摘掉,没有父对象的引用会留下来,下次清理时子对象就会消失。这是想撤掉 Operator、同时保留它所创建的资源时使用的方式。

foreground 等待子对象,然后卡住

通过 /root/op-ownership/site-delta.yaml 创建 Site delta(region 为 us-west),并通过 /root/op-ownership/delta-child.yaml 创建 ConfigMap delta-1——在所有者引用中,除了实际 uid,再加入 blockOwnerDeletion: true,并给这个 ConfigMap 自身添加终结器 own.labhub.io/hold。然后执行 kubectl -n op-own delete site delta --cascade=foreground --wait=false,稍后把 kubectl -n op-own get site delta -o json 保存到 /root/op-ownership/foreground-stuck.json。这个 Site 要一直保留到本实验结束。

foreground 会颠倒顺序——要等子对象全部消失之后,才会删除父对象。为了表达这种等待,API 服务器会给父对象加上一个终结器,请在保存的 JSON 中找出它的名称。如果子对象带有终结器,那个子对象就无法消失,等待也就不会结束。

在摘掉终结器之前,删除不会结束

通过 /root/op-ownership/site-epsilon.yaml 创建带有终结器 own.labhub.io/drain 的 Site epsilon(region 为 eu-west),并把子对象 ConfigMap epsilon-data 用实际 uid 设置后,通过 /root/op-ownership/epsilon-child.yaml 应用。执行 kubectl -n op-own delete site epsilon --wait=false,把刚执行之后的对象保存到 /root/op-ownership/finalizer-pending.json,做完清理工作(亲自删除子对象 ConfigMap)之后,在 /root/op-ownership/epsilon-cleanup.txt 中至少用一行写明清理了什么。最后摘掉终结器,让 Site 真正消失。

只要带有终结器,删除请求就只会打上 deletionTimestamp 然后停住。对象仍然可以被查询和修改,但不能重新创建。控制器做的正是这一段——先清理外部系统,再去掉自己的终结器。要从列表中去掉一个元素,使用 kubectl patch --type=json 的 remove 操作比较方便。

诊断卡在 Terminating 的对象

创建 /root/op-ownership/stuck-report.sh——在 op-own 中,(1) 把打上了 deletionTimestamp 的 Site 打印为 STUCK Site/<이름> finalizers=<쉼표로 이은 목록>,(2) 把带有 blockOwnerDeletion 为 true 的所有者引用的 ConfigMap 打印为 BLOCKER ConfigMap/<이름> owner=<종류>/<이름> finalizers=<목록>(占位符依次为名称、用逗号连接的列表、类型、列表),把两类合并排序后只输出到标准输出。只要有一条 STUCK 行,就必须以非 0 的退出码结束;没有的话,打印 NONE 并以 0 结束。把输出保存到 /root/op-ownership/stuck-report.txt,并在 /root/op-ownership/diagnosis.txt 中用人能读懂的语句写明谁在阻止什么、为什么(其中必须包含阻塞的子对象的名称及其终结器的名称)。

诊断的关键是把两个列表并排放在一起——卡住的父对象,以及能够扣住这个父对象的子对象。不要把每次查看都会变化的值(比如存在时长或时刻)放进输出。这样才能把这份报告保存成文件,以后与新的结果比较。