使用 CustomResourceDefinition 扩展 Kubernetes API

本页面展示如何通过创建CustomResourceDefinition自定义资源安装到 Kubernetes API 中。

准备工作

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具已配置为与你的集群通信。建议在至少有两个非控制平面主机节点的集群上运行本教程。如果你还没有集群,可以使用 minikube 创建一个,或者使用以下 Kubernetes 演练场之一:

你的 Kubernetes 服务器版本必须是 1.16 或更高。

要检查版本,请输入 kubectl version

如果你使用的是受支持的旧版本 Kubernetes,请切换到该版本的文档以查看与你的集群相关的建议。

创建 CustomResourceDefinition

当你创建一个新的 CustomResourceDefinition (CRD) 时,Kubernetes API 服务器会为你指定的每个版本创建一个新的 RESTful 资源路径。从 CRD 对象创建的自定义资源可以是命名空间范围的,也可以是集群范围的,这在 CRD 的 spec.scope 字段中指定。与现有的内置对象一样,删除命名空间会删除该命名空间中的所有自定义对象。CustomResourceDefinitions 本身是非命名空间范围的,并且对所有命名空间都可用。

例如,如果你将以下 CustomResourceDefinition 保存到文件 resourcedefinition.yaml 中:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.stable.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: stable.example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # Each version can be enabled/disabled by Served flag.
      served: true
      # One and only one version must be marked as the storage version.
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # singular name to be used as an alias on the CLI and for display
    singular: crontab
    # kind is normally the CamelCased singular type. Your resource manifests use this.
    kind: CronTab
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - ct

并创建它

kubectl apply -f resourcedefinition.yaml

然后会在以下路径创建一个新的命名空间范围的 RESTful API 端点:

/apis/stable.example.com/v1/namespaces/*/crontabs/...

然后可以使用此端点 URL 创建和管理自定义对象。这些对象的 kind 将是你在上面创建的 CustomResourceDefinition 对象的规约中的 CronTab

创建端点可能需要几秒钟。你可以观察 CustomResourceDefinition 的 Established 状态变为 true,或观察 API 服务器的发现信息,直到你的资源出现。

创建自定义对象

创建 CustomResourceDefinition 对象后,你可以创建自定义对象。自定义对象可以包含自定义字段。这些字段可以包含任意 JSON。在以下示例中,cronSpecimage 自定义字段设置在 kind 为 CronTab 的自定义对象中。CronTab 这个 kind 来自于你在上面创建的 CustomResourceDefinition 对象的规约。

如果你将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image

并创建它

kubectl apply -f my-crontab.yaml

然后,你可以使用 kubectl 来管理你的 CronTab 对象。例如:

kubectl get crontab

应该会输出类似以下的列表:

NAME                 AGE
my-new-cron-object   6s

使用 kubectl 时,资源名称不区分大小写,并且你可以使用 CRD 中定义的单数或复数形式,以及任何短名称。

你还可以查看原始 YAML 数据:

kubectl get ct -o yaml

你应该会看到它包含了你用于创建它的 YAML 中的自定义字段 cronSpecimage

apiVersion: v1
items:
- apiVersion: stable.example.com/v1
  kind: CronTab
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"stable.example.com/v1","kind":"CronTab","metadata":{"annotations":{},"name":"my-new-cron-object","namespace":"default"},"spec":{"cronSpec":"* * * * */5","image":"my-awesome-cron-image"}}        
    creationTimestamp: "2021-06-20T07:35:27Z"
    generation: 1
    name: my-new-cron-object
    namespace: default
    resourceVersion: "1326"
    uid: 9aab1d66-628e-41bb-a422-57b8b3b1f5a9
  spec:
    cronSpec: '* * * * */5'
    image: my-awesome-cron-image
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

删除 CustomResourceDefinition

当你删除 CustomResourceDefinition 时,服务器将卸载 RESTful API 端点并删除存储在其中的所有自定义对象。

kubectl delete -f resourcedefinition.yaml
kubectl get crontabs
Error from server (NotFound): Unable to list {"stable.example.com" "v1" "crontabs"}: the server could not
find the requested resource (get crontabs.stable.example.com)

如果你随后重新创建相同的 CustomResourceDefinition,它将是空的。

指定结构化模式

CustomResources 在自定义字段中存储结构化数据(以及 API 服务器隐式验证的内置字段 apiVersionkindmetadata)。通过 OpenAPI v3.0 验证,可以指定一个模式,该模式在创建和更新期间进行验证,有关此类模式的详细信息和限制,请参见下文。

对于 apiextensions.k8s.io/v1 版本,CustomResourceDefinitions 强制要求定义结构化模式。在 CustomResourceDefinition 的 beta 版本中,结构化模式是可选的。

结构化模式是一种 OpenAPI v3.0 验证模式,它:

  1. 为根、对象节点的每个指定字段(通过 OpenAPI 中的 propertiesadditionalProperties)以及数组节点的每个项(通过 OpenAPI 中的 items)指定非空类型(通过 OpenAPI 中的 type),但以下情况除外:
    • 节点带有 x-kubernetes-int-or-string: true
    • 节点带有 x-kubernetes-preserve-unknown-fields: true
  2. 对于对象中的每个字段和数组中的每个项,如果在 allOfanyOfoneOfnot 中的任何一个中指定,则模式还在这些逻辑连接词之外指定该字段/项(比较示例 1 和 2)。
  3. 不在 allOfanyOfoneOfnot 中设置 descriptiontypedefaultadditionalPropertiesnullable,带有 x-kubernetes-int-or-string: true 的两个模式除外(参见下文)。
  4. 如果指定了 metadata,则只允许对 metadata.namemetadata.generateName 进行限制。

非结构化示例 1

allOf:
- properties:
    foo:
      ...

与规则 2 冲突。以下是正确的做法:

properties:
  foo:
    ...
allOf:
- properties:
    foo:
      ...

非结构化示例 2

allOf:
- items:
    properties:
      foo:
        ...

与规则 2 冲突。以下是正确的做法:

