LabHub
学习 学习路径 课程

CRD 与 Operator

schema、子资源、版本 — CRD 的三条轴

在 LabHub 中继续学习

一句话总结

编写 CRD 不是列出字段,而是设计能把多少验证工作交给 API Server

概念图: 能把多少验证工作交给 API Server · 同时列出允许的值 · 1) structural schema。 · pruning 是默认行为。

为什么需要了解这一点

当控制器代码开始堆积 if spec.Replicas < 1 { return error } 之类的检查时,会出现两个问题。第一,检查发生得太晚。错误对象已经存入 etcd,用户却以为 kubectl apply 成功了。第二,规则没有记录在任何文档中。用户必须阅读控制器日志,才能知道哪里出错。

把规则移到 schema 后,情况正好相反:kubectl apply 会当场失败,错误消息会说明哪个字段为什么无效;如果是枚举,甚至会同时列出允许的值。而且 kubectl explain webservice.spec 本身就会成为文档。

工作原理

支撑 CRD 的轴有三个。

1) structural schema。 Kubernetes 1.16 以后,所有 CRD 都必须用 OpenAPI v3 明确声明每个字段的类型。满足这一条件后,pruning、默认值注入、Server-Side Apply 和 CEL 验证才能工作。这里必须清楚区分三种情况。

分类 含义 使用场景
required 为空就拒绝 没有合理默认值的核心标识符
default 为空时由 API Server 填充 大多数人会使用相同值的字段
什么也不加 允许为空,也不填充 真正的可选功能

可以提供默认值的字段若硬设为 required,用户每次都要重复写相同的样板值,以后也很难再降级为可选字段。反之,像镜像这样错误默认值会造成风险的字段,明确拒绝更好。

pruning 是默认行为。 schema 中不存在的字段会在保存前被裁掉。拼错的字段悄然消失,起初可能令人困惑,但这正是强制执行“schema 即契约”的机制。需要接收任意键值的位置,可以用 x-kubernetes-preserve-unknown-fields: true 开放例外,但只应开放最小范围。滥用会彻底失去 structural schema 的好处。

2) 子资源。 启用 status 子资源后,spec 与 status 会成为不同端点。用户只写 spec,控制器只写 status。由此产生一个关键性质:写入 status 不会增加 metadata.generation 因而控制器能区分“用户修改了 spec”和“我刚刚写入 status”,这是避免无限 reconcile 的基础。

scale 子资源只需提供 specReplicasPathstatusReplicasPathlabelSelectorPath 三条路径,就能让 kubectl scale 与 HPA 支持自定义类型。需要 selector 路径,是因为 HPA 要用该 selector 统计 Pod。

3) 版本。 一个 CRD 可同时提供多个版本,每个版本都有两个标志。

存储只采用一种表示,因此所有版本之间必须能够无损转换。status.storedVersions 会记录“曾经用于保存该 CRD 对象的版本”。切换存储版本后,若不重新保存现有对象,旧版本会继续留在列表中;此时删除旧 schema,就可能无法读取已存储对象。大多数版本删除事故,都是跳过了重新保存这一步。

本练习环境无法提供 webhook 端点,因此不讨论 conversion webhook。我们会在 strategy: None 下同时提供多个版本,并确认存储版本规则。

实际工作中的表现

第一,第一天就可能卡在 CRD 命名规则。 metadata.name 必须是 <복수형>.<그룹>。只写 webservices 会被 API Server 拒绝。练习提供的损坏 CRD 正是这种情况。之所以有此规则,是因为 CRD 本身是集群范围资源,名称就是全局唯一键。

第二,自定义列会改变运维质量。 执行 kubectl get webservices 时,如果只显示 NAME 与 AGE,就没人愿意使用该命令。把故障处理中操作人员最想知道的两三个值设为列,一条命令就能变成仪表板。

第三,枚举就是文档。 添加 enum 后,拒绝消息会同时列出允许值。用户无需翻找 Wiki,只读错误消息就能得到答案。把验证移到 schema 的真正收益,不只是拒绝错误,而是提供这种指引。

下个练习将做什么

接下来有两个练习。第一个练习从零编写 apps.labhub.io/v1WebService——包括命名规则、schema、自定义列、status 与 scale 子资源,以及 v1alpha1 与 v1 两个版本。第二个练习会故意提交违反规则的资源,收集 API Server 的拒绝措辞;亲眼确认默认值注入与 pruning 后,再通过 CEL 规则把字段间约束加入 schema,并制作验证矩阵。