Edit This Page

用户自定义资源版本

本页介绍了如何添加版本信息到 CustomResourceDefinitions,如何表示 CustomResourceDefinitions 的稳定水平或者用 API 之间的表征的转换提高您的 API 到一个新的版本。它还描述了如何将对象从一个版本升级到另一个版本。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。 如果你还没有集群,你可以通过 Minikube 构建一 个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:

要获知版本信息,请输入 kubectl version.

  • 确保您的 Kubernetes 集群的主版本为apiextensions.k8s.io/v1的1.16.0或更高版本,apiextensions.k8s.io/v1beta1的1.11.0或更高版本。

概览

FEATURE STATE: Kubernetes v1.18 [stable]

CustomResourceDefinition API 提供了用于引入和升级的工作流程到 CustomResourceDefinition 的新版本。

创建 CustomResourceDefinition 时,会在 CustomResourceDefinition spec.versions 列表设置适当的稳定级和版本号。例如v1beta1表示第一个版本尚未稳定。所有自定义资源对象将首先存储在这个版本

创建 CustomResourceDefinition 后,客户端可以开始使用 v1beta1 API。

稍后可能需要添加新版本,例如 v1。

增加一个新版本:

  1. 选择一种转化策略。由于自定义资源对象需要能够两种版本都可用,这意味着它们有时会以与存储版本不同的版本。为了能够做到这一点, 有时必须在它们存储的版本和提供的版本。如果转换涉及结构变更,并且需要自定义逻辑,转换应该使用 webhook。如果没有结构变更, 则使用 None 默认转换策略,不同版本时只有apiVersion字段有变更。
  2. 如果使用转换 Webhook,请创建并部署转换 Webhook。希望看到更多详细信息,请参见 Webhook conversion
  3. 更新 CustomResourceDefinition,来将新版本包含在具有served:true的 spec.versions 列表。另外,设置spec.conversion字段 到所选的转换策略。如果使用转换 Webhook,请配置spec.conversion.webhookClientConfig来调用 webhook。

添加新版本后,客户端可以逐步迁移到新版本。对于某些客户而言,在使用旧版本的同时支持其他人使用新版本。

将存储的对象迁移到新版本:

  1. 请参阅 将现有对象升级到新的存储版本 章节。

对于客户来说,在将对象升级到新的存储版本之前,期间和之后使用旧版本和新版本都是安全的。

删除旧版本:

  1. 确保所有客户端都已完全迁移到新版本。kube-apiserver 可以查看日志以帮助识别仍通过进行访问的所有客户端旧版本。
  2. spec.versions列表中将旧版本的 served 设置为 false。如果任何客户端仍然意外地使用他们可能开始报告的旧版本尝试访问旧版本的自定义资源对象时出错。 如果发生这种情况,请切换回在旧版本上使用served:true,然后迁移其他客户使用新版本,然后重复此步骤。
  3. 确保已完成 将现有对象升级到新存储版本 的步骤。
    1. 在 CustomResourceDefinition 的spec.versions列表中,确认新版本的stored已设置为true
    2. 确认旧版本不再列在 CustomResourceDefinition status.storedVersions
  4. 从 CustomResourceDefinitionspec.versions 列表中删除旧版本。
  5. 在转换 webhooks 中放弃对旧版本的转换支持。

指定多个版本

CustomResourceDefinition API 的versions字段可用于支持您自定义资源的多个版本已经开发的。版本可以具有不同的架构,并且转换 Webhooks 可以在版本之间转换自定义资源。 在适用的情况下,Webhook 转换应遵循 Kubernetes API

说明: In apiextensions.k8s.io/v1beta1, there was a version field instead of versions. The version field is deprecated and optional, but if it is not empty, it must match the first item in the versions field.

