把副本数调大了却什么都没发生 - scale 子资源的约定
目标
用三个路径为 CRD 的 subresources.scale 正确建立契约,亲手制造缺少选择器路径和路径中有拼写错误两种情况下各自出现的沉默,然后编写能够发现这种沉默的检查脚本。
为什么重要
kubectl scale 和 HPA 不知道目标的类型。它们所知道的,只有 scale 子资源这一扇共同的窗口,而 CRD 用三个 JSONPath 把这扇窗口打开。所以,编写 Operator 的人如果把这三行写对,自己的类型就能免费接入 Kubernetes 现有的所有自动扩缩容工具;写错的话,这些工具就会不报任何错误,什么也不做。这种沉默就是本模块的主题。路径中有拼写错误时,kubectl scale 会回答成功,却不会修改值;缺少选择器路径时,HPA 会成功读取目标,却永远不会调节。两者在有人去看界面之前都不会暴露,所以必须同时准备好能放进部署流水线的检查方法。
步骤
- 在
/root/op-scale/shard-crd.yaml中编写 CRDshards.scale.labhub.io——group 为scale.labhub.io,kind 为Shard,复数形式为shards,只有一个版本 v1(served 和 storage 均为 true),在 schema 中放入spec.replicas(integer)、status.replicas(integer)、status.selector(string),并在subresources中启用status: {}和scale。scale 的三个路径分别是.spec.replicas、.status.replicas、.status.selector。应用之后,把集群中登记的三个路径以<이름>=<값>三行的形式(占位符依次为名称与值)保存到/root/op-scale/scale-paths.txt(specReplicasPath=...、statusReplicasPath=...、labelSelectorPath=...)。 - 创建命名空间
op-scale,并在/root/op-scale/shard-a.yaml中编写 Sharda——spec.replicas为 2。应用之后,把kubectl -n op-scale get shard a --subresource=scale -o yaml的输出原样保存到/root/op-scale/scale-initial.yaml。 - 用
kubectl -n op-scale scale shard a --replicas=5把 Sharda的期望数量提高到 5。然后从 scale 子资源中取出两个数字,以spec.replicas=5和status.replicas=0两行的形式保存到/root/op-scale/after-scale.txt。 - 替控制器做它该做的事,写入 status——用
kubectl -n op-scale patch shard a --subresource=status把status.replicas填为 5,把status.selector填为app=shard-a。然后把 scale 子资源的输出保存到/root/op-scale/scale-ready.yaml。 - 在
/root/op-scale/shard-hpa.yaml中编写autoscaling/v2的 HPAshard-a-hpa——scaleTargetRef的 apiVersion 为scale.labhub.io/v1,kind 为Shard,name 为a,minReplicas 为 2,maxReplicas 为 10,指标是 Resource cpu 的 Utilization 70。应用后等待条件被填充,然后用两行保存到/root/op-scale/hpa-status.txt——AbleToScale=<상태>/<이유>(占位符依次为状态与原因)和REFERENCE=<kubectl get hpa 의 REFERENCE 칸>(占位符为 kubectl get hpa 输出中 REFERENCE 一栏的内容)。 - 在
/root/op-scale/block-crd.yaml中编写 CRDblocks.scale.labhub.io(kind 为Block,复数形式为blocks),但在 scale 中不加入labelSelectorPath。通过/root/op-scale/block-b.yaml创建 Blockb(spec.replicas为 3),通过/root/op-scale/block-hpa.yaml创建 HPAblock-b-hpa(minReplicas 为 1,maxReplicas 为 6,cpu 70)并应用,条件被填充后,用两行保存到/root/op-scale/noselector.txt——ScalingActive=<상태>/<이유>(占位符依次为状态与原因)和MESSAGE=<조건 메시지>(占位符为条件消息)。 - 在
/root/op-scale/relay-crd.yaml中编写 CRDrelays.scale.labhub.io(kind 为Relay,复数形式为relays),但故意把specReplicasPath写成.spec.replica(去掉末尾 s 的拼写错误)。通过/root/op-scale/relay-r.yaml创建 Relayr(spec.replicas为 2)并应用,然后依次执行kubectl -n op-scale scale relay r --replicas=4和kubectl -n op-scale get relay r --subresource=scale -o yaml,把结果汇总到/root/op-scale/typo-report.txt——必须有scale-rc=<종료 코드>(占位符为退出码)、spec.replicas=<명령 뒤 실제 값>(占位符为命令执行后的实际值),以及一行原样写入第二条命令的错误语句的内容。 - 在
/root/op-scale/scale-audit.tsv中写入三行<CRD 이름><탭><기대 분류>(占位符依次为 CRD 名称、制表符、预期分类)——shards.scale.labhub.io为ok,blocks.scale.labhub.io为nosel,relays.scale.labhub.io为ok。/root/op-scale/scale-audit.sh要读取这张表,把 CRD 分为ok、nosel、broken,符合预期时只向标准输出打印OK …,不符合时只向标准输出打印MISMATCH …,只要有一行不符就必须以非 0 的退出码结束。先在修复之前运行一次,把输出保存到/root/op-scale/scale-audit-before.txt;然后通过/root/op-scale/relay-crd-fixed.yaml修复拼写错误并应用,确认kubectl -n op-scale scale relay r --replicas=4这次是否真的修改了值,再次运行并把输出保存到/root/op-scale/scale-audit.txt。
参考
- 三个路径是
specReplicasPath、statusReplicasPath、labelSelectorPath,值以点号开头。 - 要写入 status,需要使用
kubectl patch … --subresource=status。 - HPA 的判定会记录在
status.conditions的AbleToScale和ScalingActive两个条件中。 - 这个环境中没有指标服务器,所以 HPA 的 TARGETS 始终显示未知——这是正常的。
- 常见错误:漏掉选择器路径,却因为 HPA 已经挂上而放心。
- 常见错误:只看
kubectl scale的退出码,就相信值已经改变。 - 参考:https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
- 参考:https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
用三个路径建立契约
在 /root/op-scale/shard-crd.yaml 中编写 CRD shards.scale.labhub.io——group 为 scale.labhub.io,kind 为 Shard,复数形式为 shards,只有一个版本 v1(served 和 storage 均为 true),在 schema 中放入 spec.replicas(integer)、status.replicas(integer)、status.selector(string),并在 subresources 中启用 status: {} 和 scale。scale 的三个路径分别是 .spec.replicas、.status.replicas、.status.selector。应用之后,把集群中登记的三个路径以 <이름>=<값> 三行的形式(占位符依次为名称与值)保存到 /root/op-scale/scale-paths.txt(specReplicasPath=...、statusReplicasPath=...、labelSelectorPath=...)。
这三个路径是告诉 API 服务器“在这个类型中,期望数量在哪里、实际数量在哪里、用来选取 Pod 的选择器在哪里”的契约。路径是以点号开头的 JSONPath,只允许点号表示法(不能使用数组表示法)。用 kubectl get crd shards.scale.labhub.io -o jsonpath 取出登记的值,就能避免手抄出错。
scale 子资源看起来是什么样
创建命名空间 op-scale,并在 /root/op-scale/shard-a.yaml 中编写 Shard a——spec.replicas 为 2。应用之后,把 kubectl -n op-scale get shard a --subresource=scale -o yaml 的输出原样保存到 /root/op-scale/scale-initial.yaml。
子资源就是用另一扇窗口来查看同一个对象。返回的不是 Shard,而是 autoscaling/v1 的 Scale 对象,其中只有 spec.replicas 一项和 status.replicas 一项。目前还没有人写过 status,请预想一下 status 一侧的数字会是多少。
kubectl scale 会修改什么
用 kubectl -n op-scale scale shard a --replicas=5 把 Shard a 的期望数量提高到 5。然后从 scale 子资源中取出两个数字,以 spec.replicas=5 和 status.replicas=0 两行的形式保存到 /root/op-scale/after-scale.txt。
kubectl scale 并不是 Deployment 专用的命令——对所有启用了 scale 子资源的类型,它都同样有效。请思考两个数字为什么不同。specReplicasPath 是用户写入的位置,statusReplicasPath 是控制器写入的位置,而这个集群中没有照管 Shard 的控制器。
手动填写控制器的位置
替控制器做它该做的事,写入 status——用 kubectl -n op-scale patch shard a --subresource=status 把 status.replicas 填为 5,把 status.selector 填为 app=shard-a。然后把 scale 子资源的输出保存到 /root/op-scale/scale-ready.yaml。
status 是单独的端点,所以用普通的 patch 写不进去。必须加上 --subresource=status 才能进入那扇窗口。选择器是一行字符串,直接使用标签选择器语法(키=값,韩文,意为“键=值”)——这个值以后会成为 HPA 统计 Pod 的依据。
把 HPA 挂到自定义资源上
在 /root/op-scale/shard-hpa.yaml 中编写 autoscaling/v2 的 HPA shard-a-hpa——scaleTargetRef 的 apiVersion 为 scale.labhub.io/v1,kind 为 Shard,name 为 a,minReplicas 为 2,maxReplicas 为 10,指标是 Resource cpu 的 Utilization 70。应用后等待条件被填充,然后用两行保存到 /root/op-scale/hpa-status.txt——AbleToScale=<상태>/<이유>(占位符依次为状态与原因)和 REFERENCE=<kubectl get hpa 의 REFERENCE 칸>(占位符为 kubectl get hpa 输出中 REFERENCE 一栏的内容)。
HPA 控制器不需要知道目标的类型。只要能读取 scale 子资源,就能挂上。条件可以用 kubectl -n <ns> get hpa <이름> -o jsonpath 取出(占位符依次为命名空间与 HPA 名称),而在没有指标服务器的这个环境中,TARGETS 始终显示未知——那与“是否能读取目标”是两回事。
去掉选择器路径,HPA 就会悄悄停下
在 /root/op-scale/block-crd.yaml 中编写 CRD blocks.scale.labhub.io(kind 为 Block,复数形式为 blocks),但在 scale 中不加入 labelSelectorPath。通过 /root/op-scale/block-b.yaml 创建 Block b(spec.replicas 为 3),通过 /root/op-scale/block-hpa.yaml 创建 HPA block-b-hpa(minReplicas 为 1,maxReplicas 为 6,cpu 70)并应用,条件被填充后,用两行保存到 /root/op-scale/noselector.txt——ScalingActive=<상태>/<이유>(占位符依次为状态与原因)和 MESSAGE=<조건 메시지>(占位符为条件消息)。
关键在于它与上一步有什么不同。读取目标(AbleToScale)成功了,却没有开始调节。HPA 要计算目标利用率,就必须知道“这个工作负载的 Pod 是哪些”,而提供这个答案的位置,正是被漏掉的那个路径。请直接阅读条件消息。
回答成功,实际什么也没做
在 /root/op-scale/relay-crd.yaml 中编写 CRD relays.scale.labhub.io(kind 为 Relay,复数形式为 relays),但故意把 specReplicasPath 写成 .spec.replica(去掉末尾 s 的拼写错误)。通过 /root/op-scale/relay-r.yaml 创建 Relay r(spec.replicas 为 2)并应用,然后依次执行 kubectl -n op-scale scale relay r --replicas=4 和 kubectl -n op-scale get relay r --subresource=scale -o yaml,把结果汇总到 /root/op-scale/typo-report.txt——必须有 scale-rc=<종료 코드>(占位符为退出码)、spec.replicas=<명령 뒤 실제 값>(占位符为命令执行后的实际值),以及一行原样写入第二条命令的错误语句的内容。
这一步要看的不是“发生了什么”,而是“什么都没有发生,为什么看上去却像成功了”。把退出码和实际值分开写下来,两者对不上这一点就一目了然。第二条命令的错误输出到标准错误,所以请用 2>&1 一起获取。
制作能发现悄无声息的故障的检查表
在 /root/op-scale/scale-audit.tsv 中写入三行 <CRD 이름><탭><기대 분류>(占位符依次为 CRD 名称、制表符、预期分类)——shards.scale.labhub.io 为 ok,blocks.scale.labhub.io 为 nosel,relays.scale.labhub.io 为 ok。/root/op-scale/scale-audit.sh 要读取这张表,把 CRD 分为 ok、nosel、broken,符合预期时只向标准输出打印 OK …,不符合时只向标准输出打印 MISMATCH …,只要有一行不符就必须以非 0 的退出码结束。先在修复之前运行一次,把输出保存到 /root/op-scale/scale-audit-before.txt;然后通过 /root/op-scale/relay-crd-fixed.yaml 修复拼写错误并应用,确认 kubectl -n op-scale scale relay r --replicas=4 这次是否真的修改了值,再次运行并把输出保存到 /root/op-scale/scale-audit.txt。
检查表的价值由“修复前”的输出来证明。如果只留下修复之后全部为 OK 的结果,就没有人知道这项检查能发现什么。仅看 CRD 定义来分类是不够的——路径是否真的指向实际对象,必须读一次 scale 子资源才能知道。如果脚本直接写文件,再次运行时会覆盖产物,所以只向标准输出输出。