---
title: CustomResourceDefinition 的版本
content_type: task
weight: 30
min-kubernetes-server-version: v1.16
---
<!--
title: Versions in CustomResourceDefinitions
reviewers:
- sttts
- liggitt
content_type: task
weight: 30
min-kubernetes-server-version: v1.16
-->

<!-- overview -->
<!--
This page explains how to add versioning information to
[CustomResourceDefinitions](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#customresourcedefinition-v1beta1-apiextensions), to indicate the stability
level of your CustomResourceDefinitions or advance your API to a new version with conversion between API representations. It also describes how to upgrade an object from one version to another.
-->
本页介绍如何添加版本信息到
[CustomResourceDefinitions](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#customresourcedefinition-v1beta1-apiextensions)。
目的是标明 CustomResourceDefinitions 的稳定级别或者服务于 API 升级。
API 升级时需要在不同 API 表示形式之间进行转换。
本页还描述如何将对象从一个版本升级到另一个版本。

## {{% heading "prerequisites" %}}

{{< include "task-tutorial-prereqs.md" >}}

<!--
You should have a initial understanding of [custom resources](/docs/concepts/extend-kubernetes/api-extension/custom-resources/).
-->
你应该对[定制资源](/zh/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
有一些初步了解。

{{< version-check >}}

<!-- steps -->

<!--
## Overview

The CustomResourceDefinition API provides a workflow for introducing and upgrading
to new versions of a CustomResourceDefinition.

When a CustomResourceDefinition is created, the first version is set in the
CustomResourceDefinition `spec.versions` list to an appropriate stability level
and a version number. For example `v1beta1` would indicate that the first
version is not yet stable. All custom resource objects will initially be stored
at this version.

Once the CustomResourceDefinition is created, clients may begin using the
`v1beta1` API.

Later it might be necessary to add new version such as `v1`.
-->
## 概览

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

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

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

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

<!--
Adding a new version:

1. Pick a conversion strategy. Since custom resource objects need to be able to
   be served at both versions, that means they will sometimes be served at a
   different version than their storage version. In order for this to be
   possible, the custom resource objects must sometimes be converted between the
   version they are stored at and the version they are served at. If the
   conversion involves schema changes and requires custom logic, a conversion
   webhook should be used. If there are no schema changes, the default `None`
   conversion strategy may be used and only the `apiVersion` field will be
   modified when serving different versions.
2. If using conversion webhooks, create and deploy the conversion webhook. See
   the [Webhook conversion](#webhook-conversion) for more details.
3. Update the CustomResourceDefinition to include the new version in the
   `spec.versions` list with `served:true`.  Also, set `spec.conversion` field
   to the selected conversion strategy. If using a conversion webhook, configure
   `spec.conversion.webhookClientConfig` field to call the webhook.
-->
添加新版本：

1. 选择一种转化策略。由于定制资源对象需要能够两种版本都可用，
   这意味着它们有时会以与存储版本不同的版本来提供服务。为了能够做到这一点，
   有时必须在它们存储的版本和提供的版本之间进行转换。如果转换涉及模式变更，
   并且需要自定义逻辑，则应该使用 Webhook 来完成。如果没有模式变更，
   则可使用默认的 `None` 转换策略，为不同版本提供服务时只有 `apiVersion` 字段
   会被改变。
2. 如果使用转换 Webhook，请创建并部署转换 Webhook。更多详细信息请参见
   [Webhook conversion](#webhook-conversion)。
3. 更新 CustomResourceDefinition，将新版本设置为 `served：true`，加入到
   `spec.versions` 列表。另外，还要设置 `spec.conversion` 字段
   为所选的转换策略。如果使用转换 Webhook，请配置
   `spec.conversion.webhookClientConfig` 来调用 Webhook。

<!--
Once the new version is added, clients may incrementally migrate to the new
version. It is perfectly safe for some clients to use the old version while
others use the new version.

Migrate stored objects to the new version:
-->
添加新版本后，客户端可以逐步迁移到新版本。让某些客户使用旧版本的同时
支持其他人使用新版本是相当安全的。

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

<!--
1. See the [upgrade existing objects to a new stored version](#upgrade-existing-objects-to-a-new-stored-version) section.

It is safe for clients to use both the old and new version before, during and
after upgrading the objects to a new stored version.
-->

1. 请参阅[将现有对象升级到新的存储版本](#upgrade-existing-objects-to-a-new-stored-version)节。

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

<!--
Removing an old version:

1. Ensure all clients are fully migrated to the new version. The kube-apiserver
   logs can reviewed to help identify any clients that are still accessing via
   the old version.
1. Set `served` to `false` for the old version in the `spec.versions` list. If
   any clients are still unexpectedly using the old version they may begin reporting
   errors attempting to access the custom resource objects at the old version.
   If this occurs, switch back to using `served:true` on the old version, migrate the 
   remaining clients to the new version and repeat this step.
1. Ensure the [upgrade of existing objects to the new stored version](#upgrade-existing-objects-to-a-new-stored-version) step has been completed.
    1. Verify that the `stored` is set to `true` for the new version in the `spec.versions` list in the CustomResourceDefinition.
    1. Verify that the old version is no longer listed in the CustomResourceDefinition `status.storedVersions`.
1. Remove the old version from the CustomResourceDefinition `spec.versions` list.
1. Drop conversion support for the old version in conversion webhooks.
-->
删除旧版本：

1. 确保所有客户端都已完全迁移到新版本。
   可以查看 kube-apiserver 的日志以识别仍通过旧版本进行访问的所有客户端。
1. 在 `spec.versions` 列表中将旧版本的 `served` 设置为 `false`。
   如果仍有客户端意外地使用旧版本，他们可能开始会报告采用旧版本尝试访
   定制资源的错误消息。
   如果发生这种情况，请将旧版本的`served：true` 恢复，然后迁移余下的客户端
   使用新版本，然后重复此步骤。
1. 确保已完成[将现有对象升级到新存储版本](#upgrade-existing-objects-to-a-new-stored-version)
   的步骤。
   1. 在 CustomResourceDefinition 的 `spec.versions` 列表中，确认新版本的
      `stored` 已被设置为 `true`。
   2. 确认旧版本不在 CustomResourceDefinition `status.storedVersions` 中。
1. 从 CustomResourceDefinition `spec.versions` 列表中删除旧版本。
1. 在转换 Webhooks 中放弃对旧版本的转换支持。

<!--
## Specify multiple versions

The CustomResourceDefinition API `versions` field can be used to support multiple versions of custom resources that you
have developed. Versions can have different schemas, and conversion webhooks can convert custom resources between versions.
Webhook conversions should follow the [Kubernetes API conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md) wherever applicable.
Specifically, See the [API change documentation](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md) for a set of useful gotchas and suggestions.
-->
## 指定多个版本  {#specify-multiple-versions}

CustomResourceDefinition API 的 `versions` 字段可用于支持你所开发的
定制资源的多个版本。版本可以具有不同的模式，并且转换 Webhooks
可以在多个版本之间转换定制资源。
在适当的情况下，Webhook 转换应遵循
[Kubernetes API 约定](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md)。
尤其是，请查阅
[API 变更文档](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md)
以了解一些有用的常见错误和建议。

<!--
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.
-->
{{< note >}}
在 `apiextensions.k8s.io/v1beta1` 版本中曾经有一个 `version` 字段，
名字不叫做 `versions`。该 `version` 字段已经被废弃，成为可选项。
不过如果该字段不是空，则必须与 `versions` 字段中的第一个条目匹配。
{{< /note >}}

<!--
This example shows a CustomResourceDefinition with two versions. For the first
example, the assumption is all versions share the same schema with no conversion
between them. The comments in the YAML provide more context.
-->
下面的示例显示了两个版本的 CustomResourceDefinition。
第一个例子中假设所有的版本使用相同的模式而它们之间没有转换。
YAML 中的注释提供了更多背景信息。

{{< tabs name="CustomResourceDefinition_versioning_example_1" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name 必须匹配后面 spec 中的字段，且使用格式 <plural>.<group>
  name: crontabs.example.com
spec:
  # 组名，用于 REST API: /apis/<group>/<version>
  group: example.com
  # 此 CustomResourceDefinition 所支持的版本列表
  versions:
  - name: v1beta1
    # 每个 version 可以通过 served 标志启用或禁止
    served: true
    # 有且只能有一个 version 必须被标记为存储版本
    storage: true
    # schema 是必需字段
    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
  # conversion 节是 Kubernetes 1.13+ 版本引入的，其默认值为无转换，即
  # strategy 子字段设置为 None。
  conversion:
    # None 转换假定所有版本采用相同的模式定义，仅仅将定制资源的 apiVersion
    # 设置为合适的值.
    strategy: None
  # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式，用于 URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # 名称的单数形式，用于在命令行接口和显示时作为其别名
    singular: crontab
    # kind 通常是驼峰编码（CamelCased）的单数形式，用于资源清单中
    kind: CronTab
    # shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
    shortNames:
    - ct
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}

```yaml
# 在 v1.16 中被弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name 必须匹配后面 spec 中的字段，且使用格式 <plural>.<group>
  name: crontabs.example.com
spec:
  # 组名，用于 REST API: /apis/<group>/<version>
  group: example.com
  # 此 CustomResourceDefinition 所支持的版本列表
  versions:
  - name: v1beta1
    # 每个 version 可以通过 served 标志启用或禁止
    served: true
    # 有且只能有一个 version 必须被标记为存储版本
    storage: true
  - name: v1
    served: true
    storage: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        host:
          type: string
        port:
          type: string
  # conversion 节是 Kubernetes 1.13+ 版本引入的，其默认值为无转换，即
  # strategy 子字段设置为 None。
  conversion:
    # None 转换假定所有版本采用相同的模式定义，仅仅将定制资源的 apiVersion
    # 设置为合适的值.
    strategy: None
  # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式，用于 URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # 名称的单数形式，用于在命令行接口和显示时作为其别名
    singular: crontab
    # kind 通常是驼峰编码（CamelCased）的单数形式，用于资源清单中
    kind: CronTab
    # shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
    shortNames:
    - ct
```
{{% /tab %}}
{{< /tabs >}}

<!--
You can save the CustomResourceDefinition in a YAML file, then use
`kubectl apply` to create it.
-->
你可以将 CustomResourceDefinition 存储在 YAML 文件中，然后使用
`kubectl apply` 来创建它。

```shell
kubectl apply -f my-versioned-crontab.yaml
```

<!--
After creation, the API server starts to serve each enabled version at an HTTP
REST endpoint. In the above example, the API versions are available at
`/apis/example.com/v1beta1` and `/apis/example.com/v1`.
-->
在创建之后，API 服务器开始在 HTTP REST 端点上为每个已启用的版本提供服务。
在上面的示例中，API 版本可以在 `/apis/example.com/v1beta1` 和
`/apis/example.com/v1` 处获得。

<!--
### Version priority

Regardless of the order in which versions are defined in a
CustomResourceDefinition, the version with the highest priority is used by
kubectl as the default version to access objects. The priority is determined
by parsing the _name_ field to determine the version number, the stability
(GA, Beta, or Alpha), and the sequence within that stability level.
-->
### 版本优先级

不考虑 CustomResourceDefinition 中版本被定义的顺序，kubectl 使用
具有最高优先级的版本作为访问对象的默认版本。
通过解析 _name_ 字段确定优先级来决定版本号，稳定性（GA、Beta 或 Alpha）
级别及该稳定性级别内的序列。

<!--
The algorithm used for sorting the versions is designed to sort versions in the
same way that the Kubernetes project sorts Kubernetes versions. Versions start with a
`v` followed by a number, an optional `beta` or `alpha` designation, and
optional additional numeric versioning information. Broadly, a version string might look
like `v2` or `v2beta1`. Versions are sorted using the following algorithm:
-->
用于对版本进行排序的算法在设计上与 Kubernetes 项目对 Kubernetes 版本进行排序的方式相同。
版本以 `v` 开头跟一个数字，一个可选的 `beta` 或者 `alpha` 和一个可选的附加数字
作为版本信息。
从广义上讲，版本字符串可能看起来像 `v2` 或者 `v2beta1`。
使用以下算法对版本进行排序：

<!--
- Entries that follow Kubernetes version patterns are sorted before those that
  do not.
- For entries that follow Kubernetes version patterns, the numeric portions of
  the version string is sorted largest to smallest.
- If the strings `beta` or `alpha` follow the first numeric portion, they sorted
  in that order, after the equivalent string without the `beta` or `alpha`
  suffix (which is presumed to be the GA version).
- If another number follows the `beta`, or `alpha`, those numbers are also
  sorted from largest to smallest.
- Strings that don't fit the above format are sorted alphabetically and the
  numeric portions are not treated specially. Notice that in the example below,
  `foo1` is sorted above `foo10`. This is different from the sorting of the
  numeric portion of entries that do follow the Kubernetes version patterns.
-->
- 遵循 Kubernetes 版本模式的条目在不符合条件的条目之前进行排序。
- 对于遵循 Kubernetes 版本模式的条目，版本字符串的数字部分从最大到最小排序。
- 如果第一个数字后面有字符串 `beta` 或 `alpha`，它们首先按去掉 `beta` 或
  `alpha` 之后的版本号排序（相当于 GA 版本），之后按 `beta` 先、`alpha` 后的顺序排序，
- 如果 `beta` 或 `alpha` 之后还有另一个数字，那么也会针对这些数字
  从大到小排序。
- 不符合上述格式的字符串按字母顺序排序，数字部分不经过特殊处理。
  请注意，在下面的示例中，`foo1` 排在 `foo10` 之前。
  这与遵循 Kubernetes 版本模式的条目的数字部分排序不同。

<!--
This might make sense if you look at the following sorted version list:
-->
如果查看以下版本排序列表，这些规则就容易懂了：

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

<!--
For the example in [Specify multiple versions](#specify-multiple-versions), the
version sort order is `v1`, followed by `v1beta1`. This causes the kubectl
command to use `v1` as the default version unless the provided object specifies
the version.
-->
对于[指定多个版本](#specify-multiple-versions)中的示例，版本排序顺序为
`v1`，后跟着 `v1beta1`。
这导致了 kubectl 命令使用 `v1` 作为默认版本，除非所提供的对象指定了版本。

<!--
### Version deprecation
-->
### 版本废弃

{{< feature-state state="stable" for_k8s_version="v1.19" >}}

<!--
Starting in v1.19, a CustomResourceDefinition can indicate a particular version of the resource it defines is deprecated.
When API requests to a deprecated version of that resource are made, a warning message is returned in the API response as a header.
The warning message for each deprecated version of the resource can be customized if desired.
-->
从 v1.19 开始，CustomResourceDefinition 可用来标明所定义的资源的特定版本
被废弃。当发起对已废弃的版本的 API 请求时，会在 API 响应中以 HTTP 头部
的形式返回警告消息。
如果需要，可以对资源的每个废弃版本定制该警告消息。

<!--
A customized warning message should indicate the deprecated API group, version, and kind,
and should indicate what API group, version, and kind should be used instead, if applicable.
-->
定制的警告消息应该标明废弃的 API 组、版本和类别（kind），并且应该标明
应该使用（如果有的话）哪个 API 组、版本和类别作为替代。

{{< tabs name="CustomResourceDefinition_versioning_deprecated" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
  name: crontabs.example.com
spec:
  group: example.com
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
  scope: Namespaced
  versions:
  - name: v1alpha1
    served: true
    # 此属性标明此定制资源的 v1alpha1 版本已被弃用。
    # 发给此版本的 API 请求会在服务器响应中收到警告消息头。
    deprecated: true
    # 此属性设置用来覆盖返回给发送 v1alpha1 API 请求的客户端的默认警告信息。
    deprecationWarning: "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"
    schema: ...
  - name: v1beta1
    served: true
    # 此属性标明该定制资源的 v1beta1 版本已被弃用。
    # 发给此版本的 API 请求会在服务器响应中收到警告消息头。
    # 针对此版本的请求所返回的是默认的警告消息。
    deprecated: true
    schema: ...
  - name: v1
    served: true
    storage: true
    schema: ...
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# 在 v1.16 中弃用以推荐使用  apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: crontabs.example.com
spec:
  group: example.com
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
  scope: Namespaced
  validation: ...
  versions:
  - name: v1alpha1
    served: true
    # 此属性标明此定制资源的 v1alpha1 版本已被弃用。
    # 发给此版本的 API 请求会在服务器响应中收到警告消息头。
    deprecated: true
    # 此属性设置用来覆盖返回给发送 v1alpha1 API 请求的客户端的默认警告信息。
    deprecationWarning: "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"
  - name: v1beta1
    served: true
    # 此属性标明该定制资源的 v1beta1 版本已被弃用。
    # 发给此版本的 API 请求会在服务器响应中收到警告消息头。
    # 针对此版本的请求所返回的是默认的警告消息。
    deprecated: true
  - name: v1
    served: true
    storage: true
```
{{% /tab %}}
{{< /tabs >}}


<!--
## Webhook conversion

Webhook conversion is available as beta since 1.15, and as alpha since Kubernetes 1.13. The
`CustomResourceWebhookConversion` feature should be enabled. Please refer to the [feature gate](/docs/reference/command-line-tools-reference/feature-gates/) documentation for more information.
-->
## Webhook 转换   {#webhook-conversion}

{{< feature-state state="stable" for_k8s_version="v1.16" >}}

{{< note >}}
Webhook 转换在 Kubernetes 1.13 版本引入，在 Kubernetes 1.15 中成为 Beta 功能。
要使用此功能，应启用 `CustomResourceWebhookConversion` 特性。
在大多数集群上，这类 Beta 特性应该是自动启用的。
请参阅[特性门控](/zh/docs/reference/command-line-tools-reference/feature-gates/)
文档以获得更多信息。
{{< /note >}}

<!--
The above example has a None conversion between versions which only sets the `apiVersion` field
on conversion and does not change the rest of the object. The API server also supports webhook
conversions that call an external service in case a conversion is required. For example when:
-->
上面的例子在版本之间有一个 None 转换，它只在转换时设置 `apiVersion` 字段
而不改变对象的其余部分。API 服务器还支持在需要转换时调用外部服务的 webhook 转换。
例如：

<!--
* custom resource is requested in a different version than stored version.
* Watch is created in one version but the changed object is stored in another version.
* custom resource PUT request is in a different version than storage version.
-->
* 定制资源的请求版本与其存储版本不同。
* 使用某版本创建了 Watch 请求，但所更改对象以另一版本存储。
* 定制资源的 PUT 请求所针对版本与存储版本不同。

<!--
To cover all of these cases and to optimize conversion by the API server,
the conversion requests may contain multiple objects in order to minimize the external calls.
The webhook should perform these conversions independently.
-->
为了涵盖所有这些情况并优化 API 服务器所作的转换，转换请求可以包含多个对象，
以便减少外部调用。Webhook 应该独立执行各个转换。

<!--
### Write a conversion webhook server

Please refer to the implementation of the [custom resource conversion webhook
server](https://github.com/kubernetes/kubernetes/tree/v1.15.0/test/images/crd-conversion-webhook/main.go)
that is validated in a Kubernetes e2e test. The webhook handles the
`ConversionReview` requests sent by the API servers, and sends back conversion
results wrapped in `ConversionResponse`. Note that the request
contains a list of custom resources that need to be converted independently without
changing the order of objects.
The example server is organized in a way to be reused for other conversions.
Most of the common code are located in the
[framework file](https://github.com/kubernetes/kubernetes/tree/v1.15.0/test/images/crd-conversion-webhook/converter/framework.go)
that leaves only
[one function](https://github.com/kubernetes/kubernetes/blob/v1.15.0/test/images/crd-conversion-webhook/converter/example_converter.go#L29-L80)
to be implemented for different conversions.
-->
### 编写一个转换 Webhook 服务器

请参考[定制资源转换 Webhook 服务器](https://github.com/kubernetes/kubernetes/tree/v1.15.0/test/images/crd-conversion-webhook/main.go)
的实现；该实现在 Kubernetes e2e 测试中得到验证。
Webhook 处理由 API 服务器发送的 `ConversionReview` 请求，并在
`ConversionResponse` 中封装发回转换结果。
请注意，请求包含需要独立转换的定制资源列表，这些对象在被转换之后不能改变其
在列表中的顺序。该示例服务器的组织方式使其可以复用于其他转换。
大多数常见代码都位于
[framework 文件](https://github.com/kubernetes/kubernetes/tree/v1.15.0/test/images/crd-conversion-webhook/converter/framework.go)
中，只留下
[一个函数](https://github.com/kubernetes/kubernetes/blob/v1.13.0/test/images/crd-conversion-webhook/converter/example_converter.go#L29-L80)
用于实现不同的转换。

<!--
The example conversion webhook server leaves the `ClientAuth` field
[empty](https://github.com/kubernetes/kubernetes/tree/v1.13.0/test/images/crd-conversion-webhook/config.go#L47-L48),
which defaults to `NoClientCert`. This means that the webhook server does not
authenticate the identity of the clients, supposedly API servers. If you need
mutual TLS or other ways to authenticate the clients, see
how to [authenticate API servers](/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers).
-->
{{< note >}}
转换 Webhook 服务器示例中将 `ClientAuth` 字段设置为
[空](https://github.com/kubernetes/kubernetes/tree/v1.13.0/test/images/crd-conversion-webhook/config.go#L47-L48)，
默认为 `NoClientCert`。
这意味着 webhook 服务器没有验证客户端（也就是 API 服务器）的身份。
如果你需要双向 TLS 或者其他方式来验证客户端，请参阅如何
[验证 API 服务](/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers)。
{{< /note >}}

<!--
#### Permissible mutations

A conversion webhook must not mutate anything inside of `metadata` of the converted object
other than `labels` and `annotations`.
Attempted changes to `name`, `UID` and `namespace` are rejected and fail the request
which caused the conversion. All other changes are just ignored.  
-->
#### 被允许的变更

转换 Webhook 不可以更改被转换对象的 `metadata` 中除 `labels` 和 `annotations`
之外的任何属性。
尝试更改 `name`、`UID` 和 `namespace` 时都会导致引起转换的请求失败。
所有其他变更只是被忽略而已。

<!--
### Deploy the conversion webhook service

Documentation for deploying the conversion webhook is the same as for the [admission webhook example service](/docs/reference/access-authn-authz/extensible-admission-controllers/#deploy_the_admission_webhook_service).
The assumption for next sections is that the conversion webhook server is deployed to a service named `example-conversion-webhook-server` in `default` namespace and serving traffic on path `/crdconvert`.
-->
### 部署转换 Webhook 服务

用于部署转换 webhook 的文档与
[准入 Webhook 服务示例](/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#deploy_the_admission_webhook_service)相同。
这里的假设是转换 Webhook 服务器被部署为 `default` 名字空间中名为
`example-conversion-webhook-server` 的服务，并在路径 `/crdconvert`
上处理请求。

<!--
When the webhook server is deployed into the Kubernetes cluster as a
service, it has to be exposed via a service on port 443 (The server
itself can have an arbitrary port but the service object should map it to port 443).
The communication between the API server and the webhook service may fail
if a different port is used for the service.
-->
{{< note >}}
当 Webhook 服务器作为一个服务被部署到 Kubernetes 集群中时，它必须
通过端口 443 公开其服务（服务器本身可以使用任意端口，但是服务对象
应该将它映射到端口 443）。
如果为服务器使用不同的端口，则 API 服务器和 Webhook 服务器之间的通信
可能会失败。
{{< /note >}}

<!--
### Configure CustomResourceDefinition to use conversion webhooks

The `None` conversion example can be extended to use the conversion webhook by modifying `conversion`
section of the `spec`:
-->
### 配置 CustomResourceDefinition 以使用转换 Webhook

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

{{< tabs name="CustomResourceDefinition_versioning_example_2" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name 必须匹配后面 spec 中的字段，且使用格式 <plural>.<group>
  name: crontabs.example.com
spec:
  # 组名，用于 REST API: /apis/<group>/<version>
  group: example.com
  # 此 CustomResourceDefinition 所支持的版本列表
  versions:
  - name: v1beta1
    # 每个 version 可以通过 served 标志启用或禁止
    served: true
    # 有且只能有一个 version 必须被标记为存储版本
    storage: true
    # 当不存在顶级模式定义时，每个版本（version）可以定义其自身的模式
    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:
    # Webhook strategy 告诉 API 服务器调用外部 Webhook 来完成定制资源
    # 之间的转换
    strategy: Webhook
    # 当 strategy 为 "Webhook" 时，webhook 属性是必需的
    # 该属性配置将被 API 服务器调用的 Webhook 端点
    webhook:
      # conversionReviewVersions 标明 Webhook 所能理解或偏好使用的
      # ConversionReview 对象版本。
      # API 服务器所能理解的列表中的第一个版本会被发送到 Webhook
      # Webhook 必须按所接收到的版本响应一个 ConversionReview 对象
      conversionReviewVersions: ["v1","v1beta1"]
      clientConfig:
        service:
          namespace: default
          name: example-conversion-webhook-server
          path: /crdconvert
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式，用于 URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # 名称的单数形式，用于在命令行接口和显示时作为其别名
    singular: crontab
    # kind 通常是驼峰编码（CamelCased）的单数形式，用于资源清单中
    kind: CronTab
    # shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
    shortNames:
    - ct
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# 在 v1.16 中被弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name 必须匹配后面 spec 中的字段，且使用格式 <plural>.<group>
  name: crontabs.example.com
spec:
  # 组名，用于 REST API: /apis/<group>/<version>
  group: example.com
  # 裁剪掉下面的 OpenAPI 模式中未曾定义的对象字段
  preserveUnknownFields: false
  # 此 CustomResourceDefinition 所支持的版本列表
  versions:
  - name: v1beta1
    # 每个 version 可以通过 served 标志启用或禁止
    served: true
    # 有且只能有一个 version 必须被标记为存储版本
    storage: true
    # 当不存在顶级模式定义时，每个版本（version）可以定义其自身的模式
    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:
    # Webhook strategy 告诉 API 服务器调用外部 Webhook 来完成定制资源
    strategy: Webhook
    # 当 strategy 为 "Webhook" 时，webhookClientConfig 属性是必需的
    # 该属性配置将被 API 服务器调用的 Webhook 端点
    webhookClientConfig:
      service:
        namespace: default
        name: example-conversion-webhook-server
        path: /crdconvert
      caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
  # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式，用于 URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # 名称的单数形式，用于在命令行接口和显示时作为其别名
    singular: crontab
    # kind 通常是驼峰编码（CamelCased）的单数形式，用于资源清单中
    kind: CronTab
    # shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
    shortNames:
    - ct
```
{{% /tab %}}
{{< /tabs >}}

<!--
You can save the CustomResourceDefinition in a YAML file, then use
`kubectl apply` to apply it.
-->
你可以将 CustomResourceDefinition 保存在 YAML 文件中，然后使用
`kubectl apply` 来应用它。

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

<!--
Make sure the conversion service is up and running before applying new changes.
-->
在应用新更改之前，请确保转换服务器已启动并正在运行。

<!--
### Contacting the webhook

Once the API server has determined a request should be sent to a conversion webhook,
it needs to know how to contact the webhook. This is specified in the `webhookClientConfig`
stanza of the webhook configuration.

Conversion webhooks can either be called via a URL or a service reference,
and can optionally include a custom CA bundle to use to verify the TLS connection.
-->
### 调用 Webhook

API 服务器一旦确定请求应发送到转换 Webhook，它需要知道如何调用 Webhook。
这是在 `webhookClientConfig` 中指定的 Webhook 配置。

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

### URL

<!--
`url` gives the location of the webhook, in standard URL form
(`scheme://host:port/path`). 

The `host` should not refer to a service running in the cluster; use
a service reference by specifying the `service` field instead.
The host might be resolved via external DNS in some apiservers
(i.e., `kube-apiserver` cannot resolve in-cluster DNS as that would 
be a layering violation). `host` may also be an IP address.

Please note that using `localhost` or `127.0.0.1` as a `host` is
risky unless you take great care to run this webhook on all hosts
which run an apiserver which might need to make calls to this
webhook. Such installs are likely to be non-portable, i.e., not easy
to turn up in a new cluster.
-->
url 以标准 URL 形式给出 Webhook 的位置（`scheme://host:port/path`）。
`host` 不应引用集群中运行的服务，而应通过指定 `service` 字段来提供
服务引用。
在某些 API 服务器中，`host` 可以通过外部 DNS 进行解析（即
`kube-apiserver` 无法解析集群内 DNS，那样会违反分层规则）。
`host` 也可以是 IP 地址。

请注意，除非你非常小心地在所有运行着可能调用 Webhook 的 API 服务器的
主机上运行此 Webhook，否则将 `localhost` 或 `127.0.0.1` 用作 `host`
是风险很大的。这样的安装很可能是不可移植的，即很难在新集群中启用。

<!--
The scheme must be "https"; the URL must begin with "https://".

Attempting to use a user or basic auth e.g. "user:password@" is not allowed.
Fragments ("#...") and query parameters ("?...") are also not allowed.

Here is an example of a conversion webhook configured to call a URL
(and expects the TLS certificate to be verified using system trust roots, so does not specify a caBundle):
-->
HTTP 协议必须为 `https`；URL 必须以 `https://` 开头。

尝试使用用户或基本身份验证（例如，使用`user:password@`）是不允许的。
URL 片段（`#...`）和查询参数（`?...`）也是不允许的。

下面是为调用 URL 来执行转换 Webhook 的示例，其中期望使用系统信任根
来验证 TLS 证书，因此未指定 caBundle：

{{< tabs name="CustomResourceDefinition_versioning_example_3" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      clientConfig:
        url: "https://my-webhook.example.com:9443/my-webhook-path"
...
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# 在 v1.16 中已弃用以推荐使用 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"
...
```
{{% /tab %}}
{{< /tabs >}}

<!--
### Service Reference

The `service` stanza inside `webhookClientConfig` is a reference to the service for a conversion webhook.
If the webhook is running within the cluster, then you should use `service` instead of `url`.
The service namespace and name are required. The port is optional and defaults to 443.
The path is optional and defaults to "/".

Here is an example of a webhook that is configured to call a service on port "1234"
at the subpath "/my-path", and to verify the TLS connection against the ServerName
`my-service-name.my-service-namespace.svc` using a custom CA bundle.
-->
### 服务引用

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

下面配置中，服务配置为在端口 `1234`、子路径 `/my-path` 上被调用。
例子中针对 ServerName `my-service-name.my-service-namespace.svc`，
使用自定义 CA 包验证 TLS 连接。

{{< tabs name="CustomResourceDefinition_versioning_example_4" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
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"
...
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
#  v1.16 中被弃用以推荐使用 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"
...
```
{{% /tab %}}
{{< /tabs >}}

<!--
## Webhook request and response

### Request

Webhooks are sent a POST request, with `Content-Type: application/json`,
with a `ConversionReview` API object in the `apiextensions.k8s.io` API group
serialized to JSON as the body.

Webhooks can specify what versions of `ConversionReview` objects they accept
with the `conversionReviewVersions` field in their CustomResourceDefinition:
-->
## Webhook 请求和响应

### 请求

向 Webhooks 发起请求的动词是 POST，请求的 `Content-Type` 为 `application/json`。
请求的主题为 JSON 序列化形式的
apiextensions.k8s.io API 组的 ConversionReview API 对象。

Webhooks 可以在其 CustomResourceDefinition 中使用`conversionReviewVersions` 字段
设置它们接受的 `ConversionReview` 对象的版本：

{{< tabs name="conversionReviewVersions" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    webhook:
      conversionReviewVersions: ["v1", "v1beta1"]
      ...
```

<!--
`conversionReviewVersions` is a required field when creating 
`apiextensions.k8s.io/v1` custom resource definitions.
Webhooks are required to support at least one `ConversionReview`
version understood by the current and previous API server.
-->
创建 `apiextensions.k8s.io/v1` 版本的自定义资源定义时，
`conversionReviewVersions`是必填字段。
Webhooks 要求支持至少一个 `ConversionReview` 当前和以前的 API 服务器
可以理解的版本。

{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# v1.16 已弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
...
spec:
  ...
  conversion:
    strategy: Webhook
    conversionReviewVersions: ["v1", "v1beta1"]
    ...
```
<!--
If no `conversionReviewVersions` are specified, the default when creating 
`apiextensions.k8s.io/v1beta1` custom resource definitions is `v1beta1`.
-->
创建 apiextensions.k8s.io/v1beta1 定制资源定义时若未指定
`conversionReviewVersions`，则默认值为 v1beta1。

{{% /tab %}}
{{< /tabs >}}

<!--
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.
-->
API 服务器将 `conversionReviewVersions` 列表中他们所支持的第一个
`ConversionReview` 资源版本发送给 Webhook。
如果列表中的版本都不被 API 服务器支持，则无法创建自定义资源定义。
如果某 API 服务器遇到之前创建的转换 Webhook 配置，并且该配置不支持
API 服务器知道如何发送的任何 `ConversionReview` 版本，调用 Webhook
的尝试会失败。

<!--
This example shows the data contained in an `ConversionReview` object
for a request to convert `CronTab` objects to `example.com/v1`:
-->
下面的示例显示了包含在 `ConversionReview` 对象中的数据，
该请求意在将 `CronTab` 对象转换为 `example.com/v1`：

{{< tabs name="ConversionReview_request" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: ConversionReview
request:
  # 用来唯一标识此转换调用的随机 UID
  uid: 705ab4f5-6393-11e8-b7cc-42010a800002
  
  # 对象要转换到的目标 API 组和版本
  desiredAPIVersion: example.com/v1
  
  # 要转换的对象列表
  # 其中可能包含一个或多个对象，版本可能相同也可能不同
  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
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# v1.16 中已废弃以推荐使用 apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: ConversionReview
request:
  # 用来唯一标识此转换调用的随机 UID
  uid: 705ab4f5-6393-11e8-b7cc-42010a800002
  
  # 对象要转换到的目标 API 组和版本
  desiredAPIVersion: example.com/v1
  
  # 要转换的对象列表
  # 其中可能包含一个或多个对象，版本可能相同也可能不同
  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
```
{{% /tab %}}
{{< /tabs >}}

<!--
### Response

Webhooks respond with a 200 HTTP status code, `Content-Type: application/json`,
and a body containing a `ConversionReview` object (in the same version they were sent),
with the `response` stanza populated, serialized to JSON.

If conversion succeeds, a webhook should return a `response` stanza containing the following fields:
* `uid`, copied from the `request.uid` sent to the webhook
* `result`, set to `{"status":"Success"}`
* `convertedObjects`, containing all of the objects from `request.objects`, converted to `request.desiredVersion`

Example of a minimal successful response from a webhook:
-->
### 响应

Webhooks 响应包含 200 HTTP 状态代码、`Content-Type: application/json`，
在主体中包含 JSON 序列化形式的数据，在 `response` 节中给出
 ConversionReview 对象（与发送的版本相同）。

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

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

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

{{< tabs name="ConversionReview_response_success" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
apiVersion: apiextensions.k8s.io/v1
kind: ConversionReview
response:
  # 必须与 <request.uid> 匹配
  uid: "705ab4f5-6393-11e8-b7cc-42010a800002"
  result:
    status: Success
  # 这里的对象必须与 request.objects 中的对象顺序相同并且其 apiVersion
  # 被设置为 <request.desiredAPIVersion>。
  # kind、metadata.uid、metadata.name 和 metadata.namespace 等字段都不可
  # 被 Webhook 修改。
  # Webhook 可以更改 metadata.labels 和 metadata.annotations 字段值
  # Webhook 对 metadata 中其他字段的更改都会被忽略
  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"
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
# v1.16 中已弃用以推荐使用  apiextensions.k8s.io/v1
apiVersion: apiextensions.k8s.io/v1beta1
kind: ConversionReview
response:
  # 必须与 <request.uid> 匹配
  uid: "705ab4f5-6393-11e8-b7cc-42010a800002"
  result:
    status: Failed
  # 这里的对象必须与 request.objects 中的对象顺序相同并且其 apiVersion
  # 被设置为 <request.desiredAPIVersion>。
  # kind、metadata.uid、metadata.name 和 metadata.namespace 等字段都不可
  # 被 Webhook 修改。
  # Webhook 可以更改 metadata.labels 和 metadata.annotations 字段值
  # Webhook 对 metadata 中其他字段的更改都会被忽略
  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"
```
{{% /tab %}}
{{< /tabs >}}

<!--
If conversion fails, a webhook should return a `response` stanza containing the following fields:
* `uid`, copied from the `request.uid` sent to the webhook
* `result`, set to `{"status":"Failed"}`
-->
如果转换失败，则 Webhook 应该返回包含以下字段的 `response` 节：

*`uid`，从发送到 Webhook 的 `request.uid` 复制而来
*`result`，设置为 `{"status": "Failed"}`

{{< warning >}}
<!--
Failing conversion can disrupt read and write access to the custom resources,
including the ability to update or delete the resources. Conversion failures 
should be avoided whenever possible, and should not be used to enforce validation
 constraints (use validation schemas or webhook admission instead).
-->
转换失败会破坏对定制资源的读写访问，包括更新或删除资源的能力。
转换失败应尽可能避免，并且不可用于实施合法性检查约束
（应改用验证模式或 Webhook 准入插件）。
{{< /warning >}}

<!--
Example of a response from a webhook indicating a conversion request failed, with an optional message:
-->
来自 Webhook 的响应示例，指示转换请求失败，并带有可选消息：

{{< tabs name="ConversionReview_response_failure" >}}
{{% tab name="apiextensions.k8s.io/v1" %}}
```yaml
  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
```
{{% /tab %}}
{{% tab name="apiextensions.k8s.io/v1beta1" %}}
```yaml
  # v1.16 中弃用以推荐使用 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
```
{{% /tab %}}
{{< /tabs >}}

<!--
## Writing, reading, and updating versioned CustomResourceDefinition objects
-->
## 编写、读取和更新版本化的 CustomResourceDefinition 对象

<!--
When an object is written, it is persisted at the version designated as the
storage version at the time of the write. If the storage version changes,
existing objects are never converted automatically. However, newly-created
or updated objects are written at the new storage version. It is possible for an
object to have been written at a version that is no longer served.
-->
写入对象时，将使用写入时指定的存储版本来存储。如果存储版本发生变化，
现有对象永远不会被自动转换。然而，新创建或被更新的对象将以新的存储版本写入。
对象写入的版本不再被支持是有可能的。

<!--
When you read an object, you specify the version as part of the path. If you
specify a version that is different from the object's persisted version,
Kubernetes returns the object to you at the version you requested, but the
persisted object is neither changed on disk, nor converted in any way
(other than changing the `apiVersion` string) while serving the request.
You can request an object at any version that is currently served.
-->
当读取对象时，作为路径的一部分，你需要指定版本。
如果所指定的版本与对象的持久版本不同，Kubernetes 会按所请求的版本将对象返回，
但是在满足服务请求时，被持久化的对象既不会在磁盘上更改，也不会以任何方式进行
转换（除了 `apiVersion` 字符串被更改之外）。你可以以当前提供的任何版本
来请求对象。

<!--
If you update an existing object, it is rewritten at the version that is
currently the storage version. This is the only way that objects can change from
one version to another.
-->
如果你更新一个现有对象，它将以当前的存储版本被重写。
这是可以将对象从一个版本改到另一个版本的唯一办法。

<!--
To illustrate this, consider the following hypothetical series of events:
-->
为了说明这一点，请考虑以下假设的一系列事件：

<!--
1.  The storage version is `v1beta1`. You create an object. It is persisted in
    storage at version `v1beta1`
2.  You add version `v1` to your CustomResourceDefinition and designate it as
    the storage version.
3.  You read your object at version `v1beta1`, then you read the object again at
    version `v1`. Both returned objects are identical except for the apiVersion
    field.
4.  You create a new object. It is persisted in storage at version `v1`. You now
    have two objects, one of which is at `v1beta1`, and the other of which is at
    `v1`.
5.  You update the first object. It is now persisted at version `v1` since that
    is the current storage version.
-->
1.  存储版本是 `v1beta1`。你创建一个对象。该对象以版本 `v1beta1` 存储。
2.  你将为 CustomResourceDefinition 添加版本 `v1`，并将其指定为存储版本。
3.  你使用版本 `v1beta1` 来读取你的对象，然后你再次用版本 `v1` 读取对象。
    除了 apiVersion 字段之外，返回的两个对象是完全相同的。
4.  你创建一个新对象。对象以版本 `v1` 保存在存储中。
    你现在有两个对象，其中一个是 `v1beta1`，另一个是 `v1`。
5.  你更新第一个对象。该对象现在以版本 `v1` 保存，因为 `v1` 是当前的存储版本。

<!--
### Previous storage versions
-->
### 以前的存储版本

<!--
The API server records each version which has ever been marked as the storage
version in the status field `storedVersions`. Objects may have been persisted
at any version that has ever been designated as a storage version. No objects
can exist in storage at a version that has never been a storage version.
-->
API 服务器在状态字段 `storedVersions` 中记录曾被标记为存储版本的每个版本。
对象可能以任何曾被指定为存储版本的版本保存。
存储中不会出现从未成为存储版本的版本的对象。

<!--
## Upgrade existing objects to a new stored version
-->
## 将现有对象升级到新的存储版本     {#upgrade-existing-objects-to-a-new-stored-version} 

<!--
When deprecating versions and dropping support, devise a storage upgrade
procedure.
-->
弃用版本并删除其支持时，请设计存储升级过程。

<!--
*Option 1:* Use the Storage Version Migrator

1. Run the [storage Version migrator](https://github.com/kubernetes-sigs/kube-storage-version-migrator)
2. Remove the old version from the CustomResourceDefinition `status.storedVersions` field.
-->

*选项 1：* 使用存储版本迁移程序（Storage Version Migrator）

1. 运行[存储版本迁移程序](https://github.com/kubernetes-sigs/kube-storage-version-migrator)
2. 从 CustomResourceDefinition 的 `status.storedVersions` 字段中去掉
   老的版本。

<!--
*Option 2:* Manually upgrade the existing objects to a new stored version

The following is an example procedure to upgrade from `v1beta1` to `v1`.
-->
*选项 2：* 手动将现有对象升级到新的存储版本

以下是从 `v1beta1` 升级到 `v1` 的示例过程。

<!--
1.  Set `v1` as the storage in the CustomResourceDefinition file and apply it
    using kubectl. The `storedVersions` is now `v1beta1, v1`.
2.  Write an upgrade procedure to list all existing objects and write them with
    the same content. This forces the backend to write objects in the current
    storage version, which is `v1`.
3.  Update the CustomResourceDefinition `Status` by removing `v1beta1` from
    `storedVersions` field.
-->
1.  在 CustomResourceDefinition 文件中将 `v1` 设置为存储版本，并使用 kubectl 应用它。
    `storedVersions`现在是`v1beta1, v1`。
2.  编写升级过程以列出所有现有对象并使用相同内容将其写回存储。
    这会强制后端使用当前存储版本（即 `v1`）写入对象。
3.  通过从 `storedVersions` 字段中删除 `v1beta1` 来更新 CustomResourceDefinition
    的`Status`。 