此示例显示了两个版本的 CustomResourceDefinition。第一个例子,假设所有的版本共享相同的模式而它们之间没有转换。YAML 中的评论提供了更多背景信息。

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # A schema is required
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  # The conversion section is introduced in Kubernetes 1.13+ with a default value of
  # None conversion (strategy sub-field set to None).
  conversion:
    # None conversion assumes the same schema for all versions and only sets the apiVersion
    # field of custom resources to the proper value
    strategy: None
  # 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
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
  - name: v1
    served: true
    storage: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        host:
          type: string
        port:
          type: string
  # The conversion section is introduced in Kubernetes 1.13+ with a default value of
  # None conversion (strategy sub-field set to None).
  conversion:
    # None conversion assumes the same schema for all versions and only sets the apiVersion
    # field of custom resources to the proper value
    strategy: None
  # 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

你可以将 CustomResourceDefinition 存储在 YAML 文件中,然后使用kubectl apply来创建它。

kubectl apply -f my-versioned-crontab.yaml

在创建之后,apiserver 开始在 HTTP REST 端点上为每个启用的版本提供服务。在上面的示例中,API 版本可以在/apis/example.com/v1beta1/apis/example.com/v1中获得。

版本优先级

不考虑 CustomResourceDefinition 中版本被定义的顺序,kubectl 使用具有最高优先级的版本作为访问对象的默认版本。 通过解析 name 字段确定优先级来决定版本号,稳定性(GA,Beta,或者 Alpha),以及该稳定性水平内的序列。

用于对版本进行排序的算法被设计成与 Kubernetes 项目对 Kubernetes 版本进行排序的方式相同。 版本以v开头跟一个数字,一个可选的beta 或者 alpha命名,和一个可选的附加的数字型的版本信息。 从广义上讲,版本字符串可能看起来像v2或者v2beta1。 使用以下算法对版本进行排序:

  • 遵循 Kubernetes 版本模式的条目在不符合条件的条目之前进行排序。
  • 对于遵循 Kubernetes 版本模式的条目,版本字符串的数字部分从最大到最小排序。
  • 如果字符串betaalpha跟随第一数字部分,它们按顺序排序,在没有betaalpha后缀(假定为 GA 版本)的等效字符串后面。
  • 如果另一个数字跟在betaalpha之后,那么这些数字也是从最大到最小排序。
  • 不符合上述格式的字符串按字母顺序排序,数字部分不经过特殊处理。请注意,在下面的示例中,foo1foo10上方排序。这与遵循 Kubernetes 版本模式的条目的数字部分排序不同。

如果查看以下排序版本列表可以明白:

- v10
- v2
- v1
- v11beta2
- v10beta3
- v3beta1
- v12alpha1
- v11alpha2
- foo1
- foo10

对于 指定多个版本 中的示例,版本排序顺序为v1,后跟着v1beta1。 这导致了 kubectl 命令使用v1作为默认版本,除非提供对象指定版本。

Webhook转换

说明: Webhook 转换在 Kubernetes 1.13 中作为 alpha 功能引入。要使用它,应启用CustomResourceWebhookConversion功能。请参阅 feature gate 文档以获得更多信息。

上面的例子在版本之间有一个 None 转换,它只在转换时设置apiVersion字段而不改变对象的其余部分。apiserver 还支持在需要转换时调用外部服务的 webhook 转换。例如:

  • 自定义资源被要求在一个不同的版本里而不是一个存储的版本里。
  • Watch 在一个版本中创建,但更改对象存储在另一个版本中。
  • 自定义资源 PUT 请求在一个不同的版本里而不是一个存储的版本。

为了涵盖所有这些情况并通过 API 服务优化转换,转换对象可能包含多个对象,以便最大限度地减少外部调用。webhook 应该独立执行这些转换。

编写一个转换webhook服务

请参考 自定义资源转换 webhook 服务 的实施,这在 Kubernetes e2e 测试中得到验证。webhook 处理由 apiserver 发送的ConversionReview请求,并发送回包含在ConversionResponse中的转换结果。请注意,请求包含需要独立转换不改变对象顺序的自定义资源列表。示例服务器的组织方式使其可以重用于其他转换。大多数常见代码都位于 框架文件 中,只留下 示例 用于实施不同的转换。

说明: 示例转换 webhook 服务器留下ClientAuth字段 empty,默认为NoClientCert。 这意味着 webhook 服务器没有验证客户端的身份,据称是 apiserver。 如果您需要相互 TLS 或者其他方式来验证客户端,请参阅如何 验证 API 服务