items:
  properties:
    foo:
      ...
allOf:
- items:
    properties:
      foo:
        ...

非结构化示例 3

properties:
  foo:
    pattern: "abc"
  metadata:
    type: object
    properties:
      name:
        type: string
        pattern: "^a"
      finalizers:
        type: array
        items:
          type: string
          pattern: "my-finalizer"
anyOf:
- properties:
    bar:
      type: integer
      minimum: 42
  required: ["bar"]
  description: "foo bar object"

不是结构化模式,原因如下:

  • 根节点的类型缺失(规则 1)。
  • foo 的类型缺失(规则 1)。
  • anyOf 内部的 bar 没有在外部指定(规则 2)。
  • bartypeanyOf 内部(规则 3)。
  • 描述设置在 anyOf 内部(规则 3)。
  • 不能限制 metadata.finalizers(规则 4)。

相比之下,以下对应的模式是结构化的:

type: object
description: "foo bar object"
properties:
  foo:
    type: string
    pattern: "abc"
  bar:
    type: integer
  metadata:
    type: object
    properties:
      name:
        type: string
        pattern: "^a"
anyOf:
- properties:
    bar:
      minimum: 42
  required: ["bar"]

违反结构化模式规则的情况会在 CustomResourceDefinition 的 NonStructural 状态中报告。

字段修剪

CustomResourceDefinitions 将验证过的资源数据存储在集群的持久化存储中,即etcd。与原生 Kubernetes 资源(如ConfigMap)一样,如果你指定了 API 服务器无法识别的字段,未知字段在持久化之前会被**修剪**(移除)。

apiextensions.k8s.io/v1beta1 转换到 apiextensions.k8s.io/v1 的 CRD 可能缺乏结构化模式,并且 spec.preserveUnknownFields 可能为 true

对于作为 apiextensions.k8s.io/v1beta1 创建的且 spec.preserveUnknownFields 设置为 true 的旧版 CustomResourceDefinition 对象,以下情况也适用:

  • 未启用修剪。
  • 你可以存储任意数据。

为了与 apiextensions.k8s.io/v1 兼容,请更新你的自定义资源定义以:

  1. 使用结构化 OpenAPI 模式。
  2. spec.preserveUnknownFields 设置为 false

如果你将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image
  someRandomField: 42

并创建它

kubectl create --validate=false -f my-crontab.yaml -o yaml

你的输出类似于:

apiVersion: stable.example.com/v1
kind: CronTab
metadata:
  creationTimestamp: 2017-05-31T12:56:35Z
  generation: 1
  name: my-new-cron-object
  namespace: default
  resourceVersion: "285"
  uid: 9423255b-4600-11e7-af6a-28d2447dc82b
spec:
  cronSpec: '* * * * */5'
  image: my-awesome-cron-image

请注意,字段 someRandomField 被修剪了。

此示例通过添加命令行选项 --validate=false 关闭了客户端验证,以展示 API 服务器的行为。由于OpenAPI 验证模式也会发布给客户端,kubectl 也会检查未知字段,并在对象发送到 API 服务器之前拒绝它们。

控制修剪

默认情况下,自定义资源的所有版本中未指定的字段都会被修剪。但是,通过在结构化 OpenAPI v3 验证模式中添加 x-kubernetes-preserve-unknown-fields: true,可以针对特定的字段子树选择退出修剪。

例如:

type: object
properties:
  json:
    x-kubernetes-preserve-unknown-fields: true

字段 json 可以存储任何 JSON 值,而不会被修剪。

你也可以部分指定允许的 JSON;例如:

type: object
properties:
  json:
    x-kubernetes-preserve-unknown-fields: true
    type: object
    description: this is arbitrary JSON

这样,只允许使用 object 类型的值。

对每个指定的属性(或 additionalProperties)再次启用修剪。

type: object
properties:
  json:
    x-kubernetes-preserve-unknown-fields: true
    type: object
    properties:
      spec:
        type: object
        properties:
          foo:
            type: string
          bar:
            type: string

这样,值

json:
  spec:
    foo: abc
    bar: def
    something: x
  status:
    something: x

被修剪为

json:
  spec:
    foo: abc
    bar: def
  status:
    something: x

这意味着指定 spec 对象中的 something 字段被修剪,但其外部的所有内容都不会被修剪。

整型或字符串 (IntOrString)

模式中带有 x-kubernetes-int-or-string: true 的节点被排除在规则 1 之外,因此以下模式是结构化的:

type: object
properties:
  foo:
    x-kubernetes-int-or-string: true

这些节点也部分被排除在规则 3 之外,因为允许以下两种模式(精确地是这两种,不因附加字段而变化顺序):

x-kubernetes-int-or-string: true
anyOf:
  - type: integer
  - type: string
...

以及

x-kubernetes-int-or-string: true
allOf:
  - anyOf:
      - type: integer
      - type: string
  - ... # zero or more
...

使用其中一种规范,整型和字符串都能够通过验证。

验证模式发布中,x-kubernetes-int-or-string: true 被展开为上面所示的两种模式之一。

RawExtension

RawExtension(如runtime.RawExtension中所示)包含完整的 Kubernetes 对象,即包含 apiVersionkind 字段的对象。

通过设置 x-kubernetes-embedded-resource: true,可以指定这些嵌入式对象(完全无约束或部分指定)。例如:

type: object
properties:
  foo:
    x-kubernetes-embedded-resource: true
    x-kubernetes-preserve-unknown-fields: true

这里,字段 foo 包含一个完整的对象,例如:

foo:
  apiVersion: v1
  kind: Pod
  spec:
    ...

由于同时指定了 x-kubernetes-preserve-unknown-fields: true,所以没有进行修剪。不过,使用 x-kubernetes-preserve-unknown-fields: true 是可选的。

如果设置了 x-kubernetes-embedded-resource: true,则会隐式指定并验证 apiVersionkindmetadata

提供 CRD 的多个版本

有关提供 CustomResourceDefinition 的多个版本以及如何将对象从一个版本迁移到另一个版本的更多信息,请参见CustomResourceDefinition 版本控制

