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

CRD 与 Operator

kubectl scale 是怎么认识我自定义的类型的

在 TT Lab 中继续学习

一句话总结

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 分类的检查脚本,并把修复前后的输出并排保存。