LabHub
学习 学习路径 课程

CRD 与 Operator

把校验甩给 API Server

在 LabHub 中继续学习

目标

故意提交违反规则的资源,亲自收集 API 服务器拒绝了什么以及如何拒绝,并确认默认值注入、pruning 和 CEL 验证都能由同一个 schema 完成。

为什么重要

把验证移入 schema 的真正收益,不只是“拒绝”,而是**“在拒绝时告诉用户哪里不对、为什么不对”**。加入 enum 后,拒绝消息会列出允许值;加入 maximum 后,上限会直接出现在消息中。用户无需翻阅 Wiki,只读错误就能修复。pruning 起初会让人困惑——拼错的字段不会报错,而是悄悄消失。但这正是强制“schema 即契约”的机制。因此,只有确实需要接收任意键时,才应仅在对应位置例外开放。最后,CEL 改变了做法。过去为了实现“prod 至少两个副本”这类跨字段约束,需要运行验证 Webhook 服务器、管理证书,还要承担 Webhook 故障拖垮集群的风险。现在只需在 schema 中写一行。

步骤

开始前准备:每个实验都会启动新的实验 Pod,因此上一实验创建的集群状态不会保留。如果 kubectl get crd webservices.apps.labhub.io 没有结果,请重新把上一实验的 CRD 写入 /root/crd/crd.yaml 并应用,同时执行 kubectl create ns crd-lab。本实验要求的 schema 为:spec.required: [image]replicas(integer,minimum 1,maximum 10,default 1);tier(string,enum [dev, stage, prod]default dev)。第 6、7 步会在此基础上增加 spec.extra 和 CEL 规则。

  1. /root/crd/validate/no-image.yaml 中编写 WebService:metadata.name: no-image,命名空间为 crd-lab,且 spec 中没有 image。尝试应用,并将包含标准错误的失败输出保存到 /root/crd/validate/out/err-required.txt。输出应包含 spec.image 和必填值相关消息,且 no-image 不得留在集群中。
  2. /root/crd/validate/bad-type.yaml 中写入 metadata.name: bad-typespec.image: nginx:1.27spec.replicas: "three",尝试应用,并把失败输出保存到 /root/crd/validate/out/err-type.txt
  3. /root/crd/validate/bad-tier.yaml 中写入 metadata.name: bad-tierspec.image: nginx:1.27spec.tier: qa,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-enum.txt。输出中应同时显示允许值。
  4. /root/crd/validate/too-many.yaml 中写入 metadata.name: too-manyspec.image: nginx:1.27spec.replicas: 50,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-range.txt
  5. /root/crd/validate/defaulted.yaml 中只写 metadata.name: defaultedspec.image绝对不要写 spec.replicasspec.tier。应用后确认已保存对象中自动填入 replicas: 1tier: dev
  6. /root/crd/crd.yaml 的 v1 schema 中添加 properties.spec.properties.extra,设置 type: objectx-kubernetes-preserve-unknown-fields: true,然后重新应用 CRD。接着,在 /root/crd/validate/pruned.yaml 中加入 metadata.name: prunedspec.image 和 schema 中不存在的 spec.bogus: anything 并应用(已保存对象中的 bogus 应消失);再在 /root/crd/validate/preserved.yaml 中加入 metadata.name: preservedspec.imagespec.extra.custom: kept 并应用(该值应保留)。
  7. 在 v1 schema 的 properties.spec 直属位置添加 x-kubernetes-validations 数组。第一条规则的 ruleself.tier != 'prod' || self.replicas >= 2messageprod 계층은 복제본이 2개 이상이어야 합니다。重新应用后,在 /root/crd/validate/cel-violation.yaml 中加入 metadata.name: cel-violationspec.imagespec.tier: prodspec.replicas: 1,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-cel.txt。输出中必须原样包含上述 message。
  8. 创建 /root/crd/validate/out/matrix.json。顶层键为 cases,数组中每个元素包含 nameexpectedrejectedaccepted)、actualrule 四个键。加入 5 个拒绝案例(no-image/required、bad-type/type、bad-tier/enum、too-many/maximum、cel-violation/cel)和 3 个通过案例(defaulted/default、pruned/pruning、preserved/preserve-unknown-fields);所有案例的 expectedactual 都必须相同。