进阶主题

Finalizer

Finalizer 允许控制器实现异步的删除前钩子。自定义对象支持类似内置对象的 Finalizer。

你可以像这样向自定义对象添加 Finalizer:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  finalizers:
  - stable.example.com/finalizer

自定义 Finalizer 的标识符由域名、正斜杠和 Finalizer 名称组成。任何控制器都可以向任何对象的 Finalizer 列表中添加 Finalizer。

对带有 Finalizer 的对象发出的第一个删除请求会设置 metadata.deletionTimestamp 字段的值,但不会删除对象。一旦设置了此值,finalizers 列表中的条目就只能被移除。只要还有 Finalizer 存在,也无法强制删除对象。

设置了 metadata.deletionTimestamp 字段后,观察该对象的控制器会执行它们所处理的 Finalizer,并在完成后将 Finalizer 从列表中移除。每个控制器都有责任将其 Finalizer 从列表中移除。

metadata.deletionGracePeriodSeconds 的值控制轮询更新之间的间隔。

一旦 Finalizer 列表为空,即所有 Finalizer 都已执行完毕,该资源就会被 Kubernetes 删除。

验证

自定义资源通过OpenAPI v3.0 模式进行验证,当启用验证规则特性时通过 x-kubernetes-validations 进行验证,你还可以使用准入 Webhook添加额外的验证。

此外,还对模式应用以下限制:

  • 以下字段不能设置:

    • definitions,
    • dependencies,
    • deprecated,
    • discriminator,
    • id,
    • patternProperties,
    • readOnly,
    • writeOnly,
    • xml,
    • $ref.
  • 字段 uniqueItems 不能设置为 true

  • 字段 additionalProperties 不能设置为 false

  • 字段 additionalPropertiesproperties 是互斥的。

当启用验证规则特性且 CustomResourceDefinition 模式为结构化模式时,可以使用x-kubernetes-validations扩展来使用通用表达式语言 (CEL)表达式验证自定义资源。

有关其他限制和 CustomResourceDefinition 特性,请参阅结构化模式章节。

模式在 CustomResourceDefinition 中定义。在以下示例中,CustomResourceDefinition 对自定义对象应用以下验证:

  • spec.cronSpec 必须是字符串,并且必须符合正则表达式描述的格式。
  • spec.replicas 必须是整型,最小值必须为 1,最大值必须为 10。

将 CustomResourceDefinition 保存到文件 resourcedefinition.yaml 中:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        # openAPIV3Schema is the schema for validating custom objects.
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                  pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
                image:
                  type: string
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 10
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct

并创建它

kubectl apply -f resourcedefinition.yaml

如果 kind 为 CronTab 的自定义对象的字段中包含无效值,创建请求将被拒绝。在以下示例中,自定义对象包含无效值的字段:

  • spec.cronSpec 不匹配正则表达式。
  • spec.replicas 大于 10。

如果你将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * *"
  image: my-awesome-cron-image
  replicas: 15

并尝试创建它

kubectl apply -f my-crontab.yaml

则你会得到一个错误:

The CronTab "my-new-cron-object" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"stable.example.com/v1", "kind":"CronTab", "metadata":map[string]interface {}{"name":"my-new-cron-object", "namespace":"default", "deletionTimestamp":interface {}(nil), "deletionGracePeriodSeconds":(*int64)(nil), "creationTimestamp":"2017-09-05T05:20:07Z", "uid":"e14d79e7-91f9-11e7-a598-f0761cb232d1", "clusterName":""}, "spec":map[string]interface {}{"cronSpec":"* * * *", "image":"my-awesome-cron-image", "replicas":15}}:
validation failure list:
spec.cronSpec in body should match '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
spec.replicas in body should be less than or equal to 10

如果字段包含有效值,则对象创建请求会被接受。

将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image
  replicas: 5

并创建它

kubectl apply -f my-crontab.yaml
crontab "my-new-cron-object" created

验证棘轮效应 (Validation Ratcheting)

特性状态: Kubernetes v1.33 [stable] (默认启用:true)

如果你使用的 Kubernetes 版本低于 v1.30,你需要显式启用 CRDValidationRatcheting 特性门控来使用此行为,该行为随后会应用于集群中的所有 CustomResourceDefinitions。

假设你已经启用了特性门控,Kubernetes 会为 CustomResourceDefinitions 实现**验证棘轮效应**。API 服务器愿意接受对更新后无效的资源进行的更新,前提是资源中验证失败的每个部分都没有被更新操作更改。换句话说,资源中任何仍然无效的无效部分在更新之前就必须是错误的。你不能使用此机制将一个有效的资源更新为无效状态。

此特性允许 CRD 的作者在特定条件下放心地向 OpenAPIV3 模式添加新的验证。用户可以安全地更新到新模式,而无需升级对象版本或中断工作流程。

虽然大多数置于 CRD 的 OpenAPIV3 模式中的验证都支持棘轮效应,但也有一些例外。在 Kubernetes 1.33 的实现中,以下 OpenAPIV3 模式验证不支持棘轮效应,如果违反,仍将像往常一样抛出错误:

  • 量词

    • allOf
    • oneOf
    • anyOf
    • not
    • 这些字段后代中的任何验证
  • x-kubernetes-validations 在 Kubernetes 1.28 中,棘轮效应会忽略 CRD 验证规则。从 Kubernetes 1.29 Alpha 2 开始,只有当 x-kubernetes-validations 不引用 oldSelf 时,才会发生棘轮效应。

    转换规则(Transition Rules)从不发生棘轮效应:只有不使用 oldSelf 的规则引发的错误,如果其值未改变,才会自动发生棘轮效应。

    要为 CEL 表达式编写自定义的棘轮逻辑,请查看optionalOldSelf

  • x-kubernetes-list-type 更改子模式列表类型产生的错误不会发生棘轮效应。例如,将带有重复项的列表更改为 set 类型将始终导致错误。

  • x-kubernetes-list-map-keys 更改列表模式的 Map 键产生的错误不会发生棘轮效应。

  • required 更改必需字段列表产生的错误不会发生棘轮效应。

  • properties 添加/移除/修改属性名称不会发生棘轮效应,但如果在属性名称保持不变的情况下更改每个属性的模式和子模式中的验证,则可能会发生棘轮效应。

  • additionalProperties 删除之前指定的 additionalProperties 验证不会发生棘轮效应。

  • metadata 来自 Kubernetes 内置的对象 metadata 验证错误不会发生棘轮效应(例如对象名称或标签值中的字符)。如果你为自定义资源的元数据指定自己的附加规则,则该附加验证会发生棘轮效应。

