KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

01 · 控制器 — keel 龙骨

类型:主题概念 · 应用课对应:01 章 · 声明式与调谐

类型:主题概念 · 应用课对应:01 章 · 声明式与调谐

这一页只回答四件事:它为什么会出现、它是什么、它长什么样、它怎么工作。

一、它为什么会出现

两个现象,都指向同一件事:

① apply 返回了 created,Pod 却还在 ContainerCreating
② 删掉一个 Pod,没人敲任何命令,过一会儿它自己回来了

这两个现象里都有一个"没人敲命令"的间隙。第一个间隙是时间,第二个间隙是动作——
而且那个动作还带方向:它把"少了 1 个"补成了"正好 2 个"。

问题在于:如果"达成"这件事必须由命令来推动,那它就退回到了命令式的老路——
你交完文件还得盯着它,或者写一堆"检查一下、没有就创建"的判断。那不叫省事。

它面对的需求是:让"达成期望状态"这件事自己发生,且不需要任何人(或任何脚本)去触发它。

二、它是什么

一句话:控制器就是一段一直在跑的循环——读一遍你声明的期望状态,再读一遍现在的实际状态,
两个不一样就动手把差值缩小,然后回到第一步。

它不是这三样东西——混淆会带来误判:

三、它长什么样

它自己没有输出、没有界面。想看见它,只能从它做过的事回头看。
好在集群把"谁做的"记在了一个地方——事件里的 From 那一列。

一个 Deployment 刚把它要的副本数拉满时(kubectl describe deploy web):

Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  11s   deployment-controller  Scaled up replica set web-565bc6668 from 0 to 2

From 那一列写着 deployment-controller,Message 里写着"从 0 拉到 2"——
这就是那次差值被算出来、并被执行的痕迹。 没有人下过这条命令。

同一份清单里的 Pod 拿到的事件,会让流水线上另外两个角色露出来:

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  10s   default-scheduler  Successfully assigned default/web-xxxx to keel-lab-control-plane
  Normal  Pulled     10s   kubelet            spec.containers{web}: Container image "..." already present on machine ...
  Normal  Created    10s   kubelet            spec.containers{web}: Container created
  Normal  Started    9s    kubelet            spec.containers{web}: Container started

三条 From 是三个不同的角色:default-scheduler 决定放在哪台机器,
kubelet 负责在那台机器上把它造出来。
控制器不干这两件事——它只管"该有几个"。

它认到的结果写在对象的 status 里,你用列表就能读到:

NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    2/2     2            2           55s

READY 2/2 不是哪个容器报上来的,是控制器数完写下的结论。

四、它怎么工作

一轮循环,六步:

① 读期望   读你写下的那份(要 2 个、用哪个镜像)
② 读实际   从集群里查现在到底是什么样(只有 1 个在跑)
③ 算差值   期望减实际(-1)
④ 动手     采取行动缩小差值(再造一个)
⑤ 写结论   把"我认到多少"写回对象的状态(READY 2/2)
⑥ 回到①   不等谁叫它,直接开始下一轮

第 ⑥ 步是全部要点:它没有"没事做"这个状态。 差值算出来是 0 的时候,
它什么都不做——但那一轮已经跑过了,下一轮还是会来。

第 ④ 步还有一个容易被忽略的性质:它不会去修原来那个东西。
少一个就造一个新的,容器坏了就把整个重造。对它来说"修"和"再造"没有区别。

五、它会带出哪些概念

六、回到应用课

应用课里的那句话 现在你知道它为什么成立
01 章:"kubectl apply 成功后,你只完成了『把期望状态说清楚』这一步" 因为"达成"由那个一直在跑的循环负责,它不在 apply 的调用栈里——返回成功时它可能还没跑到下一轮
01 章:"调谐(reconciliation)就是控制器不停做的那件事" 它就是第 ④ 步到第 ⑥ 步那个闭环;"不停"指的是第 ⑥ 步没有出口,不是指它很忙
01 章:"手工删掉一个由 Deployment 管理的 Pod……会恢复" 不是"删除"这个动作被监听了;是下一轮它读到的实际值比期望值少了 1,于是差值出现了
01 章:"Pending 的 Pod 等一会儿就好了" 这一轮循环跑得没错,是第 ② 步读到的现实(没有节点装得下)一直没变——差值不为零,但它没有手段缩小它

这一页改变了你的哪个判断:以后看到一个"卡住"的对象,先问它卡在第几步——
不要去问"命令有没有执行成功"——是期望没被写进去、差值没被算出来,
还是它算出了差值却没有手段消除它。这三件事的排查方向完全不同。

自测题

  1. 为什么"重新运行一次控制器"这个说法是不成立的?
  2. kubectl describe deploy 的事件里,From 那一列写着 deployment-controller。为什么这一列能证明它一直在跑?

进入 keel 阅读