bigywater.tech
Kubernetes · 94

CRD 的 CEL 校验:x-kubernetes-validations

不用 webhook,怎么给 CRD 加上“min 不能大于 max”这类跨字段校验?怎么让字段创建后不可修改?为什么有的 CEL 规则连 CRD 都创建不了?这篇用一个 Range 资源把规则、迁移规则和成本限制各自跑一遍。

oldSelf
CEL 里引用更新前的值,实现“创建后不可变”“不能降级”等迁移规则
>100x
规则的静态估算成本超出预算 100 倍时,CRD 创建就被拒绝
has()
可选字段上的规则需要 has(self.x) 保护,否则字段缺失时规则出错

跨字段规则

实测 · 本机执行输出
  min=1 max=10:退出码 0;['range.k88.example.com/ok created']
  min=20 max=10:退出码 1;['The Range "bad" is invalid: spec.max: Invalid value: min 不能大于 max']
  mode=strict max=500(带 messageExpression 的规则):退出码 1;['The Range "strict" is invalid: spec: Invalid value: strict 模式下 max 不能超过 100(mode=strict)']
  两条规则同时违反:['The Range "both" is invalid:', '* spec.max: Invalid value: min 不能大于 max', '* spec: Invalid value: strict 模式下 max 不能超过 100(mode=strict)']
  不写 mode(规则里用 has(self.mode) 保护):退出码 0;['range.k88.example.com/nomode created']

CRD 的 spec 上两条规则:self.min <= self.max(message: min 不能大于 max,fieldPath: .max)和 !has(self.mode) || self.mode != 'strict' || self.max <= 100(messageExpression 拼出消息)。min=1 max=10 通过;min=20 max=10 被拒:The Range "bad" is invalid: spec.max: Invalid value: min 不能大于 max——错误挂在 fieldPath 指的 .max 上;mode=strict max=500 被拒;两条同时违反时一次列出两条;不写 mode 时,has() 保护了规则,创建通过。

迁移规则

实测 · 本机执行输出
  把 owner 从 alice 改成 bob:退出码 1;['The Range "imm" is invalid: spec.owner: Invalid value: "bob": owner 创建后不可修改']
  只改 max(owner 没变):退出码 0;['range.k88.example.com/imm patched']
  tier dev → prod:退出码 0;['range.k88.example.com/imm patched']
  tier prod → dev:退出码 1;['The Range "imm" is invalid: spec.tier: Invalid value: "dev": prod 不能降级']
  创建时没有 owner,之后第一次设置 owner(oldSelf 不存在,规则不执行):退出码 0;['range.k88.example.com/noowner patched']

oldSelf 是更新前的值,只在 UPDATE 时存在。owner 的规则 self == oldSelf:把 alice 改成 bob 被拒(spec.owner: Invalid value: "bob": owner 创建后不可修改),只改 max 通过。tier 的规则 oldSelf != 'prod' || self == 'prod':dev → prod 通过,prod → dev 被拒(prod 不能降级)。创建时没有 owner 的对象,之后第一次设置 owner 通过——oldSelf 不存在时迁移规则不执行。

成本限制

实测 · 本机执行输出
  list 没有 maxItems、string 没有 maxLength,规则 self.all(x, self.exists_one(y, x == y)):退出码 1
    The CustomResourceDefinition "namesets.k88.example.com" is invalid:
    * spec.validation.openAPIV3Schema.properties[spec].properties[names].x-kubernetes-validations[0].rule: Forbidden: estimated rule cost exceeds budget by factor of more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and maxLength where arrays, maps, and strings are declared)
    * spec.validation.openAPIV3Schema.properties[spec].properties[names].x-kubernetes-validations[0].rule: Forbidden: contributed to estimated rule cost total exceeding cost limit for entire OpenAPIv3 schema
  加上 maxItems: 20 和 maxLength: 20:退出码 0;['customresourcedefinition.apiextensions.k8s.io/namesets.k88.example.com created']
  names: [a, b, a]:退出码 1;['The NameSet "dup" is invalid: spec.names: Invalid value: names 里有重复']
  25 个元素(超过 maxItems 20):['The NameSet "toolong" is invalid:', '* spec.names: Too many: 25: must have at most 20 items']
  另一个意外:messageExpression 里写 string(self.max)(把整数转字符串拼进消息):退出码 1;['The CustomResourceDefinition "ranges2.k88.example.com" is invalid:', '* spec.validation.openAPIV3Schema.properties[spec].x-kubernetes-validations[1].messageExpression: Forbidden: estimated messageExpression cost exceeds budget by factor of more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and maxLength where arrays, maps, and strings are declared)']

没有上限的数组和字符串会让 CRD 本身被拒:names 是没有 maxItems 的数组、元素是没有 maxLength 的字符串,规则 self.all(x, self.exists_one(y, x == y)):CRD 创建失败,estimated rule cost exceeds budget by factor of more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and maxLength …)。加上 maxItems: 20 和 maxLength: 20 后创建成功;names: [a, b, a] 被规则拒绝(names 里有重复);25 个元素被 schema 拒绝(Too many: 25: must have at most 20 items)。还有一个意外:messageExpression 里写 string(self.max) 把整数转字符串拼进消息,同样被判成本超预算(用 self.mode 这样有 maxLength 的字符串就没问题)。

分步演示

CRD CEL 校验
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);自建 CRD ranges.k88.example.com 与 namesets.k88.example.com(kind 起初叫 List,与内置 List 冲突后改名 NameSet);对象用 kubectl apply / patch 提交;成本限制的报错来自创建 CRD 的 API 响应;每种情形各一次
    • 没有验证:optionalOldSelf;ratcheting validation(旧值已违反规则时放行未改动字段);扩展库 URL、quantity 等 CEL 函数;规则里的 reason 字段(FieldValueForbidden / FieldValueRequired 等)对 HTTP 状态的影响;listType=map 与唯一性规则的配合;string(int) 为什么被估算为超预算(推测与整数转字符串的长度估算有关)
    京ICP备2026067502号-1京公网安备11010502063487号