部署转换webhook服务

用于部署转换 webhook 的文档与 准入webhook示例服务。 下一节的假设是转换 webhook 服务器部署到default命名空间中名为example-conversion-webhook-server的服务器上,并在路径/crdconvert上提供流量。

说明: 当 webhook 服务器作为一个部署到 Kubernetes 集群中的服务器时,它必须通过端口443上的服务器公开(服务器本身可以有一个任意端口,但是服务器对象应该将它映射到端口443)。 如果为服务器使用不同的端口,则 apiserver 和 webhook 服务器之间的通信可能会失败。

配置CustomResourceDefinition以使用转换webhooks

通过修改spec中的conversion部分,可以扩展None转换示例来使用转换 webhook。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # Each version can define it's own schema when there is no top-level
    # schema is defined.
    schema:
      openAPIV3Schema:
        type: object
        properties:
          hostPort:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  conversion:
    # a Webhook strategy instruct API server to call an external webhook for any conversion between custom resources.
    strategy: Webhook
    # webhook is required when strategy is `Webhook` and it configures the webhook endpoint to be called by API server.
    webhook:
      # conversionReviewVersions indicates what ConversionReview versions are understood/preferred by the webhook.
      # The first version in the list understood by the API server is sent to the webhook.
      # The webhook must respond with a ConversionReview object in the same version it received.
      conversionReviewVersions: ["v1","v1beta1"]
      clientConfig:
        service:
          namespace: default
          name: example-conversion-webhook-server
          path: /crdconvert
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 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
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: example.com
  # prunes object fields that are not specified in OpenAPI schemas below.
  preserveUnknownFields: false
  # list of versions supported by this CustomResourceDefinition
  versions:
  - name: v1beta1
    # 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
    # Each version can define it's own schema when there is no top-level
    # schema is defined.
    schema:
      openAPIV3Schema:
        type: object
        properties:
          hostPort:
            type: string
  - name: v1
    served: true
    storage: false
    schema:
      openAPIV3Schema:
        type: object
        properties:
          host:
            type: string
          port:
            type: string
  conversion:
    # a Webhook strategy instruct API server to call an external webhook for any conversion between custom resources.
    strategy: Webhook
    # webhookClientConfig is required when strategy is `Webhook` and it configures the webhook endpoint to be called by API server.
    webhookClientConfig:
      service:
        namespace: default
        name: example-conversion-webhook-server
        path: /crdconvert
      caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 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

您可以将 CustomResourceDefinition 保存在 YAML 文件中,然后使用kubectl apply来应用它。

kubectl apply -f my-versioned-crontab-with-conversion.yaml

在应用新更改之前,请确保转换服务器已启动并正在运行。

调用-webhook

apiserver 一旦确定请求应发送到转换 webhook,它需要知道如何调用 webhook。这是在webhookClientConfig中指定的 webhook 配置。

转换 webhook 可以通过调用 URL 或服务,并且可以选择包含自定义 CA 包,以用于验证 TLS 连接。

URL