验证规则

特性状态: Kubernetes v1.29 [stable]

验证规则使用通用表达式语言 (CEL)来验证自定义资源的值。验证规则使用 x-kubernetes-validations 扩展包含在 CustomResourceDefinition 模式中。

规则的作用域限定在模式中 x-kubernetes-validations 扩展的位置。CEL 表达式中的 self 变量绑定到该作用域的值。

所有验证规则的作用域都限定在当前对象:不支持跨对象或有状态的验证规则。

例如:

  ...
  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

将拒绝创建此自定义资源的请求:

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 表示验证失败时显示的消息。如果未设置 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: \<input>: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: \<input>:1:5: undefined field 'nonExistingField'
    
  • invalid argument:宏的参数无效。

    例如,规则 has(self) 会返回错误:

    Invalid value: apiextensions.ValidationRule{Rule:"has(self)", Message:""}: compilation failed: ERROR: <input>: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)验证两个列表中有一个非空,但不能两个都非空
!('MY_KEY' in self.map1) || self['MY_KEY'].matches('^[a-zA-Z]*$')验证 Map 中特定键对应的值(如果该键存在于 Map 中)
self.envars.filter(e, e.name == 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$')验证 listMap 中键字段 'name' 为 'MY_ENV' 的条目的 '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)验证键为 'x' 的 listMap 项的 'foo' 属性小于 10
type(self) == string ? self == '100%' : self == 1000验证整型或字符串字段的整型和字符串两种情况
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 中的项作为键
size(self.clusters.filter(c, c.name == self.primary)) == 1验证 'primary' 属性在 'clusters' listMap 中只出现一次

交叉参考:CEL 支持的求值

  • 如果规则的作用域限定在资源的根节点,它可以选择 CRD 的 OpenAPIv3 模式中声明的任何字段,以及 apiVersionkindmetadata.namemetadata.generateName。这包括在同一个表达式中选择 specstatus 中的字段。

      ...
      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 表达式中被视为不存在的字段。

      ...
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            x-kubernetes-validations:
              - rule: "has(self.foo)"
            properties:
              ...
              foo:
                type: integer
    
  • 如果规则的作用域限定在带有 additionalProperties 的对象(即 Map),可以通过 self[mapKey] 访问 Map 的值,可以通过 mapKey in self 检查 Map 是否包含某个键,并且可以通过 CEL 宏和函数(如 self.all(...))访问 Map 的所有条目。

      ...
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            x-kubernetes-validations:
              - rule: "self['xyz'].foo > 0"
            additionalProperties:
              ...
              type: object
              properties:
                foo:
                  type: integer
    
  • 如果规则的作用域限定在数组,可以通过 self[i] 以及宏和函数访问数组元素。

      ...
      openAPIV3Schema:
        type: object
        properties:
          ...
          foo:
            type: array
            x-kubernetes-validations:
              - rule: "size(self) == 1"
            items:
              type: string
    
  • 如果规则的作用域限定在标量,self 绑定到该标量值。

      ...
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              ...
              foo:
                type: integer
                x-kubernetes-validations:
                - rule: "self > 0"
    

示例

规则作用域的字段类型规则示例
根对象self.status.actual <= self.spec.maxDesired
对象 Mapself.components['Widget'].priority < 10
整型列表self.values.all(value, value >= 0 && value < 100)
字符串self.startsWith('kube')

始终可以从对象的根以及任何带有 x-kubernetes-embedded-resource 注解的对象访问 apiVersionkindmetadata.namemetadata.generateName。其他元数据属性不可访问。

通过 x-kubernetes-preserve-unknown-fields 在自定义资源中保留的未知数据在 CEL 表达式中不可访问。这包括:

  • 对象模式中带有 x-kubernetes-preserve-unknown-fields 保留的未知字段值。

  • 属性模式为“未知类型”的对象属性。“未知类型”递归定义为:

    • 没有类型并且 x-kubernetes-preserve-unknown-fields 设置为 true 的模式
    • 项模式为“未知类型”的数组
    • additionalProperties 模式为“未知类型”的对象

只有形式为 [a-zA-Z_.-/][a-zA-Z0-9_.-/]* 的属性名称可访问。在表达式中访问时,可访问属性名称根据以下规则进行转义:

转义序列等效属性名称
__underscores____
__dot__.
__dash__-
__slash__/
__{keyword}__CEL 保留关键字

注意:CEL 保留关键字需要与精确的属性名称匹配才能被转义(例如,单词 sprint 中的 int 不会被转义)。

转义示例

属性名称带有转义属性名称的规则
namespaceself.__namespace__ > 0
x-propself.x__dash__prop > 0
redact__dself.redact__underscores__d > 0
字符串self.startsWith('kube')

带有 x-kubernetes-list-typesetmap 的数组的相等性比较会忽略元素的顺序,即 [1, 2] == [2, 1]。带有 x-kubernetes-list-type 的数组的串联使用列表类型的语义:

  • setX + Y 执行并集操作,其中保留 X 中所有元素的数组位置,并附加 Y 中不相交的元素,保留它们的偏序。

  • mapX + Y 执行合并操作,其中保留 X 中所有键的数组位置,但当 XY 的键集相交时,值会被 Y 中的值覆盖。Y 中带有不相交键的元素会被附加,保留它们的偏序。

以下是 OpenAPIv3 和 CEL 类型之间的声明类型映射:

