diff --git a/content/zh-cn/docs/reference/using-api/server-side-apply.md b/content/zh-cn/docs/reference/using-api/server-side-apply.md index f40fc802c9..8eedbe818b 100644 --- a/content/zh-cn/docs/reference/using-api/server-side-apply.md +++ b/content/zh-cn/docs/reference/using-api/server-side-apply.md @@ -2,7 +2,6 @@ title: 服务器端应用(Server-Side Apply) content_type: concept weight: 25 -min-kubernetes-server-version: 1.16 --- {{< feature-state for_k8s_version="v1.22" state="stable" >}} - -## 简介 {#introduction} +Kubernetes 支持多个应用程序协作管理一个[对象](/zh-cn/docs/concepts/overview/working-with-objects/)的字段。 +服务器端应用为集群的控制平面提供了一种可选机制,用于跟踪对对象字段的更改。 +在特定资源级别,服务器端应用记录并跟踪有关控制该对象字段的信息。 -服务器端应用协助用户、控制器通过声明式配置的方式管理他们的资源。 -客户端可以发送完整描述的目标(A fully specified intent), -声明式地创建和修改{{< glossary_tooltip text="对象" term_id="object" >}}。 + +服务器端应用协助用户和{{< glossary_tooltip text="控制器" term_id="controller" >}}通过声明式配置的方式管理他们的资源。 +客户提交他们**完整描述的意图**,声明式地创建和修改{{< glossary_tooltip text="对象" term_id="object" >}}。 -一个完整描述的目标并不是一个完整的对象,仅包括能体现用户意图的字段和值。 -该目标(intent)可以用来创建一个新对象, -也可以通过服务器来实现与现有对象的[合并](#merge-strategy)。 +一个完整描述的意图并不是一个完整的对象,仅包括能体现用户意图的字段和值。 +该意图可以用来创建一个新对象(未指定的字段使用默认值), +也可以通过 API 服务器来实现与现有对象的[合并](#merge-strategy)。 -系统支持多个应用者(appliers)在同一个对象上开展协作。 +[与客户端应用对比](#comparison-with-client-side-apply)小节解释了服务器端应用与最初的客户端 +`kubectl apply` 实现的区别。 - -“[字段管理(field management)](#field-management)”机制追踪对象字段的变化。 -当一个字段值改变时,其所有权从当前管理器(manager)转移到施加变更的管理器。 -当尝试将新配置应用到一个对象时,如果字段有不同的值,且由其他管理器管理, -将会引发[冲突](#conflicts)。 -冲突引发警告信号:此操作可能抹掉其他协作者的修改。 -冲突可以被刻意忽略,这种情况下,值将会被改写,所有权也会发生转移。 - - -当你从配置文件中删除一个字段,然后应用这个配置文件, -将触发服务器端应用检查此字段是否还被其他字段管理器拥有。 -如果没有,那就从活动对象中删除该字段;如果有,那就重置为默认值。 -该规则同样适用于 list 或 map 项目。 - - -服务器端应用既是原有 `kubectl apply` 的替代品, -也是控制器发布自身变化的一个简化机制。 - -如果你启用了服务器端应用,控制平面就会跟踪被所有新创建对象管理的字段。 + ## 字段管理 {#field-management} -相对于通过 `kubectl` 管理的注解 `last-applied`, -服务器端应用使用了一种更具声明式特点的方法: -它持续的跟踪用户的字段管理,而不仅仅是最后一次的执行状态。 -这就意味着,作为服务器端应用的一个副作用, -关于用哪一个字段管理器负责管理对象中的哪个字段的这类信息,都要对外界开放了。 +Kubernetes API 服务器跟踪所有新建对象的**受控字段(Managed Fields)**。 + +当尝试应用对象时,由另一个[管理器](#managers)拥有的字段且具有不同值,将导致[冲突](#conflicts)。 +这样做是为了表明操作可能会撤消另一个合作者的更改。 +可以强制写入具有托管字段的对象,在这种情况下,任何冲突字段的值都将被覆盖,并且所有权将被转移。 + + +每当字段的值确实发生变化时,所有权就会从其当前管理器转移到进行更改的管理器。 + +服务器端应用会检查是否存在其他字段管理器也拥有该字段。 +如果该字段不属于任何其他字段管理器,则该字段将被设置为其默认值(如果有),或者以其他方式从对象中删除。 +同样的规则也适用于作为列表(list)、关联列表或键值对(map)的字段。 用户管理字段这件事,在服务器端应用的场景中,意味着用户依赖并期望字段的值不要改变。 最后一次对字段值做出断言的用户将被记录到当前字段管理器。 -这可以通过发送 `POST`、 `PUT`、 -或非应用(non-apply)方式的 `PATCH` 等命令来修改字段值的方式实现, -或通过把字段放在配置文件中,然后发送到服务器端应用的服务端点的方式实现。 -当使用服务器端应用,尝试着去改变一个被其他人管理的字段, -会导致请求被拒绝(在没有设置强制执行时,参见[冲突](#conflicts))。 +这可以通过发送 `POST`(**create**)、`PUT`(**update**)、或非应用的 `PATCH`(**patch**) +显式更改字段管理器详细信息来实现。 +还可以通过在服务器端应用操作中包含字段的值来声明和记录字段管理器。 如果两个或以上的应用者均把同一个字段设置为相同值,他们将共享此字段的所有权。 后续任何改变共享字段值的尝试,不管由那个应用者发起,都会导致冲突。 -共享字段的所有者可以放弃字段的所有权,这只需从配置文件中删除该字段即可。 +共享字段的所有者可以放弃字段的所有权,这只需发出不包含该字段的服务器端应用 **patch** 请求即可。 字段管理的信息存储在 `managedFields` 字段中,该字段是对象的 -[`metadata`](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#objectmeta-v1-meta) +[`metadata`](/zh-cn/docs/reference/kubernetes-api/common-definitions/object-meta/)。 中的一部分。 + +如果从清单中删除某个字段并应用该清单,则服务器端应用会检查是否有其他字段管理器也拥有该字段。 +如果该字段不属于任何其他字段管理器,则服务器会将其从活动对象中删除,或者重置为其默认值(如果有)。 +同样的规则也适用于关联列表(list)或键值对(map)。 + + +与(旧版)由 `kubectl` 所管理的注解 +[`kubectl.kubernetes.io/last-applied configuration`](/zh-cn/docs/reference/labels-annotations-taints/#kubectl-kubernetes-io-last-applied-configuration) +相比,服务器端应用使用了一种更具声明式的方法, +它跟踪用户(或客户端)的字段管理,而不是用户上次应用的状态。 +作为服务器端应用的副作用,哪个字段管理器管理的对象的哪个字段的相关信息也会变得可用。 + + +### 示例 {#ssa-example-configmap} + 服务器端应用创建对象的简单示例如下: ```yaml +--- apiVersion: v1 kind: ConfigMap metadata: @@ -154,7 +172,7 @@ metadata: test-label: test managedFields: - manager: kubectl - operation: Apply + operation: Apply # 注意大写: “Apply” (或者 “Update”) apiVersion: v1 time: "2010-10-10T0:00:00Z" fieldsType: FieldsV1 @@ -168,51 +186,64 @@ data: key: some value ``` - -上述对象在 `metadata.managedFields` 中包含了唯一的管理器。 -管理器由管理实体自身的基本信息组成,比如操作类型、API 版本、以及它管理的字段。 +示例的 ConfigMap 对象在 `.metadata.managedFields` 中包含字段管理记录。 +字段管理记录包括关于管理实体本身的基本信息,以及关于被管理的字段和相关操作(`Apply` 或 `Update`)的详细信息。 +如果最后更改该字段的请求是服务器端应用的**patch**操作,则 `operation` 的值为 `Apply`;否则为 `Update`。 -{{< note >}} - -该字段由 API 服务器管理,用户不应该改动它。 -{{< /note >}} +还有另一种可能的结果。客户端会提交无效的请求体。 +如果完整描述的意图没有构造出有效的对象,则请求失败。 - -不过,执行 `Update` 操作修改 `metadata.managedFields` 也是可实现的。 +但是,可以通过 **update** 或不使用服务器端应用的 **patch** 操作去更新 `.metadata.managedFields`。 强烈不鼓励这么做,但当发生如下情况时, 比如 `managedFields` 进入不一致的状态(显然不应该发生这种情况), 这么做也是一个合理的尝试。 -`managedFields` 的格式在 -[API](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#fieldsv1-v1-meta) -文档中描述。 + +`managedFields` 的格式在 Kubernetes API 参考中[描述](/zh-cn/docs/reference/kubernetes-api/common-definitions/object-meta/#System)。 + +{{< caution >}} +`.metadata.managedFields` 字段由 API 服务器管理。 +你不应该手动更新它。 +{{< /caution >}} ## 冲突 {#conflicts} -冲突是一种特定的错误状态, +**冲突**是一种特定的错误状态, 发生在执行 `Apply` 改变一个字段,而恰巧该字段被其他用户声明过主权时。 这可以防止一个应用者不小心覆盖掉其他用户设置的值。 冲突发生时,应用者有三种办法来解决它: @@ -220,70 +251,78 @@ this occurs, the applier has 3 options to resolve the conflicts: * **覆盖前值,成为唯一的管理器:** 如果打算覆盖该值(或应用者是一个自动化部件,比如控制器), - 应用者应该设置查询参数 `force` 为 true(在 kubectl 中,可以通过在 - apply 命令中使用 `--force-conflicts` 标志来完成),然后再发送一次请求。 - 这将强制操作成功,改变字段的值,从所有其他管理器的 managedFields 条目中删除指定字段。 + 应用者应该设置查询参数 `force` 为 true(对 `kubectl apply` 来说,你可以使用命令行参数 + `--force-conflicts`),然后再发送一次请求。 + 这将强制操作成功,改变字段的值,从所有其他管理器的 `managedFields` 条目中删除指定字段。 * **不覆盖前值,放弃管理权:** 如果应用者不再关注该字段的值, - 可以从配置文件中删掉它,再重新发送请求。 - 这就保持了原值不变,并从 managedFields 的应用者条目中删除该字段。 + 应用者可以从资源的本地模型中删掉它,并在省略该字段的情况下发送请求。 + 这就保持了原值不变,并从 `managedFields` 的应用者条目中删除该字段。 * **不覆盖前值,成为共享的管理器:** 如果应用者仍然关注字段值,并不想覆盖它, - 他们可以在配置文件中把字段的值改为和服务器对象一样,再重新发送请求。 - 这样在不改变字段值的前提下, - 就实现了字段管理被应用者和所有声明了管理权的其他的字段管理器共享。 + 他们可以更改资源的本地模型中该字段的值,以便与服务器上对象的值相匹配, + 然后基于本地更新发出新请求。这样做会保持值不变, + 并使得该字段的管理由应用者与已经声称管理该字段的所有其他字段管理者共享。 -## 管理器 {#managers} +## 字段管理器 {#managers} 管理器识别出正在修改对象的工作流程(在冲突时尤其有用), -管理器可以通过修改请求的参数 `fieldManager` 指定。 -虽然 kubectl 默认发往 `kubectl` 服务端点,但它则请求到应用的服务端点(apply endpoint)。 -对于其他的更新,它默认的是从用户代理计算得来。 +并且可以作为修改请求的一部分,通过 +[`fieldManager`](/zh-cn/docs/reference/kubernetes-api/common-parameters/common-parameters/#fieldManager) +查询参数来指定。 +当你 Apply 某个资源时,需要指定 `fieldManager` 参数。 +对于其他更新,API 服务器使用 “User-Agent:” HTTP 头(如果存在)推断字段管理器标识。 - -## 应用和更新 {#apply-and-update} +## 序列化 {#serialization} -此特性涉及两类操作,分别是 `Apply` -(内容类型为 `application/apply-patch+yaml` 的 `PATCH` 请求) -和 `Update` (所有修改对象的其他操作)。 -这两类操作都会更新字段 `managedFields`,但行为表现有一点不同。 +在协议层面,Kubernetes 用 [YAML](https://yaml.org/) 来表示 Server-Side Apply 的消息体, +媒体类型为 `application/apply-patch+yaml`。 {{< note >}} - +序列化与 Kubernetes 对象相同,只是客户端不需要发送完整的对象。 + +以下是服务器端应用消息正文的示例(完整描述的意图): + +```yaml +{ + "apiVersion": "v1", + "kind": "ConfigMap" +} +``` + + +(这个请求将导致无更改的更新,前提是它作为 **patch** 请求的主体发送到有效的 `v1/configmaps` 资源, +并且请求中设置了合适的 `Content-Type`)。 + + +## 字段管理范围内的操作 {#apply-and-update} + +考虑字段管理的 Kubernetes API 操作包括: + +1. 服务器端应用(HTTP `PATCH`,内容类型为 `application/apply-patch+yaml`) +2. 替换现有对象(对 Kubernetes 而言是 **update**;HTTP 层面表现为 `PUT`) + + +这两种操作都会更新 `.metadata.managedFields`,但行为略有不同。 + +除非你指定要强制重写,否则应用操作在遇到字段级冲突时总是会失败; +相比之下,如果你使用 **update** 进行的更改会影响托管字段,那么冲突从来不会导致操作失败。 + + -例如,在冲突发生的时候,只有 `apply` 操作失败,而 `update` 则不会。 -此外,`apply` 操作必须通过提供一个 `fieldManager` 查询参数来标识自身, -而此查询参数对于 `update` 操作则是可选的。 -最后,当使用 `apply` 命令时,你不能在应用中的对象中持有 `managedFields`。 +所有服务器端应用的 **patch** 请求都必须提供 `fieldManager` 查询参数来标识自己, +而此参数对于 **update** 操作是可选的。 +最后,使用 `Apply` 操作时,不能在提交的请求主体中设置 `managedFields`。 一个包含多个管理器的对象,示例如下: ```yaml +--- apiVersion: v1 kind: ConfigMap metadata: @@ -339,19 +429,19 @@ data: ``` 在这个例子中, -第二个操作被管理器 `kube-controller-manager` 以 `Update` 的方式运行。 -此 `update` 更改 data 字段的值, -并使得字段管理器被改为 `kube-controller-manager`。 +第二个操作被管理器 `kube-controller-manager` 以 **update** 的方式运行。 +更新操作执行成功,并更改了 data 字段中的一个值, +并使得该字段的管理器被改为 `kube-controller-manager`。 -如果把 `update` 操作改为 `Apply`,那就会因为所有权冲突的原因,导致操作失败。 +如果尝试把更新操作改为服务器端应用,那么这一尝试会因为所有权冲突的原因,导致操作失败。 -当用户发送一个“完整描述的目标”对象到服务器端应用的服务端点, -服务器会将它和活动对象做一次合并,如果两者中有重复定义的值,那就以配置文件的为准。 -如果配置文件中的项目集合不是此用户上一次操作项目的超集, -所有缺少的、没有其他应用者管理的项目会被删除。 +当用户发送一个**完整描述的意图**对象到服务器端应用的服务端点时, +服务器会将它和当前对象做一次合并,如果两者中有重复定义的值,那就以请求体中的为准。 +如果请求体中条目的集合不是此用户上一次操作条目的超集, +所有缺失的、没有其他应用者管理的条目会被删除。 关于合并时用来做决策的对象规格的更多信息,参见 [sigs.k8s.io/structured-merge-diff](https://sigs.k8s.io/structured-merge-diff). +Kubernetes API(以及为 Kubernetes 实现该 API 的 Go 代码)都允许定义**合并策略标记(Merge Strategy Markers)**。 +这些标记描述 Kubernetes 对象中各字段所支持的合并策略。 Kubernetes 1.16 和 1.17 中添加了一些标记, -允许 API 开发人员描述由 list、map、和 structs 支持的合并策略。 -这些标记可应用到相应类型的对象,在 Go 文件或在 -[CRD](/docs/reference/generated/kubernetes-api/{{< param "version" >}}#jsonschemaprops-v1-apiextensions-k8s-io) -的 OpenAPI 的模式中定义: +对一个 {{< glossary_tooltip term_id="CustomResourceDefinition" text="CustomResourceDefinition" >}} +来说,你可以在定义自定义资源时设置这些标记。 -| Golang 标记 | OpenAPI extension | 可接受的值 | 描述 | 引入版本 | -|---|---|---|---|---| -| `//+listType` | `x-kubernetes-list-type` | `atomic`/`set`/`map` | 适用于 list。`set` 适用于仅包含标量元素的列表。这些元素必须是不重复的。`map` 仅适用于包含嵌套类型的列表。列表中的键(参见 `listMapKey`)不可以重复。`atomic` 适用于任何类型的列表。如果配置为 `atomic`,则合并时整个列表会被替换掉。任何时候,只有一个管理器负责管理指定列表。如果配置为 `set` 或 `map`,不同的管理器也可以分开管理条目。 | 1.16 | -| `//+listMapKey` | `x-kubernetes-list-map-keys` | 字段名称的列表,例如,`["port", "protocol"]` | 仅当 `+listType=map` 时适用。取值为字段名称的列表,这些字段值的组合能够唯一标识列表中的条目。尽管可以存在多个键,`listMapKey` 是单数的,这是因为键名需要在 Go 类型中各自独立指定。键字段必须是标量。 | 1.16 | -| `//+mapType` | `x-kubernetes-map-type` | `atomic`/`granular` | 适用于 map。 `atomic` 指 map 只能被单个的管理器整个的替换。 `granular` 指 map 支持多个管理器各自更新自己的字段。 | 1.17 | -| `//+structType` | `x-kubernetes-map-type` | `atomic`/`granular` | 适用于 structs;否则就像 `//+mapType` 有相同的用法和 openapi 注释.| 1.17 | +| Golang 标记 | OpenAPI 扩展 | 可接受的值 | 描述 | +|---|---|---|---| +| `//+listType` | `x-kubernetes-list-type` | `atomic`/`set`/`map` | 适用于 list。`set` 适用于仅包含标量元素的列表。其中的元素不可重复。`map` 仅适用于嵌套了其他类型的列表。列表中的键(参见 `listMapKey`)不可以重复。`atomic` 适用于所有类型的列表。如果配置为 `atomic`,则合并时整个列表会被替换掉。任何时候,只有一个管理器负责管理指定列表。如果配置为 `set` 或 `map`,不同的管理器也可以分开管理不同条目。 | +| `//+listMapKey` | `x-kubernetes-list-map-keys` | 字段名称的列表,例如,`["port", "protocol"]` | 仅当 `+listType=map` 时适用。取值为字段名称的列表,这些字段值的组合能够唯一标识列表中的条目。尽管可以存在多个键,`listMapKey` 是单数的,这是因为键名需要在 Go 类型中各自独立指定。键字段必须是标量。 | +| `//+mapType` | `x-kubernetes-map-type` | `atomic`/`granular` | 适用于 map。 `atomic` 表示 map 只能被某个管理器整体替换。 `granular` 表示 map 支持多个管理器各自更新自己的字段。 | +| `//+structType` | `x-kubernetes-map-type` | `atomic`/`granular` | 适用于 structs;此外,起用法和 OpenAPI 注释与 `//+mapType` 相同。| -若未指定 `listType`,API 服务器将 `patchMergeStrategy=merge` 标记解释为 +若未指定 `listType`,API 服务器将 `patchStrategy=merge` 标记解释为 `listType=map` 并且视对应的 `patchMergeKey` 标记为 `listMapKey` 取值。 `atomic` 列表类型是递归的。 -这些标记都是用源代码注释的方式给出的,不必作为字段标签(tag)再重复。 +(在 Kubernetes 的 [Go](https://go.dev/) 代码中, +这些标记以注释的形式给出,代码作者不需要用字段标记的形式重复指定它们)。 + + +### 自定义资源和服务器端应用 {#custom-resources-and-server-side-apply} + +默认情况下,服务器端应用将自定义资源视为无结构的数据。 +所有键被视为 struct 数据类型的字段,所有列表都被视为 atomic 形式。 + + +如果 CustomResourceDefinition 定义了的 +[schema](/zh-cn/docs/reference/generated/kubernetes-api/{{< param "version" >}}#jsonschemaprops-v1-apiextensions-k8s-io) +包含在上一小节[合并策略](#merge-strategy)中定义的注解, +那么在合并此类型的对象时,就会使用这些注解。 ### 拓扑变化时的兼容性 {#compatibility-across-toplogy-changes} - -在极少的情况下,CRD 或者内置类型的作者可能希望更改其资源中的某个字段的 +在极少的情况下,CustomResourceDefinition(CRD)的作者或者内置类型可能希望更改其资源中的某个字段的 拓扑配置,同时又不提升版本号。 -通过升级集群或者更新 CRD 来更改类型的拓扑信息与更新现有对象的结果不同。 +通过升级集群或者更新 CRD 来更改类型的拓扑信息,与更新现有对象的结果不同。 变更的类型有两种:一种是将字段从 `map`/`set`/`granular` 更改为 `atomic`, 另一种是做逆向改变。 @@ -460,22 +573,23 @@ would cause a conflict. 元素之一的属主。这意味着,对这些对象的进一步变更会引发冲突。 -当一个列表、映射或结构从 `atomic` 改为 `map`/`set`/`granular` 之一 -时,API 服务器无法推导这些字段的新的属主。因此,当对象的这些字段 +当某 `listType`、`mapType` 或 `structType` 从 `atomic` 改为 `map`/`set`/`granular` 之一时, +API 服务器无法推导这些字段的新的属主。因此,当对象的这些字段 再次被更新时不会引发冲突。出于这一原因,不建议将某类型从 `atomic` 改为 `map`/`set`/`granular`。 以下面的自定义资源为例: ```yaml +--- apiVersion: example.com/v1 kind: Foo metadata: @@ -502,52 +616,28 @@ Before `spec.data` gets changed from `atomic` to `granular`, without a conflict), but it no longer owns `key1` and `key2`, so another manager can then modify or delete those fields without conflict. --> -在 `spec.data` 从 `atomic` 改为 `granular` 之前,`manager-one` 是 -`spec.data` 字段及其所包含字段(`key1` 和 `key2`)的属主。 +在 `spec.data` 从 `atomic` 改为 `granular` 之前, +`manager-one` 是 `spec.data` 字段及其所包含字段(`key1` 和 `key2`)的属主。 当对应的 CRD 被更改,使得 `spec.data` 变为 `granular` 拓扑时, -`manager-one` 继续拥有顶层字段 `spec.data`(这意味着其他管理者想删除名为 -`data` 的映射而不引起冲突是不可能的),但不再拥有 -`key1` 和 `key2`。因此,其他管理者可以在不引起冲突的情况下更改 -或删除这些字段。 - - -## 自定义资源 {#custom-resources} - -默认情况下,服务器端应用把自定义资源看做非结构化数据。 -所有的键值(keys)就像 struct 的字段一样被处理, -所有的 list 被认为是原子性的。 - -如果自定义资源定义(Custom Resource Definition,CRD)定义了一个 -[模式](/docs/reference/generated/kubernetes-api/{{< param "version" >}}#jsonschemaprops-v1-apiextensions-k8s-io), -它包含类似以前“合并策略”章节中定义过的注解, -这些注解将在合并此类型的对象时使用。 +`manager-one` 继续拥有顶层字段 `spec.data`(这意味着其他管理器想删除名为 +`data` 的映射而不引起冲突是不可能的),但不再拥有 `key1` 和 `key2`。 +因此,其他管理器可以在不引起冲突的情况下更改或删除这些字段。 ## 在控制器中使用服务器端应用 {#using-server-side-apply-in-controller} @@ -556,10 +646,10 @@ might not be able to resolve or act on these conflicts. * 应用的对象必须包含控制器关注的所有字段。 * 对于在控制器没有执行过应用操作之前就已经存在的字段,不能删除。 - (控制器在这种用例环境下,依然可以发送一个 PATCH/UPDATE) + (控制器在这种用例环境下,依然可以发送一个 **patch** 或 **update**) * 对象不必事先读取,`resourceVersion` 不必指定。 -强烈推荐:设置控制器在冲突时强制执行,这是因为冲突发生时,它们没有其他解决方案或措施。 +强烈推荐:设置控制器始终在其拥有和管理的对象上强制冲突,这是因为冲突发生时,它们没有其他解决方案或措施。 ## 转移所有权 {#transferring-ownership} @@ -587,10 +677,10 @@ Say a user has defined deployment with `replicas` set to the desired value: 假设用户定义了 Deployment,且 `replicas` 字段已经设置为期望的值: -{{< codenew file="application/ssa/nginx-deployment.yaml" >}} +{{% code_sample file="application/ssa/nginx-deployment.yaml" %}} 并且,用户使用服务器端应用,像这样创建 Deployment: @@ -599,9 +689,9 @@ kubectl apply -f https://k8s.io/examples/application/ssa/nginx-deployment.yaml - ``` -然后,为 Deployment 启用 HPA,例如: +然后,为 Deployment 启用自动扩缩,例如: ```shell kubectl autoscale deployment nginx-deployment --cpu-percent=50 --min=1 --max=10 @@ -609,32 +699,34 @@ kubectl autoscale deployment nginx-deployment --cpu-percent=50 --min=1 --max=10 -现在,用户希望从他们的配置中删除 `replicas`,所以他们总是和 HPA 控制器冲突。 -然而,这里存在一个竟态: -在 HPA 需要调整 `replicas` 之前会有一个时间窗口, -如果在 HPA 写入字段成为所有者之前,用户删除了`replicas`, -那 API 服务器就会把 `replicas` 的值设为 1, 也就是默认值。 -这不是用户希望发生的事情,即使是暂时的。 +现在,用户希望从他们的配置中删除 `replicas`,从而避免与 HorizontalPodAutoscaler(HPA)及其控制器发生冲突。 +然而,这里存在一个竞态: +在 HPA 需要调整 `.spec.replicas` 之前会有一个时间窗口, +如果在 HPA 写入字段并成为新的属主之前,用户删除了 `.spec.replicas`, +那 API 服务器就会把 `.spec.replicas` 的值设为 1(Deployment 的默认副本数)。 +这不是用户希望发生的事情,即使是暂时的——它很可能会导致正在运行的工作负载降级。 这里有两个解决方案: @@ -643,7 +735,7 @@ First, the user defines a new configuration containing only the `replicas` field - (高级操作)然而,如果用户不想等待,比如他们想为合作伙伴保持集群清晰, 那他们就可以执行以下步骤,安全的从配置文件中删除 `replicas`。 -首先,用户新定义一个只包含 `replicas` 字段的配置文件: +首先,用户新定义一个只包含 `replicas` 字段的新清单: ```yaml # 将此文件另存为 'nginx-deployment-replicas-only.yaml' @@ -666,9 +758,10 @@ want to modify the `spec.replicas` field using SSA. {{< /note >}} -用户使用名为 `handover-to-hpa` 的字段管理器,应用此配置文件。 +用户使用私有字段管理器名称应用该清单。在本例中,用户选择了 `handover-to-hpa`: ```shell kubectl apply -f nginx-deployment-replicas-only.yaml \ @@ -681,67 +774,86 @@ If the apply results in a conflict with the HPA controller, then do nothing. The conflict indicates the controller has claimed the field earlier in the process than it sometimes does. -At this point the user may remove the `replicas` field from their configuration. +At this point the user may remove the `replicas` field from their manifest: --> 如果应用操作和 HPA 控制器产生冲突,那什么都不做。 冲突表明控制器在更早的流程中已经对字段声明过所有权。 -在此时间点,用户可以从配置文件中删除 `replicas` 。 +在此时间点,用户可以从清单中删除 `replicas` 。 -{{< codenew file="application/ssa/nginx-deployment-no-replicas.yaml" >}} +{{% code_sample file="application/ssa/nginx-deployment-no-replicas.yaml" %}} 注意,只要 HPA 控制器为 `replicas` 设置了一个新值, 该临时字段管理器将不再拥有任何字段,会被自动删除。 -这里不需要执行清理工作。 +这里无需进一步清理。 -### 在用户之间转移所有权 {#transferring-ownership-between-users} +### 在管理器之间转移所有权 {#transferring-ownership-between-managers} -通过在配置文件中把一个字段设置为相同的值,用户可以在他们之间转移字段的所有权, -从而共享了字段的所有权。 -当用户共享了字段的所有权,任何一个用户可以从他的配置文件中删除该字段, -并应用该变更,从而放弃所有权,并实现了所有权向其他用户的转移。 +通过在配置文件中把一个字段设置为相同的值,多个字段管理器可以在彼此之间转移字段的所有权, +从而实现字段所有权的共享。 +当某管理器共享了字段的所有权,管理器中任何一个成员都可以从其应用的配置中删除该字段, +从而放弃所有权,并完成了所有权向其他字段管理器的转移。 +## 与客户端应用的对比 {#comparison-with-client-side-apply} + +服务器端应用意味着既可以替代原来 `kubectl apply` 子命令的客户端实现, +也可供{{< glossary_tooltip term_id="controller" text="控制器" >}}作为实施变更的简单有效机制。 + +与 `kubectl` 管理的 `last-applied` 注解相比, +服务器端应用使用一种更具声明性的方法来跟踪对象的字段管理,而不是记录用户最后一次应用的状态。 +这意味着,使用服务器端应用的副作用,就是字段管理器所管理的对象的每个字段的相关信息也会变得可用。 + + -## 与客户端应用的对比 {#comparison-with-client-side-apply} - 由服务器端应用实现的冲突检测和解决方案的一个结果就是, 应用者总是可以在本地状态中得到最新的字段值。 如果得不到最新值,下次执行应用操作时就会发生冲突。 解决冲突三个选项的任意一个都会保证:此应用过的配置文件是服务器上对象字段的最新子集。 -这和客户端应用(Client Side Apply) 不同,如果有其他用户覆盖了此值, +这和客户端应用(Client-Side Apply)不同,如果有其他用户覆盖了此值, 过期的值被留在了应用者本地的配置文件中。 除非用户更新了特定字段,此字段才会准确, 应用者没有途径去了解下一次应用操作是否会覆盖其他用户的修改。 @@ -749,12 +861,16 @@ case. 另一个区别是使用客户端应用的应用者不能改变他们正在使用的 API 版本,但服务器端应用支持这个场景。 -## 从客户端应用升级到服务器端应用 {#upgrading-from-client-side-apply-to-server-side-apply} +## 客户端应用和服务器端应用的迁移 {#migration-between-client-side-and-server-side-apply} + +### 从客户端应用升级到服务器端应用 {#upgrading-from-client-side-apply-to-server-side-apply} 客户端应用方式时,用户使用 `kubectl apply` 管理资源, 可以通过使用下面标记切换为使用服务器端应用。 @@ -764,14 +880,14 @@ kubectl apply --server-side [--dry-run=server] ``` 默认情况下,对象的字段管理从客户端应用方式迁移到 kubectl 触发的服务器端应用时,不会发生冲突。 {{< caution >}} -## 从服务器端应用降级到客户端应用 {#downgrading-from-server-side-apply-to-client-side-apply} +### 从服务器端应用降级到客户端应用 {#downgrading-from-server-side-apply-to-client-side-apply} 如果你用 `kubectl apply --server-side` 管理一个资源, 可以直接用 `kubectl apply` 命令将其降级为客户端应用。 @@ -834,81 +950,118 @@ kubectl apply --server-side --field-manager=my-manager [--dry-run=server] ``` -## API 端点 {#api-endpoint} +## API 实现 {#api-implementation} -启用了服务器端应用特性之后, -`PATCH` 服务端点接受额外的内容类型 `application/apply-patch+yaml`。 -服务器端应用的用户就可以把 YAMl -格式的部分定义对象(partially specified objects)发送到此端点。 -当一个配置文件被应用时,它应该包含所有体现你意图的字段。 +支持服务器端应用的资源的 `PATCH` 动词可以接受非官方的 `application/apply-patch+yaml` 内容类型。 +服务器端应用的用户可以将部分指定的对象以 YAML 格式作为 `PATCH` 请求的主体发送到资源的 URI。 +应用配置时,你应该始终包含对要定义的结果(如所需状态)重要的所有字段。 + +所有 JSON 消息都是有效的 YAML。一些客户端使用 YAML 请求体指定服务器端应用请求, +而这些 YAML 同样是合法的 JSON。 + + +### 访问控制和权限 {#rbac-and-permissions} + +由于服务端应用是一种 `PATCH` 类型的操作, +所以一个主体(例如 Kubernetes {{< glossary_tooltip text="RBAC" term_id="rbac" >}} 的 Role)需要 +**patch** 权限才能编辑存量资源,还需要 **create** 权限才能使用服务器端应用创建新资源。 -### RBAC 和权限 {#rbac-and-permissions} - -因为服务器端应用是一种 `PATCH` 操作,所以一个角色编辑资源需要 `PATCH` 权限, -但要用服务器端应用创建资源还需要 `CREATE` 动作权限。 - - -## 清除 ManagedFields {#clearing-managedfields} +## 清除 `managedFields` {#clearing-managedfields} -可以从对象中剥离所有 managedField, -实现方法是通过使用 `MergePatch`、 `StrategicMergePatch`、 -`JSONPatch`、 `Update`、以及所有的非应用方式的操作来覆盖它。 -这可以通过用空条目覆盖 managedFields 字段的方式实现。以下是两个示例: +通过使用 **patch**(JSON Merge Patch, Strategic Merge Patch, JSON Patch)覆盖对象, +或者通过 **update**(HTTP `PUT`),可以从对象中剥离所有 `managedFields`; +换句话说,通过除了 **apply** 之外的所有写操作均可实现这点。 +清除 `managedFields` 字段的操作可以通过用空条目覆盖 `managedFields` 字段的方式实现。以下是两个示例: ```console PATCH /api/v1/namespaces/default/configmaps/example-cm -Content-Type: application/merge-patch+json Accept: application/json -Data: {"metadata":{"managedFields": [{}]}} +Content-Type: application/merge-patch+json + +{ + "metadata": { + "managedFields": [ + {} + ] + } +} ``` ```console PATCH /api/v1/namespaces/default/configmaps/example-cm -Content-Type: application/json-patch+json Accept: application/json -Data: [{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}] +Content-Type: application/json-patch+json +If-Match: 1234567890123456789 + +[{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}] ``` -这一操作将用只包含一个空条目的列表覆写 managedFields, -来实现从对象中整个的去除 managedFields。 -注意,只把 managedFields 设置为空列表并不会重置字段。 -这么做是有目的的,所以 managedFields 将永远不会被与该字段无关的客户删除。 +这一操作将用只包含一个空条目的列表来覆盖 `managedFields`, +从而实现从对象中整体去除 `managedFields`。 +注意,只把 `managedFields` 设置为空列表并不会重置该字段。 +这一设计是有意为之的,目的是避免 `managedFields` 被与该字段无关的客户删除。 -在重置操作结合 managedFields 以外其他字段更改的场景中, -将导致 managedFields 首先被重置,其他改变被押后处理。 +在某些场景中,执行重置操作的同时还会给出对 `managedFields` 之外的别的字段的变更, +对于这类操作,`managedFields` 首先会被重置,其他变更被压后处理。 其结果是,应用者取得了同一个请求中所有字段的所有权。 +{{< note >}} + +对于无法接收资源对象类型的子资源,服务器端应用无法正确跟踪其所有权。 +如果你将针对此类子资源使用服务器端应用,则可能无法跟踪被变更的字段。 +{{< /note >}} + +## {{% heading "whatsnext" %}} + + +你可以阅读 Kubernetes API 参考中的 +[`metadata`](/zh-cn/docs/reference/kubernetes-api/common-definitions/object-meta/) +顶级字段的 `managedFields`。