参考

观察缺少必填字段时如何被拒绝

/root/crd/validate/no-image.yaml 中编写 WebService:metadata.name: no-image,命名空间为 crd-lab,且 spec 中没有 image。尝试应用,并将包含标准错误的失败输出保存到 /root/crd/validate/out/err-required.txt。输出应包含 spec.image 和必填值相关消息,且 no-image 不得留在集群中。

这是故意制造失败的步骤。拒绝消息会写入标准错误,因此保存到文件时必须同时捕获标准错误。被拒绝的资源不得留在集群中。

观察类型不匹配时如何被拒绝

/root/crd/validate/bad-type.yaml 中写入 metadata.name: bad-typespec.image: nginx:1.27spec.replicas: "three",尝试应用,并把失败输出保存到 /root/crd/validate/out/err-type.txt

YAML 中用引号包围数字后,它会成为字符串。请保存 schema 期待 integer 时产生的消息。

观察枚举违规及允许值提示

/root/crd/validate/bad-tier.yaml 中写入 metadata.name: bad-tierspec.image: nginx:1.27spec.tier: qa,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-enum.txt。输出中应同时显示允许值。

enum 违规消息不仅拒绝请求,还会提示可用选项。输出中必须包含该列表。

观察超出范围时如何被拒绝

/root/crd/validate/too-many.yaml 中写入 metadata.name: too-manyspec.image: nginx:1.27spec.replicas: 50,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-range.txt

请提供超过 maximum 的值。消息中会直接提到上限。

确认未填写字段会被注入默认值

/root/crd/validate/defaulted.yaml 中只写 metadata.name: defaultedspec.image绝对不要写 spec.replicasspec.tier。应用后确认已保存对象中自动填入 replicas: 1tier: dev

若在清单中填写值,就无法确认是否由默认机制注入。请留空这两个字段,然后重新读取已保存对象进行比较。

观察未知字段的剪除与例外保留

/root/crd/crd.yaml 的 v1 schema 中添加 properties.spec.properties.extra,设置 type: objectx-kubernetes-preserve-unknown-fields: true,然后重新应用 CRD。接着,在 /root/crd/validate/pruned.yaml 中加入 metadata.name: prunedspec.image 和 schema 中不存在的 spec.bogus: anything 并应用(已保存对象中的 bogus 应消失);再在 /root/crd/validate/preserved.yaml 中加入 metadata.name: preservedspec.imagespec.extra.custom: kept 并应用(该值应保留)。

schema 中不存在的字段会在保存前被剪除。需要接收任意键的位置,应先在 schema 中定义为对象,再标记为保留未知字段。该标记是以 x- 开头的扩展键。

使用 CEL 表达跨字段约束

在 v1 schema 的 properties.spec 直属位置添加 x-kubernetes-validations 数组。第一条规则的 ruleself.tier != 'prod' || self.replicas >= 2messageprod 계층은 복제본이 2개 이상이어야 합니다。重新应用后,在 /root/crd/validate/cel-violation.yaml 中加入 metadata.name: cel-violationspec.imagespec.tier: prodspec.replicas: 1,尝试应用,并把失败输出保存到 /root/crd/validate/out/err-cel.txt。输出中必须原样包含上述 message。

这条规则无法只查看一个字段来判断。请在 spec 对象层级放置规则数组,并在规则中用 self 指代当前对象。message 是用户会看到的语句,因此会原样出现在错误中。

建立验证矩阵并与实际结果对照

创建 /root/crd/validate/out/matrix.json。顶层键为 cases,数组中每个元素包含 nameexpectedrejectedaccepted)、actualrule 四个键。加入 5 个拒绝案例(no-image/required、bad-type/type、bad-tier/enum、too-many/maximum、cel-violation/cel)和 3 个通过案例(defaulted/default、pruned/pruning、preserved/preserve-unknown-fields);所有案例的 expectedactual 都必须相同。

把之前步骤创建的案例整理成表。每个案例写明名称、预期、实际结果及触发的规则,预期与实际不能有任何一项不同。