OpenAPIv3 类型CEL 类型
带有 Properties 的 'object'object / “消息类型”
带有 AdditionalProperties 的 'object'map
带有 x-kubernetes-embedded-type 的 'object'object / “消息类型”,'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 隐式包含在模式中
带有 x-kubernetes-preserve-unknown-fields 的 'object'object / “消息类型”,未知字段在 CEL 表达式中不可访问
x-kubernetes-int-or-string动态对象,可以是整型或字符串,可以使用 type(value) 检查类型
'array'list
带有 x-kubernetes-list-type=map 的 'array'基于 Map 的相等性和唯一键保证的 list
带有 x-kubernetes-list-type=set 的 'array'基于 Set 的相等性和唯一条目保证的 list
'boolean'boolean
'number' (所有格式)double
'integer' (所有格式)int (64)
'null'null_type
'string'字符串
format=byte 的 'string' (base64 编码)bytes
format=date 的 'string'timestamp (google.protobuf.Timestamp)
format=datetime 的 'string'timestamp (google.protobuf.Timestamp)
format=duration 的 'string'duration (google.protobuf.Duration)

交叉参考:CEL 类型OpenAPI 类型Kubernetes 结构化模式

messageExpression 字段

与用于定义验证规则失败时报告的字符串的 message 字段类似,messageExpression 允许你使用 CEL 表达式来构建消息字符串。这使得你可以在验证失败消息中插入更多描述性信息。messageExpression 必须计算为一个字符串,并且可以使用与 rule 字段相同的可用变量。例如:

x-kubernetes-validations:
- rule: "self.x <= self.maxLimit"
  messageExpression: '"x exceeded max limit of " + string(self.maxLimit)'

请注意,CEL 字符串串联(+ 运算符)不会自动转换为字符串。如果你有一个非字符串标量,请使用 string(<value>) 函数将标量转换为字符串,如上例所示。

messageExpression 必须求值得到一个字符串,这在写入 CRD 时会进行检查。请注意,可以在同一规则上同时设置 messagemessageExpression,如果两者都存在,将使用 messageExpression。但是,如果 messageExpression 求值时出现错误,则会使用 message 中定义的字符串作为替代,并且会记录 messageExpression 的错误。如果 messageExpression 中定义的 CEL 表达式生成空字符串或包含换行符的字符串,也会发生这种回退。

如果满足上述条件之一且未设置 message,则会使用默认的验证失败消息作为替代。

messageExpression 是一个 CEL 表达式,因此验证函数对资源的使用中列出的限制适用。如果在 messageExpression 执行期间由于资源限制导致求值停止,则不会执行任何进一步的验证规则。

设置 messageExpression 是可选的。

message 字段

如果想设置静态消息,可以提供 message 而不是 messageExpression。如果验证失败,message 的值将用作不透明的错误字符串。

设置 message 是可选的。

reason 字段

可以在 validation 中添加一个机器可读的验证失败原因,在请求未通过此验证规则时返回。

例如:

x-kubernetes-validations:
- rule: "self.x <= self.maxLimit"
  reason: "FieldValueInvalid"

返回给调用者的 HTTP 状态码将与第一个失败的验证规则的原因匹配。目前支持的原因有:"FieldValueInvalid"、"FieldValueForbidden"、"FieldValueRequired"、"FieldValueDuplicate"。如果未设置或原因为未知,默认为 "FieldValueInvalid"。

设置 reason 是可选的。

fieldPath 字段

可以指定验证失败时返回的字段路径。

例如:

x-kubernetes-validations:
- rule: "self.foo.test.x <= self.maxLimit"
  fieldPath: ".foo.test.x"

在上面的示例中,验证检查字段 x 的值是否小于 maxLimit 的值。如果未指定 fieldPath,则验证失败时,fieldPath 将默认为 self 所指向的位置。指定 fieldPath 后,返回的错误将具有正确指向字段 x 位置的 fieldPath

fieldPath 的值必须是相对于此 `x-kubernetes-validations` 扩展在 schema 中的位置的 JSON 路径。此外,它应该引用 schema 中已存在的字段。例如,当验证检查 map testMap 下的特定属性 foo 时,可以将 fieldPath 设置为 ".testMap.foo" 或 `.testMap['foo']'`。如果验证需要检查两个列表中的唯一属性,`fieldPath` 可以设置为其中任一列表。例如,可以设置为 `.testList1` 或 `.testList2`。目前它支持通过子操作引用现有字段。更多信息请参考 Kubernetes 中的 JSONPath 支持。`fieldPath` 字段不支持对数组进行数字索引。

设置 fieldPath 是可选的。

optionalOldSelf 字段

特性状态: Kubernetes v1.33 [stable] (默认启用:true)

如果你的集群未启用 CRD validation ratcheting,CustomResourceDefinition API 不包含此字段,尝试设置它可能会导致错误。

optionalOldSelf 字段是一个布尔字段,它会改变下述过渡规则的行为。通常,如果无法确定 oldSelf,过渡规则将不会被评估:例如在对象创建期间或在更新中引入新值时。

如果 optionalOldSelf 设置为 true,则过渡规则将始终被评估,并且 oldSelf 的类型将更改为 CEL Optional 类型。

optionalOldSelf 在 schema 作者希望引入对新值通常更严格的约束,同时仍然允许旧值通过旧验证实现“祖父条款”或棘轮效应的场景中非常有用,它提供了比默认基于相等性的行为更强大的控制工具

使用示例

CEL描述
self.foo == "foo" || (oldSelf.hasValue() && oldSelf.value().foo != "foo")棘轮规则。一旦一个值被设置为 "foo",它必须保持为 foo。但是,如果它在引入 "foo" 约束之前就已存在,则可以使用任何值。
[oldSelf.orValue(""), self].all(x, ["OldCase1", "OldCase2"].exists(case, x == case)) || ["NewCase1", "NewCase2"].exists(case, self == case) || ["NewCase"].has(self)“如果 oldSelf 使用了已移除的枚举情况,则进行棘轮验证”
oldSelf.optMap(o, o.size()).orValue(0) < 4 || self.size() >= 4对新增加的 map 或 list 最小大小进行棘轮验证

