kubectl scale 是怎么认识我自定义的类型的
一句话总结
kubectl scale 和 HPA 在工作时并不知道目标的类型。它们所知道的,只有 scale 子资源这一扇共同的窗口,而 CRD 用 specReplicasPath、statusReplicasPath、labelSelectorPath 这三行把这扇窗口打开。
为什么需要这份契约
Kubernetes 中有多种类型具有“副本数”这个概念:Deployment、ReplicaSet、StatefulSet、ReplicationController,还有数不清的 CRD。它们的字段名可能各不相同,实际上也确实如此。但 kubectl scale 只有一条命令,HPA 控制器也只有一个。如果这些工具为每种类型都有不同的代码,那么每出现一个新的 CRD,就得修改 kubectl。所以 Kubernetes 选择了相反的方向——工具保持固定,由类型自己把自己翻译出来。
这个翻译的结果就是 autoscaling/v1 的 Scale 对象。任何类型只要启用 scale 子资源,GET .../shards/a/scale 返回的就不是 Shard,而是 Scale。其中只有 spec.replicas 一项、status.replicas 一项,以及一个 status.selector 字符串。kubectl 和 HPA 都只看这三项。
工作原理
在 CRD 的每个版本中,于 subresources.scale 之下写明三个路径。
subresources:
status: {}
scale:
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas
labelSelectorPath: .status.selector
| 路径 | 谁来写 | 缺失或写错时 |
|---|---|---|
specReplicasPath |
用户、kubectl、HPA 写入 | 必填。必须位于 .spec 之下。如果实际对象中没有这个值,子资源会报错 |
statusReplicasPath |
控制器写入 status | 必填。必须位于 .status 之下。如果对象中没有值,实际数量会显示为 0 |
labelSelectorPath |
控制器写入 status,HPA 读取 | 可选。缺失时 HPA 无法开始调节 |
三个路径都只允许点号表示法,不能使用数组表示法。labelSelectorPath 所指的位置必须是一个字符串而不是结构体,其中存放的是标签选择器序列化之后的形式(app=web)。
这里有一点必须一并记住。statusReplicasPath 所指的位置必须位于 status 子资源之内,labelSelectorPath 通常也放在那里(位于 .spec 之下也是允许的,但选择器是由控制器计算后填入的值,所以 status 才是它该在的位置)。因此,如果不同时启用 status: {},控制器就根本没有可以写入这个值的窗口;即使启用了,用普通的 kubectl patch 也写不进去——必须加上 --subresource=status。在没有控制器的集群中执行 kubectl scale,只有期望数量会变成 5,实际数量仍停留在 0,这不是故障,而是说明还没有人填写那个位置。
HPA 这一侧更有意思一些。HPA 通过 scaleTargetRef 中写明的 apiVersion、kind、name 找到目标,并读取该目标的 scale 子资源。这个过程是否成功,会记录在 status.conditions 的 AbleToScale 中;而能否根据指标计算并真正进行调节,则记录在 ScalingActive 中。这是两个不同的问题。如果缺少选择器路径,AbleToScale 为 True(SucceededGetScale),ScalingActive 却是 False(InvalidSelector),消息会说目标的 scale 中没有选择器。在界面列表中,HPA 看上去仍是完好的一行。
在现场相遇的样子
第一,回答“成功”的失败。如果把 specReplicasPath 错写一个字母,例如 .spec.replica,kubectl scale 会以退出码 0 输出 scaled。但对象的值并没有变。对同一个 CRD 执行 kubectl get … --subresource=scale,才会出现“the spec replicas field ... does not exist”。如果部署脚本只看退出码就进入下一步,这个故障可以存活好几个月。
第二,已经挂上却不调节的 HPA。给缺少选择器路径的 CRD 挂上 HPA,不会有任何人看到错误。负载升高了,副本数却不变。人们通常先怀疑指标管道,而实际原因是 CRD 那三行中缺了一行。在事故复盘中,之所以要花很长时间才能找到这个原因,是因为症状是“自动扩缩容不生效”,所以排查范围首先就朝指标方向展开。
第三,因此要把检查自动化。开发 CRD 的团队,值得在 CI 中确认两件事——实际读取一次 scale 子资源,以及如果是要用自动扩缩容的类型,检查是否有选择器路径。只读定义,是无法知道第一件事的。路径是否真的能指向实际对象,只有去询问才能知道。
本实验环境的局限
实验 Pod 所在的集群中没有指标服务器(metrics-server)。所以 HPA 的 TARGETS 一栏始终显示未知,无法看到副本数随负载上下浮动的样子。不过 HPA 控制器本身是在运行的,所以“是否读取到了目标”“为什么没能开始调节”都会如实记录在条件中——本模块所涉及的失败,全都会留在那个位置。此外,没有照管 Shard 的控制器,所以 status 要由人直接填写,来模拟控制器的位置。
下一项实验要做什么
创建一个具备三个路径的 CRD,挂上 kubectl scale 和 HPA,再分别创建缺少选择器路径的 CRD 和路径中有拼写错误的 CRD,通过条件消息确认各自会出现什么样的沉默。最后把这三个 CRD 整理成表格,编写按 ok、nosel、broken 分类的检查脚本,并把修复前后的输出并排保存。