From 6960097befe155b79f2d53d647e8d2d1e80a24f7 Mon Sep 17 00:00:00 2001 From: Abirdcfly Date: Mon, 16 May 2022 00:19:56 +0800 Subject: [PATCH] [zh] sync custom-resource-definitions.md Signed-off-by: Abirdcfly Co-authored-by: Qiming Teng --- .../custom-resource-definitions.md | 799 +++++++++++++++++- 1 file changed, 791 insertions(+), 8 deletions(-) diff --git a/content/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions.md b/content/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions.md index 76ac25c924..0f60c649f1 100644 --- a/content/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions.md +++ b/content/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions.md @@ -546,7 +546,7 @@ resource definitions to: * 裁剪未启用。 * 可以存储任意数据。 -为了与 `apiextensions.k8s.io/v1` 兼容,将你的自定义资源定义更新为: +为了与 `apiextensions.k8s.io/v1` 兼容,将你的定制资源定义更新为: 1. 使用结构化的 OpenAPI 模式。 2. `spec.preserveUnknownFields` 设置为 `false`。 @@ -902,15 +902,16 @@ Kubernetes 会最终删除该资源, ### Validation Custom resources are validated via -[OpenAPI v3 schemas](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) -and you can add additional validation using +[OpenAPI v3 schemas](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject), +by x-kubernetes-validations when the [Validation Rules feature](#validation-rules) is enabled, and you +can add additional validation using [admission webhooks](/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook). --> ### 合法性检查 {#validation} 定制资源是通过 [OpenAPI v3 模式定义](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) -来执行合法性检查的, +来执行合法性检查的,当启用[验证规则特性](#validation-rules)时,通过 `x-kubernetes-validations` 验证, 你可以通过使用[准入控制 Webhook](/zh/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook) 来添加额外的合法性检查逻辑。 @@ -949,6 +950,16 @@ Additionally, the following restrictions are applied to the schema: - 字段 `additionalProperties` 不可设置为 `false` - 字段 `additionalProperties` 与 `properties` 互斥,不可同时使用 + +当[验证规则特性](#validation-rules)被启用并且 CustomResourceDefinition +模式是一个[结构化的模式定义](#specifying-a-structural-schema)时, +`x-kubernetes-validations` 扩展可以使用[通用表达式语言(CEL)](https://github.com/google/cel-spec)表达式来验证定制资源。 + +## 验证规则 + +{{< feature-state state="alpha" for_k8s_version="v1.23" >}} + + +验证规则从 1.23 开始处于 Alpha 状态, +当 `CustomResourceValidationExpressions` [特性门控](/zh/docs/reference/command-line-tools-reference/feature-gates/)被启用时, +验证定制资源。这个功能只有在模式是[结构化的模式](#specifying-a-structural-schema)时才可用。 + + +验证规则使用[通用表达式语言(CEL)](https://github.com/google/cel-spec)来验证定制资源的值。 +验证规则使用 `x-kubernetes-validations` 扩展包含在 `CustomResourceDefinition` 模式定义中。 + + +规则的作用域是模式定义中 `x-kubernetes-validations` 扩展所在的位置。 +CEL 表达式中的 `self` 变量被绑定到限定作用域的取值。 + + +所有验证规则都是针对当前对象的:不支持跨对象或有状态的验证规则。 + + +例如: + +```yaml + ... + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-validations: + - rule: "self.minReplicas <= self.replicas" + message: "replicas should be greater than or equal to minReplicas." + - rule: "self.replicas <= self.maxReplicas" + message: "replicas should be smaller than or equal to maxReplicas." + properties: + ... + minReplicas: + type: integer + replicas: + type: integer + maxReplicas: + type: integer + required: + - minReplicas + - replicas + - maxReplicas +``` + + +将拒绝创建这个定制资源的请求: + +```yaml +apiVersion: "stable.example.com/v1" +kind: CronTab +metadata: + name: my-new-cron-object +spec: + minReplicas: 0 + replicas: 20 + maxReplicas: 10 +``` + + +返回响应为: + +``` +The CronTab "my-new-cron-object" is invalid: +* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: replicas should be smaller than or equal to maxReplicas. +``` + + +`x-kubernetes-validations` 可以有多条规则。 + +`x-kubernetes-validations` 下的 `rule` 代表将由 CEL 评估的表达式。 + +`message` 代表验证失败时显示的信息。如果消息没有设置,上述响应将是: +``` +The CronTab "my-new-cron-object" is invalid: +* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: failed rule: self.replicas <= self.maxReplicas +``` + + +当 CRD 被创建/更新时,验证规则被编译。 +如果验证规则的编译失败,CRD 的创建/更新请求将失败。 +编译过程也包括类型检查。 + + +编译失败: +- `no_matching_overload`:此函数没有参数类型的重载。 + + 例如,像 `self == true` 这样的规则对一个整数类型的字段将得到错误: + ``` + Invalid value: apiextensions.ValidationRule{Rule:"self == true", Message:""}: compilation failed: ERROR: \:1:6: found no matching overload for '_==_' applied to '(int, bool)' + ``` + +- `no_such_field`:不包含所需的字段。 + 例如,针对一个不存在的字段,像 `self.nonExistingField > 0` 这样的规则将返回错误: + ``` + Invalid value: apiextensions.ValidationRule{Rule:"self.nonExistingField > 0", Message:""}: compilation failed: ERROR: \:1:5: undefined field 'nonExistingField' + ``` + +- `invalid argument`:对宏的无效参数。 + 例如,像 `has(self)` 这样的规则将返回错误: + ``` + Invalid value: apiextensions.ValidationRule{Rule:"has(self)", Message:""}: compilation failed: ERROR: :1:4: invalid argument to has() macro + ``` + + + +验证规则例子: + +| 规则 | 目的 | +| ---------------- | ------------ | +| `self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas` | 验证定义副本数的三个字段大小顺序是否正确 | +| `'Available' in self.stateCounts` | 验证 map 中是否存在键名为 `Available`的条目 | +| `(size(self.list1) == 0) != (size(self.list2) == 0)` | 验证两个 list 之一是非空的,但不是二者都非空 | +| !('MY_KEY' in self.map1) || self['MY_KEY'].matches('^[a-zA-Z]*$') | 如果某个特定的 key 在 map 中,验证 map 中这个 key 的 value | +| `self.envars.filter(e, e.name = 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$')` | 验证一个 listMap 中主键 'name' 为 'MY_ENV' 'value' 的表项,检查其取值 'value' | +| `has(self.expired) && self.created + self.ttl < self.expired` | 验证 'Expired' 日期是否晚于 'Create' 日期加上 'ttl' 持续时间 | +| `self.health.startsWith('ok')` | 验证 'health' 字符串字段有前缀 'ok' | +| `self.widgets.exists(w, w.key == 'x' && w.foo < 10)` | 验证 key 为 'x' 的 listMap 项的 'foo' 属性是否小于 10 | +| `type(self) == string ? self == '100%' : self == 1000` | 在 int 型和 string 型两种情况下验证 int-or-string 字段 | +| `self.metadata.name.startsWith(self.prefix)` | 验证对象的名称是否具有另一个字段值的前缀 | +| `self.set1.all(e, !(e in self.set2))` | 验证两个 listSet 是否不相交 | +| `size(self.names) == size(self.details) && self.names.all(n, n in self.details)` | 验证 'details' map 是由 'names' listSet 的项目所决定的。 | + +参考:[CEL 中支持的求值](https://github.com/google/cel-spec/blob/v0.6.0/doc/langdef.md#evaluation) + + + +- 如果规则的作用域是某资源的根,则它可以对 CRD 的 OpenAPIv3 模式表达式中声明的任何字段进行字段选择, + 以及 `apiVersion`、`kind`、`metadata.name` 和 `metadata.generateName`。 + 这包括在同一表达式中对 `spec` 和 `status` 的字段进行选择: + ```yaml + ... + openAPIV3Schema: + type: object + x-kubernetes-validations: + - rule: "self.status.availableReplicas >= self.spec.minReplicas" + properties: + spec: + type: object + properties: + minReplicas: + type: integer + ... + status: + type: object + properties: + availableReplicas: + type: integer + ``` + + +- 如果规则的作用域是具有属性的对象,那么可以通过 `self.field` 对该对象的可访问属性进行字段选择, + 而字段存在与否可以通过 `has(self.field)` 来检查。 + 在 CEL 表达式中,Null 值的字段被视为不存在的字段。 + + ```yaml + ... + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-validations: + - rule: "has(self.foo)" + properties: + ... + foo: + type: integer + ``` + + +- 如果规则的作用域是一个带有 additionalProperties 的对象(即map),那么 map 的值 + 可以通过 `self[mapKey]` 访问,map 的包含性可以通过 `mapKey in self` 检查, + map 中的所有条目可以通过 CEL 宏和函数如 `self.all(...)` 访问。 + ```yaml + ... + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-validations: + - rule: "self['xyz'].foo > 0" + additionalProperties: + ... + type: object + properties: + foo: + type: integer + ``` + + +- 如果规则的作用域是 array,则 array 的元素可以通过 `self[i]` 访问,也可以通过宏和函数访问。 + ```yaml + ... + openAPIV3Schema: + type: object + properties: + ... + foo: + type: array + x-kubernetes-validations: + - rule: "size(self) == 1" + items: + type: string + ``` + + +- 如果规则的作用域为标量,则 `self` 将绑定到标量值。 + ```yaml + ... + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + ... + foo: + type: integer + x-kubernetes-validations: + - rule: "self > 0" + ``` + +例子: + +| 规则作用域字段类型 | 规则示例 | +| -----------------------| -----------------------| +| 根对象 | `self.status.actual <= self.spec.maxDesired`| +| 对象映射 | `self.components['Widget'].priority < 10`| +| 整数列表 | `self.values.all(value, value >= 0 && value < 100)`| +| 字符串 | `self.startsWith('kube')`| + + + +`apiVersion`、`kind``metadata.name` 和 `metadata.generateName` 始终可以从对象的根目录和任何 +带有 `x-kubernetes-embedded-resource` 注解的对象访问。 +其他元数据属性都不可访问。 + + +通过 `x-kubernetes-preserve-unknown-fields` 保存在定制资源中的未知数据在 CEL 表达中无法访问。 +这包括: + - 使用 `x-kubernetes-preserve-unknown-fields` 的对象模式保留的未知字段值。 + - 属性模式为"未知类型(Unknown Type)"的对象属性。一个"未知类型"被递归定义为: + - 一个没有类型的模式,`x-kubernetes-preserve-unknown-fields` 设置为 true。 + - 一个数组,其中项目模式为"未知类型" + - 一个 additionalProperties 模式为"未知类型"的对象 + + + +只有 `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` 形式的属性名是可访问的。 +当在表达式中访问时,可访问的属性名称会根据以下规则进行转义: + + +| 转义序列 | 属性名称等效为 | +| ----------------------- | ----------------------| +| `__underscores__` | `__` | +| `__dot__` | `.` | +|`__dash__` | `-` | +| `__slash__` | `/` | +| `__{keyword}__` | [CEL 保留关键字](https://github.com/google/cel-spec/blob/v0.6.0/doc/langdef.md#syntax) | + + +注意:CEL 保留关键字需要与要转义的确切属性名匹配(例如,单词 `sprint` 中的 `int` 不会转义)。 + + +转义的例子: + + +|属性名 | 转义属性名规则 | +| ----------------| ----------------------- | +| namespace | `self.__namespace__ > 0` | +| x-prop | `self.x__dash__prop > 0` | +| redact__d | `self.redact__underscores__d > 0` | +| string | `self.startsWith('kube')` | + + + +`set` 或 `map` 的 `x-Kubernetes-list-type` 的数组的等值比较会忽略元素顺序,即[1,2] == [2,1]。 +使用 `x-kubernetes-list-type` 对数组进行串联时,使用 List 类型的语义: +- `set`:`X + Y` 执行一个并集操作,其中 `X` 中所有元素的数组位置被保留, + `Y` 中不相交的元素被追加,保留其部分顺序。 +- `map`:`X + Y`执行合并,其中 `X` 中所有键的数组位置被保留, + 但当 `X` 和 `Y` 的键集相交时,其值被 `Y` 中的值覆盖。 + `Y` 中键值不相交的元素被附加,保留其部分顺序。 + + + +以下是 OpenAPIV3 和 CEL 类型之间的声明类型映射: + + +| OpenAPIv3 类型 | CEL 类型 | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| 带有 Properties 的对象 | 对象 / "消息类型" | +| 带有 AdditionalProperties 的对象 | map | +| 带有 x-kubernetes-embedded-type 的对象 | 对象 / "消息类型",'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 都隐式包含在模式中 | +| 带有 x-kubernetes-preserve-unknown-fields 的对象 | 对象 / "消息类型",未知字段无法从 CEL 表达式中访问 | +| x-kubernetes-int-or-string | 可能是整数或字符串的动态对象,可以用 `type(value)` 来检查类型 | +| 数组 | list | +| 带有 x-kubernetes-list-type=map 的数组 | 列表,基于集合等值和唯一键名保证的 map 组成 | +| 带有 x-kubernetes-list-type=set 的数组 | 列表,基于集合等值和唯一键名保证的 set 组成 | +| 布尔值 | boolean | +| 数字 (各种格式) | double | +| 整数 (各种格式) | int (64) | +| 'null' | null_type | +| 字符串 | string | +| 带有 format=byte (base64 编码)字符串 | bytes | +| 带有 format=date 字符串 | timestamp (google.protobuf.Timestamp) | +| 带有 format=datetime 字符串 | timestamp (google.protobuf.Timestamp) | +| 带有 format=duration 字符串 | duration (google.protobuf.Duration) | + + +参考:[CEL 类型](https://github.com/google/cel-spec/blob/v0.6.0/doc/langdef.md#values), +[OpenAPI 类型](https://swagger.io/specification/#data-types), +[Kubernetes 结构化模式](/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema)。 + + +#### 验证函数 {#available-validation-functions} + + +可用的函数包括: + - CEL 标准函数,在[标准定义列表](https://github.com/google/cel-spec/blob/v0.7.0/doc/langdef.md#list-of-standard-definitions)中定义 + - CEL 标准[宏](https://github.com/google/cel-spec/blob/v0.7.0/doc/langdef.md#macros) + - CEL [扩展字符串函数库](https://pkg.go.dev/github.com/google/cel-go@v0.11.2/ext#Strings) + - Kubernetes [CEL 扩展库](https://pkg.go.dev/k8s.io/apiextensions-apiserver@v0.24.0/pkg/apiserver/schema/cel/library#pkg-functions) + + +#### 转换规则 + + +包含引用标识符 `oldSself` 的表达式的规则被隐式视为“转换规则(Transition Rule)”。 +转换规则允许模式作者阻止两个原本有效的状态之间的某些转换。例如: + +```yaml +type: string +enum: ["low", "medium", "high"] +x-kubernetes-validations: +- rule: "!(self == 'high' && oldSelf == 'low') && !(self == 'low' && oldSelf == 'high')" + message: cannot transition directly between 'low' and 'high' +``` + + +与其他规则不同,转换规则仅适用于满足以下条件的操作: + + +- 更新现有对象的操作。转换规则从不适用于创建操作。 + + +- 旧的值和新的值都存在。仍然可以通过在父节点上放置转换规则来检查值是否已被添加或移除。 + 转换规则从不应用于定制资源创建。当被放置在可选字段上时,转换规则将不适用于设置或取消设置该字段的更新操作。 + + +- 被转换规则验证的模式节点的路径必须解析到一个在旧对象和新对象之间具有可比性的节点。 + 例如,列表项和它们的后代(`spec.foo[10].bar`)不一定能在现有对象和后来对同一对象的更新之间产生关联。 + + +如果一个模式节点包含一个永远不能应用的转换规则,在 CRD 写入时将会产生错误,例如: +"*path*: update rule *rule* cannot be set on schema because the schema or its parent +schema is not mergeable"。 + + +转换规则只允许在模式的“可关联部分(Correlatable Portions)”中使用。 +如果所有 `array` 父模式都是 `x-kubernetes-list-type=map`类型的,那么该模式的一部分就是可关联的; +任何 `set` 或者 `atomic` 数组父模式都不支持确定性地将 `self` 与 `oldSelf` 关联起来。 + + +这是一些转换规则的例子: + + +{{< table caption="转换规则样例" >}} +| 用例 | 规则 +| -------- | -------- +| 不可变 | `self.foo == oldSelf.foo` +| 赋值后禁止修改/删除 | `oldSelf != 'bar' \|\| self == 'bar'` or `!has(oldSelf.field) \|\| has(self.field)` +| 仅附加的 set | `self.all(element, element in oldSelf)` +| 如果之前的值为 X,则新值只能为 A 或 B,不能为 Y 或 Z | `oldSelf != 'X' \|\| self in ['A', 'B']` +| 单调(非递减)计数器 | `self >= oldSelf` +{{< /table >}} + + +#### 验证函数的资源使用 + + +当你创建或更新一个使用验证规则的 CustomResourceDefinition 时, +API 服务器会检查运行这些验证规则可能产生的影响。 +如果一个规则的执行成本过高,API 服务器会拒绝创建或更新操作,并返回一个错误信息。 + +运行时也使用类似的系统来观察解释器的行动。如果解释器执行了太多的指令,规则的执行将被停止,并且会产生一个错误。 + +每个 CustomResourceDefinition 也被允许有一定数量的资源来完成其所有验证规则的执行。 +如果在创建时估计其规则的总和超过了这个限制,那么也会发生验证错误。 + + +如果你只指定那些无论输入量有多大都要花费相同时间的规则,你不太可能遇到验证的资源预算问题。 + +例如,一个断言 `self.foo == 1` 的规则本身不存在因为资源预算组验证而导致被拒绝的风险。 + +但是,如果 `foo` 是一个字符串,而你定义了一个验证规则 `self.foo.contains("someString")`, +这个规则需要更长的时间来执行,取决于 `foo` 有多长。 + +另一个例子是如果 `foo` 是一个数组,而你指定了验证规则 `self.foo.all(x, x > 5)`。 +如果没有给出 `foo` 的长度限制,成本系统总是假设最坏的情况,这将发生在任何可以被迭代的事物上(list、map 等)。 + + +因此,通过 `maxItems`,`maxProperties` 和 `maxLength` 进行限制被认为是最佳实践, +以在验证规则中处理任何内容,以防止在成本估算期间验证错误。例如,给定具有一个规则的模式: + +```yaml +openAPIV3Schema: + type: object + properties: + foo: + type: array + items: + type: string + x-kubernetes-validations: + - rule: "self.all(x, x.contains('a string'))" +``` + + +API 服务器以验证预算为由拒绝该规则,并显示错误: +``` + spec.validation.openAPIV3Schema.properties[spec].properties[foo].x-kubernetes-validations[0].rule: Forbidden: + CEL rule exceeded budget by more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and + maxLength where arrays, maps, and strings are used) +``` + + +这个拒绝会发生是因为 `self.all` 意味着对 `foo` 中的每一个字符串调用 `contains()`, +而这又会检查给定的字符串是否包含 `'a string'`。如果没有限制,这是一个非常昂贵的规则。 + + +如果你不指定任何验证限制,这个规则的估计成本将超过每条规则的成本限制。 +但如果你在适当的地方添加限制,该规则将被允许: + +```yaml +openAPIV3Schema: + type: object + properties: + foo: + type: array + maxItems: 25 + items: + type: string + maxLength: 10 + x-kubernetes-validations: + - rule: "self.all(x, x.contains('a string'))" +``` + + +成本评估系统除了考虑规则本身的估计成本外,还考虑到规则将被执行的次数。 +例如,下面这个规则的估计成本与前面的例子相同(尽管该规则现在被定义在单个数组项上): + +```yaml +openAPIV3Schema: + type: object + properties: + foo: + type: array + maxItems: 25 + items: + type: string + x-kubernetes-validations: + - rule: "self.contains('a string'))" + maxLength: 10 +``` + + +如果在一个列表内部的一个列表有一个使用 `self.all` 的验证规则,那就会比具有相同规则的非嵌套列表的成本高得多。 +一个在非嵌套列表中被允许的规则可能需要在两个嵌套列表中设置较低的限制才能被允许。 +例如,即使没有设置限制,下面的规则也是允许的: + +```yaml +openAPIV3Schema: + type: object + properties: + foo: + type: array + items: + type: integer + x-kubernetes-validations: + - rule: "self.all(x, x == 5)" +``` + + +但是同样的规则在下面的模式中(添加了一个嵌套数组)产生了一个验证错误: + +```yaml +openAPIV3Schema: + type: object + properties: + foo: + type: array + items: + type: array + items: + type: integer + x-kubernetes-validations: + - rule: "self.all(x, x == 5)" +``` + + +这是因为 `foo` 的每一项本身就是一个数组,而每一个子数组依次调用 `self.all`。 +在使用验证规则的地方,尽可能避免嵌套的列表和字典。 ### 以 OpenAPI v2 形式发布合法性检查模式 {#publish-validation-schema-in-openapi-v2} @@ -1308,9 +2093,7 @@ CustomResourceDefinition 的[结构化的](#specifying-a-structural-schema)、 [OpenAPI v2 规约](/zh/docs/concepts/overview/kubernetes-api/#openapi-and-swagger-definitions) 的一部分发布出来。 -[kubectl](/zh/docs/reference/kubectl/overview) 命令行工具会基于所发布的模式定义来执行 -客户端的合法性检查(`kubectl create` 和 `kubectl apply`),为定制资源的模式定义 -提供解释(`kubectl explain`)。 +[kubectl](/zh/docs/reference/kubectl/) 命令行工具会基于所发布的模式定义来执行客户端的合法性检查(`kubectl create` 和 `kubectl apply`),为定制资源的模式定义提供解释(`kubectl explain`)。 所发布的模式还可被用于其他目的,例如生成客户端或者生成文档。