验证函数

可用的函数包括

过渡规则

包含引用标识符 oldSelf 的表达式的规则被隐式视为过渡规则。过渡规则允许 schema 作者阻止两个其他情况下有效的状态之间的某些转换。例如

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'

与其他规则不同,过渡规则仅适用于满足以下条件的操作

  • 操作更新现有对象。过渡规则绝不适用于创建操作。

  • 新旧值都存在。仍然可以通过在父节点上放置过渡规则来检查是否添加或删除了某个值。过渡规则绝不适用于自定义资源创建。当应用于可选字段时,过渡规则不适用于设置或取消设置该字段的更新操作。

  • 通过过渡规则验证的 schema 节点的路径必须解析为一个在旧对象和新对象之间可比较的节点。例如,列表项及其后代 (spec.foo[10].bar) 在现有对象和对同一对象的后续更新之间不一定能关联起来。

如果 schema 节点包含永远无法应用的过渡规则,例如“oldSelf 无法在 schema 的不可关联部分 (path 内) 使用”,则在 CRD 写入时会生成错误。

过渡规则只允许应用于 schema 的可关联部分。如果所有 array 父 schema 的类型都是 x-kubernetes-list-type=map,则 schema 的该部分是可关联的;任何 setatomic 数组父 schema 都会使得无法明确地将 selfoldSelf 关联起来。

以下是一些过渡规则的示例

过渡规则示例
用例规则
不变性self.foo == oldSelf.foo
一旦赋值,阻止修改/移除oldSelf != 'bar' || self == 'bar'!has(oldSelf.field) || has(self.field)
只追加的集合self.all(element, element in oldSelf)
如果旧值为 X,则新值只能是 A 或 B,不能是 Y 或 ZoldSelf != 'X' || self in ['A', 'B']
单调(非递减)计数器self >= oldSelf

验证函数的资源使用

当创建或更新使用验证规则的 CustomResourceDefinition 时,API 服务器会检查运行这些验证规则的可能影响。如果某个规则执行起来预计非常昂贵,API 服务器将拒绝创建或更新操作,并返回错误消息。运行时也使用类似的系统来观察解释器的行为。如果解释器执行了过多的指令,规则的执行将被中止,并导致错误。每个 CustomResourceDefinition 也被允许使用一定量的资源来完成其所有验证规则的执行。如果在创建时估计其所有规则的总和超出该限制,则也会发生验证错误。

如果只指定那些无论输入大小如何都始终花费相同时间的规则,则不太可能遇到验证资源预算问题。例如,断言 self.foo == 1 的规则本身没有因验证资源预算组而被拒绝的风险。但如果 foo 是一个字符串,并且你定义了一个验证规则 self.foo.contains("someString"),那么该规则的执行时间取决于 foo 的长度。另一个例子是,如果 foo 是一个数组,并且你指定了一个验证规则 self.foo.all(x, x > 5)。如果未给出 foo 长度的限制,成本系统总是假设最坏情况,这适用于任何可迭代的对象(列表、map 等)。

因此,最佳实践是通过 maxItemsmaxPropertiesmaxLength 对将在验证规则中处理的任何内容设置限制,以防止在成本估算期间发生验证错误。例如,给定包含一条规则的以下 schema

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'。没有限制的情况下,这是一个非常昂贵的规则。

如果未指定任何验证限制,此规则的估计成本将超出每条规则的成本限制。但如果在适当的位置添加了限制,则该规则将被允许

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'))"

成本估算系统除了考虑规则本身的估计成本外,还会考虑规则将被执行多少次。例如,以下规则的估计成本与前一个示例相同(尽管该规则现在定义在单个数组项上)

openAPIV3Schema:
  type: object
  properties:
    foo:
      type: array
      maxItems: 25
      items:
        type: string
        x-kubernetes-validations:
          - rule: "self.contains('a string'))"
        maxLength: 10

如果列表中的列表包含使用 self.all 的验证规则,其成本远高于具有相同规则的非嵌套列表。在非嵌套列表上允许的规则,可能需要在嵌套列表上设置更低的限制才能被允许。例如,即使未设置限制,以下规则也是允许的

openAPIV3Schema:
  type: object
  properties:
    foo:
      type: array
      items:
        type: integer
    x-kubernetes-validations:
      - rule: "self.all(x, x == 5)"

但在以下 schema(添加了嵌套数组)上使用相同的规则会产生验证错误

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。如果使用了验证规则,应尽量避免嵌套列表和 map。

默认值设置 (Defaulting)

默认值设置允许在OpenAPI v3 验证 schema 中指定默认值

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        # openAPIV3Schema is the schema for validating custom objects.
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                  pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
                  default: "5 0 * * *"
                image:
                  type: string
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 10
                  default: 1
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct

这样 cronSpecreplicas 都会被设置默认值

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  image: my-awesome-cron-image

导致

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "5 0 * * *"
  image: my-awesome-cron-image
  replicas: 1

默认值设置发生在对象上

  • 在向 API 服务器发送请求时使用请求版本的默认值,
  • 从 etcd 读取时使用存储版本的默认值,
  • 在带有非空补丁的变动准入插件之后,使用准入 webhook 对象版本的默认值。

从 etcd 读取数据时应用的默认值不会自动写回 etcd。需要通过 API 发送更新请求才能将这些默认值持久化回 etcd。

非叶子字段的默认值必须经过剪枝(metadata 字段的默认值除外),并且必须根据提供的 schema 进行验证。例如,在上面的示例中,为 spec 字段设置默认值 {"replicas": "foo", "badger": 1} 是无效的,因为 badger 是一个未知字段,而 replicas 不是字符串。

对于 x-kubernetes-embedded-resources: true 节点的 metadata 字段(或默认值中覆盖 metadata 的部分),其默认值在 CustomResourceDefinition 创建期间不会被剪枝,而是在处理请求期间通过剪枝步骤进行。