url 以标准 URL 形式给出 webhook 的位置(scheme://host:port/path)。 host不应引用集群中运行的服务;采用通过指定service字段来提供服务引用。 host可以通过某些 apiserver 中的外部 DNS 进行解析(即kube-apiserver无法解析集群内 DNS 违反分层规则)。主机也可以是 IP 地址。

请注意,除非您非常小心在所有主机上运行此 Webhook,否则 localhost 或 127.0.0.1 用作主机是风险很大的,运行一个 apiserver 可能需要对此进行调用 webhook。这样的安装很可能是不可移植的,即不容易出现在新集群中。

方案必须为https:URL 必须以https://开头。

尝试使用用户或基本身份验证,例如不允许使用user:password@。片段(#...)和查询参数(?...)也不允许。

这是配置为调用 URL 的转换 Webhook 的示例(并且期望使用系统信任根来验证 TLS 证书,因此不指定 caBundle):

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        url: "https://my-webhook.example.com:9443/my-webhook-path"
...
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhookClientConfig:
      url: "https://my-webhook.example.com:9443/my-webhook-path"
...

服务引用

webhookClientConfig内部的service段是对转换 webhook 服务的引用。如果 Webhook 在集群中运行,则应使用service而不是url。 服务名称空间和名称是必需的。端口是可选的,默认为 443。该路径是可选的,默认为/

这是一个配置为在端口1234上调用服务的 Webhook 的示例在子路径/my-path下,并针对 ServerName 验证 TLS 连接 使用自定义 CA 捆绑包的my-service-name.my-service-namespace.svc

apiVersion: apiextensions.k8s.io/v1b
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        service:
          namespace: my-service-namespace
          name: my-service-name
          path: /my-path
          port: 1234
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...
# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhookClientConfig:
      service:
        namespace: my-service-namespace
        name: my-service-name
        path: /my-path
        port: 1234
      caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...

Webhook-请求和响应

请求

向 Webhooks 发送 POST 请求,请求的内容类型为:application/json,与 APIapitensions.k8s.io API 组中的 ConversionReview API 对象一起使用序列化为 JSON 作为主体。

Webhooks 可以指定他们接受的ConversionReview对象的版本在其 CustomResourceDefinition 中使用conversionReviewVersions字段:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      conversionReviewVersions: ["v1", "v1beta1"]
      ...

创建时,conversionReviewVersions是必填字段apiextensions.k8s.io/v1自定义资源定义。 需要 Webhooks 支持至少一个ConversionReview当前和以前的 apiserver 可以理解的版本。

# Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    conversionReviewVersions: ["v1", "v1beta1"]
    ...

如果未指定conversionReviewVersions,则创建时的默认值 apiextensions.k8s.io/v1beta1 自定义资源定义为 v1beta1。

API servers send the first ConversionReview version in the conversionReviewVersions list they support. If none of the versions in the list are supported by the API server, the custom resource definition will not be allowed to be created. If an API server encounters a conversion webhook configuration that was previously created and does not support any of the ConversionReview versions the API server knows how to send, attempts to call to the webhook will fail.

此示例显示了包含在ConversionReview对象中的数据请求将CronTab对象转换为example.com/v1

{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "request": {
    # Random uid uniquely identifying this conversion call
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    
    # The API group and version the objects should be converted to
    "desiredAPIVersion": "example.com/v1",
    
    # The list of objects to convert.
    # May contain one or more objects, in one or more versions.
    "objects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "hostPort": "localhost:1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "hostPort": "example.com:2345"
      }
    ]
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "request": {
    # Random uid uniquely identifying this conversion call
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    
    # The API group and version the objects should be converted to
    "desiredAPIVersion": "example.com/v1",
    
    # The list of objects to convert.
    # May contain one or more objects, in one or more versions.
    "objects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "hostPort": "localhost:1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1beta1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "hostPort": "example.com:2345"
      }
    ]
  }
}

响应

Webhooks 响应是以 200 HTTP 状态代码,Content-Type:application/json,和包含 ConversionReview 对象的主体(与发送的版本相同), 带有response节的序列,并序列化为 JSON。

如果转换成功,则 Webhook 应该返回包含以下字段的response节:

  • uid,从发送到 webhook 的request.uid复制而来
  • result,设置为{"status":"Success"}}
  • convertedObjects,包含来自request.objects的所有对象,转换为request.desiredVersion

Webhook 的最简单成功响应示例:

