bigywater.tech
Kubernetes · 9

CRD:自定义资源的校验、版本与存储

写一个 CRD 之后,你的自定义资源在 API Server 里到底得到了哪些待遇?这篇把 schema 校验、剪枝、默认值和 status 子资源一项项真的跑了一遍,并直接去 etcd 里把存下来的 JSON 读出来,看默认值是什么时候被补上的。

4 种报错
size=0、缺 size、color=blue、size 写成字符串各自被 schema 拒绝时的报错
{"color":"red","size":3}
etcd 里存的 spec:CRD 事后新增的 shape 默认值不在其中
generation 1 → 2
只有通过主资源改 spec 才会增加,写 status 不会

schema 校验

实测 · 本机执行输出
  [size=3] (0, 'widget.k3.example.com/w1 created')
  读回 w1 的 spec:{"color":"red","size":3}
  [size=0] 退出码 1;spec.size: Invalid value: 0: spec.size in body should be greater than or equal to 1
  [缺少 size] 退出码 1;spec.size: Required value
  [color=blue] 退出码 1;spec.color: Unsupported value: "blue": supported values: "red", "green"
  [size="abc"] 退出码 1;spec.size: Invalid value: "string": spec.size in body must be of type integer: "string"
  kubectl get widgets(附加打印列来自 additionalPrinterColumns):
    NAME   SIZE   COLOR
    w1     3      red

CRD 的 openAPIV3Schema 在提交时就起作用:size=0 报 should be greater than or equal to 1,缺 size 报 Required value,color=blue 报 Unsupported value 并列出允许的值,类型错的报 must be of type integer。合法对象读回来多了 color: red,是 default 补的。kubectl get 的 SIZE、COLOR 两列来自 additionalPrinterColumns。

未知字段

实测 · 本机执行输出
  默认(服务端严格字段校验):退出码 1;Error from server (BadRequest): error when creating "STDIN": Widget in version "v1" cannot be handled as a Widget: strict decoding error: unknown field "spec.extra"
  --validate=false:退出码 0;widget.k3.example.com/w2 created
  读回 w2 的 spec:{"color":"red","size":2}  ← extra 不见了
  --validate=warn:退出码 0;widget.k3.example.com/w2 configured Warning: unknown field "spec.extra"

schema 里没声明的字段不会被保留。kubectl 默认开着服务端严格字段校验,直接报 strict decoding error: unknown field "spec.extra";用 --validate=false 绕过去,请求被接受,但读回来 extra 已经被剪掉(pruning);--validate=warn 则是剪掉并给出 Warning。想保留未知字段要在 schema 里写 x-kubernetes-preserve-unknown-fields: true。

etcd 里存了什么

实测 · 本机执行输出
  etcd key /registry/k3.example.com/widgets/k3/w1
  存进去的 JSON:apiVersion=k3.example.com/v1 kind=Widget spec={"color": "red", "size": 3} metadata 字段:['annotations', 'creationTimestamp', 'generation', 'managedFields', 'name', 'namespace', 'uid']
  给 CRD 新增一个带默认值的字段 spec.shape(default: circle)之后:
    读 w1 的 spec:{"color":"red","shape":"circle","size":3}
    etcd 里 w1 的 spec:{"color": "red", "size": 3}  ← 没有 shape,默认值只在读的时候补

CR 以 JSON 存在 /registry/<group>/<resource>/<ns>/<name>。w1 存进去的 spec 是 {"color": "red", "size": 3}:写入时补的默认值已经固化。之后给 CRD 加一个带默认值的字段 shape,读 w1 马上看到 "shape":"circle",但 etcd 里的 spec 一个字没变——CRD 事后新增的默认值是在读出来的时候补上的,不会改写旧对象。

status 子资源

实测 · 本机执行输出
  初始:generation=1 status=(空)
  patch 主资源写 status.ready=true:widget.k3.example.com/w1 patched;读回 status=(空)
  patch --subresource=status 写同样的内容:widget.k3.example.com/w1 patched;读回 status={"ready":true};generation=1
  通过 status 子资源改 spec.size=9:widget.k3.example.com/w1 patched (no change);读回 spec.size=3
  通过主资源改 spec.size=5:widget.k3.example.com/w1 patched;读回 spec.size=5;generation=2

开了 subresources.status 之后,主资源和 /status 各管各的:往主资源 patch status 被忽略,往 /status patch spec 也被忽略(no change)。metadata.generation 只在 spec 变化时加 1,所以控制器可以用 generation 和 status.observedGeneration 判断自己处理的是不是最新的 spec。

分步演示

CRD 的写入与读出
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);CRD widgets.k3.example.com(v1,spec.size 必填且 1..10,spec.color 枚举 red/green 默认 red,status 子资源,两个附加打印列);etcd 里的 JSON 用 etcd 静态 Pod 里的 etcdctl get --print-value-only 读出;实验后删除了 CRD 与命名空间;每种情形各一次
    • 没有验证:x-kubernetes-preserve-unknown-fields、x-kubernetes-embedded-resource;nullable 与默认值的交互;CEL 校验规则(后面有专门的文章);CRD 的结构化 schema 要求(非结构化 schema 被拒绝);scale 子资源;大对象(超过 etcd 请求大小限制)的行为
    京ICP备2026067502号-1京公网安备11010502063487号