默认值设置和可空性 (Nullable)

对于未指定 nullable 标志或将其值设为 false 的字段,其 Null 值将在默认值设置发生之前被剪枝。如果存在默认值,则会应用该默认值。当 nullable 为 true 时,Null 值将被保留,不会被设置默认值。

例如,给定以下的 OpenAPI schema

type: object
properties:
  spec:
    type: object
    properties:
      foo:
        type: string
        nullable: false
        default: "default"
      bar:
        type: string
        nullable: true
      baz:
        type: string

创建一个 foobarbaz 的值为 null 的对象

spec:
  foo: null
  bar: null
  baz: null

导致

spec:
  foo: "default"
  bar: null

其中 foo 因为是非 nullable 字段而被剪枝并设置了默认值,bar 由于 nullable: true 而保留了 Null 值,而 baz 因为是非 nullable 字段且没有默认值而被剪枝。

在 OpenAPI 中发布验证 Schema

符合结构性启用剪枝的 CustomResourceDefinition OpenAPI v3 验证 schema 会从 Kubernetes API 服务器发布为 OpenAPI v3 和 OpenAPI v2。建议使用 OpenAPI v3 文档,因为它无损地表示了 CustomResourceDefinition OpenAPI v3 验证 schema,而 OpenAPI v2 则是有损转换。

kubectl 命令行工具使用发布的 schema 对自定义资源执行客户端验证 (kubectl createkubectl apply) 和 schema 解释 (kubectl explain)。发布的 schema 也可用于其他目的,例如客户端生成或文档。

与 OpenAPI V2 的兼容性

为了与 OpenAPI V2 兼容,OpenAPI v3 验证 schema 会有损地转换为 OpenAPI v2 schema。该 schema 显示在OpenAPI v2 规范definitionspaths 字段中。

在转换过程中会应用以下修改,以保持与之前 1.13 版本中 kubectl 的向后兼容性。这些修改可以防止 kubectl 过度严格并拒绝它不理解的有效 OpenAPI schema。此转换不会修改 CRD 中定义的验证 schema,因此不会影响 API 服务器中的验证

  1. 以下字段由于 OpenAPI v2 不支持而被移除。

    • 字段 allOfanyOfoneOfnot 被移除
  2. 如果设置了 nullable: true,则会丢弃 typenullableitemsproperties,因为 OpenAPI v2 无法表达 nullable。为了避免 kubectl 拒绝正常的对象,这是必要的。

附加打印列

kubectl 工具依赖于服务器端输出格式化。集群的 API 服务器决定了 kubectl get 命令显示哪些列。可以为 CustomResourceDefinition 自定义这些列。以下示例添加了 SpecReplicasAge 列。

将 CustomResourceDefinition 保存到文件 resourcedefinition.yaml 中:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              cronSpec:
                type: string
              image:
                type: string
              replicas:
                type: integer
    additionalPrinterColumns:
    - name: Spec
      type: string
      description: The cron spec defining the interval a CronJob is run
      jsonPath: .spec.cronSpec
    - name: Replicas
      type: integer
      description: The number of jobs launched by the CronJob
      jsonPath: .spec.replicas
    - name: Age
      type: date
      jsonPath: .metadata.creationTimestamp

创建 CustomResourceDefinition

kubectl apply -f resourcedefinition.yaml

使用上一节中的 my-crontab.yaml 创建一个实例。

调用服务器端打印

kubectl get crontab my-new-cron-object

注意输出中的 NAMESPECREPLICASAGE

NAME                 SPEC        REPLICAS   AGE
my-new-cron-object   * * * * *   1          7s

优先级

每列都包含一个 priority 字段。目前,优先级区分了在标准视图或宽视图(使用 -o wide 标志)中显示的列。

  • 优先级为 0 的列显示在标准视图中。
  • 优先级大于 0 的列仅显示在宽视图中。

类型