{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "response": {
    # must match <request.uid>
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    "result": {
      "status": "Success"
    },
    # Objects must match the order of request.objects, and have apiVersion set to <request.desiredAPIVersion>.
    # kind, metadata.uid, metadata.name, and metadata.namespace fields must not be changed by the webhook.
    # metadata.labels and metadata.annotations fields may be changed by the webhook.
    # All other changes to metadata fields by the webhook are ignored.
    "convertedObjects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "host": "localhost",
        "port": "1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "host": "example.com",
        "port": "2345"
      }
    ]
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "response": {
    # must match <request.uid>
    "uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
    "result": {
      "status": "Failed"
    },
    # Objects must match the order of request.objects, and have apiVersion set to <request.desiredAPIVersion>.
    # kind, metadata.uid, metadata.name, and metadata.namespace fields must not be changed by the webhook.
    # metadata.labels and metadata.annotations fields may be changed by the webhook.
    # All other changes to metadata fields by the webhook are ignored.
    "convertedObjects": [
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-04T14:03:02Z",
          "name": "local-crontab",
          "namespace": "default",
          "resourceVersion": "143",
          "uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
        },
        "host": "localhost",
        "port": "1234"
      },
      {
        "kind": "CronTab",
        "apiVersion": "example.com/v1",
        "metadata": {
          "creationTimestamp": "2019-09-03T13:02:01Z",
          "name": "remote-crontab",
          "resourceVersion": "12893",
          "uid": "359a83ec-b575-460d-b553-d859cedde8a0"
        },
        "host": "example.com",
        "port": "2345"
      }
    ]
  }
}

如果转换失败,则 Webhook 应该返回包含以下字段的response节: *uid,从发送到 webhook 的request.uid复制而来 *result,设置为{"status":"Failed"}

警告:

转换失败会破坏对自定义资源的读写访问,包括更新或删除资源的能力。转换失败应尽可能避免使用,并且不应用于强制验证 约束(改用验证模式 或 Webhook admission)。

来自 Webhook 的响应示例,指示转换请求失败,并带有可选消息:

{
  "apiVersion": "apiextensions.k8s.io/v1",
  "kind": "ConversionReview",
  "response": {
    "uid": "<value from request.uid>",
    "result": {
      "status": "Failed",
      "message": "hostPort could not be parsed into a separate host and port"
    }
  }
}
{
  # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "kind": "ConversionReview",
  "response": {
    "uid": "<value from request.uid>",
    "result": {
      "status": "Failed",
      "message": "hostPort could not be parsed into a separate host and port"
    }
  }
}

编写、读取和更新版本化的 CustomResourceDefinition 对象

写入对象时,它将保留在写入时指定为存储版本的版本中。如果存储版本发生变化,现有对象永远不会自动转换。然而,新创建或更新的对象将在新的存储版本中编写。对象可能已在不再被服务的版本中编写。

当读取对象时,将版本指定为路径的一部分。 如果指定的版本与对象的持久版本不同,Kubernetes 会在您请求的版本里将对象返还给您,但是在提供请求时,持久化对象既不会在磁盘上更改,也不会以任何方式进行转换(除了更改apiVersion字符串)。您可以在当前提供的任何版本中请求对象。

如果您更新了一个现有对象,它将在现在的存储版本中被重写。 这是对象可以从一个版本改到另一个版本的唯一办法。

为了说明这一点,请考虑以下假设的一系列事件:

  1. 存储版本是v1beta1。 它保存在版本v1beta1的存储中。
  2. 您将版本v1添加到 CustomResourceDefinition 中,并将其指定为存储版本。
  3. 您在版本v1beta1中读取您的对象,然后您再次在版本v1中读取对象。 除了 apiVersion 字段之外,两个返回的对象都是相同的。
  4. 您创建一个新对象。 它存储在版本v1的存储中。 您现在有两个对象,其中一个位于v1beta1,另一个位于v1
  5. 您更新第一个对象。 它现在保存在版本v1中,因为那是当前的存储版本。

以前的存储版本

API 服务在状态字段storedVersions中记录曾被标记为存储版本的每个版本。 对象可能已被保留在任何曾被指定为存储版本的版本中。 从未成为存储版本的版本的存储中不能存在任何对象。

将现有对象升级到新的存储版本

弃用版本并删除支持时,请设计存储升级过程。 以下是从v1beta1升级到v1的示例过程。

  1. v1设置为 CustomResourceDefinition 文件中的存储,并使用 kubectl 应用它。 storedVersions现在是v1beta1、 v1
  2. 编写升级过程以列出所有现有对象并使用相同内容编写它们。 这会强制后端在当前存储版本中写入对象,即v1
  3. 通过从storedVersions字段中删除v1beta1来更新 CustomResourceDefinitionStatus