列的 type 字段可以是以下任何一种类型(参考OpenAPI v3 数据类型

  • integer – 非浮点数
  • number – 浮点数
  • string – 字符串
  • booleantruefalse
  • date – 渲染为自此时间戳以来的时间差。

如果 CustomResource 中的值与为列指定的类型不匹配,则会省略该值。使用 CustomResource 验证确保值类型正确。

格式

列的 format 字段可以是以下任何一种格式

  • int32
  • int64
  • float
  • double
  • byte
  • date
  • date-time
  • password

列的 format 控制 kubectl 打印值时使用的样式。

字段选择器

字段选择器允许客户端根据一个或多个资源字段的值来选择自定义资源。

所有自定义资源都支持 metadata.namemetadata.namespace 字段选择器。

CustomResourceDefinition 中声明的字段,当包含在 CustomResourceDefinitionspec.versions[*].selectableFields 字段中时,也可与字段选择器一起使用。

自定义资源的可选字段

特性状态: Kubernetes v1.32 [stable](默认启用:true)

CustomResourceDefinitionspec.versions[*].selectableFields 字段可用于声明自定义资源中的哪些其他字段可以通过 CustomResourceFieldSelectors 特性门控用于字段选择器(自 Kubernetes v1.31 起此特性门控默认启用)。以下示例将 .spec.color.spec.size 字段添加为可选字段。

将 CustomResourceDefinition 保存到 shirt-resource-definition.yaml

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: shirts.stable.example.com
spec:
  group: stable.example.com
  scope: Namespaced
  names:
    plural: shirts
    singular: shirt
    kind: Shirt
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              color:
                type: string
              size:
                type: string
    selectableFields:
    - jsonPath: .spec.color
    - jsonPath: .spec.size
    additionalPrinterColumns:
    - jsonPath: .spec.color
      name: Color
      type: string
    - jsonPath: .spec.size
      name: Size
      type: string

创建 CustomResourceDefinition

kubectl apply -f https://k8s.io/examples/customresourcedefinition/shirt-resource-definition.yaml

通过编辑 shirt-resources.yaml 定义一些 Shirt;例如

---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
  name: example1
spec:
  color: blue
  size: S
---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
  name: example2
spec:
  color: blue
  size: M
---
apiVersion: stable.example.com/v1
kind: Shirt
metadata:
  name: example3
spec:
  color: green
  size: M

创建自定义资源

kubectl apply -f https://k8s.io/examples/customresourcedefinition/shirt-resources.yaml

获取所有资源

kubectl get shirts.stable.example.com

输出如下

NAME       COLOR  SIZE
example1   blue   S
example2   blue   M
example3   green  M

获取蓝色 Shirt(检索 colorblue 的 Shirt)

kubectl get shirts.stable.example.com --field-selector spec.color=blue

输出应为

NAME       COLOR  SIZE
example1   blue   S
example2   blue   M

只获取 colorgreensizeM 的资源

kubectl get shirts.stable.example.com --field-selector spec.color=green,spec.size=M

输出应为

NAME       COLOR  SIZE
example2   blue   M

子资源

自定义资源支持 /status/scale 子资源。

status 和 scale 子资源可以通过在 CustomResourceDefinition 中定义它们来选择性启用。

Status 子资源

启用 status 子资源后,自定义资源的 /status 子资源将被暴露。

  • status 和 spec 节分别由自定义资源中的 .status.spec JSONPath 表示。

  • /status 子资源的 PUT 请求接受一个自定义资源对象,并忽略 status 节以外的任何更改。

  • /status 子资源的 PUT 请求仅验证自定义资源的 status 节。

  • 对自定义资源的 PUT/POST/PATCH 请求会忽略对 status 节的更改。

  • 除了对 .metadata.status 的更改外,所有更改都会使 .metadata.generation 的值递增。

  • CRD OpenAPI 验证 schema 的根部只允许以下构造

    • description
    • example
    • exclusiveMaximum
    • exclusiveMinimum
    • externalDocs
    • format
    • items
    • maximum
    • maxItems
    • maxLength
    • minimum
    • minItems
    • minLength
    • multipleOf
    • pattern
    • properties
    • required
    • title
    • type
    • uniqueItems

Scale 子资源

启用 scale 子资源后,自定义资源的 /scale 子资源将被暴露。autoscaling/v1.Scale 对象将作为 /scale 的有效载荷发送。

要启用 scale 子资源,需要在 CustomResourceDefinition 中定义以下字段。

  • specReplicasPath 定义了自定义资源中对应 scale.spec.replicas 的 JSONPath。

    • 这是一个必填字段。
    • 只允许使用点表示法且在 .spec 下的 JSONPath。
    • 如果自定义资源中 specReplicasPath 下没有值,则 /scale 子资源在 GET 请求时会返回错误。
  • statusReplicasPath 定义了自定义资源中对应 scale.status.replicas 的 JSONPath。

    • 这是一个必填字段。
    • 只允许使用点表示法且在 .status 下的 JSONPath。
    • 如果自定义资源中 statusReplicasPath 下没有值,则 /scale 子资源中的 status replica 值将默认为 0。
  • labelSelectorPath 定义了自定义资源中对应 Scale.Status.Selector 的 JSONPath。

    • 这是一个可选字段。
    • 必须设置此字段才能与 HPA 和 VPA 一起工作。
    • 只允许使用点表示法且在 .status.spec 下的 JSONPath。
    • 如果自定义资源中 labelSelectorPath 下没有值,则 /scale 子资源中的 status selector 值将默认为空字符串。
    • 此 JSON 路径指向的字段必须是字符串字段(而不是复杂的 selector 结构),该字段包含字符串形式的序列化标签选择器。

在以下示例中,status 和 scale 子资源都已启用。

将 CustomResourceDefinition 保存到文件 resourcedefinition.yaml 中:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
            status:
              type: object
              properties:
                replicas:
                  type: integer
                labelSelector:
                  type: string
      # subresources describes the subresources for custom resources.
      subresources:
        # status enables the status subresource.
        status: {}
        # scale enables the scale subresource.
        scale:
          # specReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Spec.Replicas.
          specReplicasPath: .spec.replicas
          # statusReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Replicas.
          statusReplicasPath: .status.replicas
          # labelSelectorPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Selector.
          labelSelectorPath: .status.labelSelector
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct

并创建它

kubectl apply -f resourcedefinition.yaml

创建 CustomResourceDefinition 对象后,即可创建自定义对象。

如果你将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image
  replicas: 3

并创建它

kubectl apply -f my-crontab.yaml

然后会在以下位置创建新的命名空间 RESTful API 端点

/apis/stable.example.com/v1/namespaces/*/crontabs/status

以及

/apis/stable.example.com/v1/namespaces/*/crontabs/scale

可以使用 kubectl scale 命令缩放自定义资源。例如,以下命令将上述创建的自定义资源的 .spec.replicas 设置为 5

kubectl scale --replicas=5 crontabs/my-new-cron-object
crontabs "my-new-cron-object" scaled

kubectl get crontabs my-new-cron-object -o jsonpath='{.spec.replicas}'
5

可以使用 PodDisruptionBudget 来保护启用了 scale 子资源的自定义资源。

类别 (Categories)

类别是自定义资源所属的资源分组列表(例如 all)。可以使用 kubectl get <类别名称> 列出属于该类别的资源。

以下示例在 CustomResourceDefinition 的类别列表中添加了 all,并说明了如何使用 kubectl get all 输出自定义资源。

将以下 CustomResourceDefinition 保存到 resourcedefinition.yaml

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct
    # categories is a list of grouped resources the custom resource belongs to.
    categories:
    - all

并创建它

kubectl apply -f resourcedefinition.yaml

创建 CustomResourceDefinition 对象后,即可创建自定义对象。

将以下 YAML 保存到文件 my-crontab.yaml 中:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image

并创建它

kubectl apply -f my-crontab.yaml

使用 kubectl get 时可以指定类别

kubectl get all

并且它将包含 kind 为 CronTab 的自定义资源

NAME                          AGE
crontabs/my-new-cron-object   3s

下一步