服务器端应用
Kubernetes v1.22 [稳定]
(默认启用:true)Kubernetes 支持多个应用者协同管理单个对象的字段。
服务端应用提供了一种可选机制,用于集群的控制平面跟踪对对象字段的更改。在特定资源的级别,服务端应用记录和跟踪有关该对象字段控制的信息。
服务端应用帮助用户和控制器通过声明式配置管理他们的资源。客户端可以通过提交其完全指定的意图来声明式地创建和修改对象。
完全指定的意图是一个部分对象,它仅包含用户对其有意见的字段和值。该意图要么创建新对象(使用未指定字段的默认值),要么由 API 服务器与现有对象合并。
与客户端应用的比较解释了服务端应用与原始客户端 kubectl apply
实现的不同之处。
字段管理
Kubernetes API 服务器跟踪所有新创建对象的托管字段。
当尝试应用对象时,如果某个字段具有不同的值并且由另一个管理器所有,将导致冲突。这样做是为了表示该操作可能会撤消另一个协作者的更改。可以强制写入具有托管字段的对象,在这种情况下,将覆盖任何冲突字段的值,并且将转移所有权。
每当字段的值发生更改时,所有权都会从当前管理器转移到进行更改的管理器。
Apply 检查是否还有其他字段管理器也拥有该字段。如果该字段不归任何其他字段管理器所有,则该字段将设置为其默认值(如果有),否则将从对象中删除。相同的规则适用于列表、关联列表或映射的字段。
对于用户来说,在服务端应用的意义上管理字段,意味着用户依赖并期望字段的值不会改变。最后对字段值进行断言的用户将被记录为当前字段管理器。可以通过使用 HTTP POST
(创建)、PUT
(更新)或非应用 PATCH
(补丁)显式更改字段管理器详细信息来完成此操作。你还可以通过在服务端应用操作中包含该字段的值来声明和记录字段管理器。
服务端应用 patch 请求要求客户端将其身份作为字段管理器提供。当使用服务端应用时,尝试更改由不同管理器控制的字段会导致请求被拒绝,除非客户端强制覆盖。有关覆盖的详细信息,请参阅冲突。
当两个或多个应用者将字段设置为相同的值时,他们将共享该字段的所有权。任何后续尝试更改任何应用者共享字段的值都将导致冲突。共享字段所有者可以通过发出不包含该字段的服务端应用 patch 请求来放弃字段的所有权。
字段管理详细信息存储在 managedFields
字段中,该字段是对象metadata
的一部分。
如果你从清单中删除一个字段并应用该清单,服务端应用会检查是否还有其他字段管理器也拥有该字段。如果该字段不归任何其他字段管理器所有,则该字段要么从实时对象中删除,要么重置为其默认值(如果有)。相同的规则适用于关联列表或映射项。
与 kubectl
管理的(旧版)kubectl.kubernetes.io/last-applied-configuration
注解相比,服务端应用使用更具声明性的方法,它跟踪用户(或客户端)的字段管理,而不是用户上次应用的状态。作为使用服务端应用的一个副作用,有关哪个字段管理器管理对象中每个字段的信息也变得可用。
示例
使用服务端应用创建的对象的简单示例可能如下所示
注意
默认情况下,kubectl get
会忽略托管字段。当输出格式为 json
或 yaml
时,添加 --show-managed-fields
以显示 managedFields
。---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
namespace: default
labels:
test-label: test
managedFields:
- manager: kubectl
operation: Apply # note capitalization: "Apply" (or "Update")
apiVersion: v1
time: "2010-10-10T0:00:00Z"
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
f:test-label: {}
f:data:
f:key: {}
data:
key: some value
该示例 ConfigMap 对象在 .metadata.managedFields
中包含单个字段管理记录。字段管理记录由有关管理实体本身的基本信息,以及有关正在管理的字段和相关操作(Apply
或 Update
)的详细信息组成。如果上次更改该字段的请求是服务端应用 patch,则 operation
的值为 Apply
;否则,为 Update
。
还有另一种可能的结果。客户端可能会提交无效的请求主体。如果完全指定的意图没有生成有效的对象,则请求失败。
但是可以通过 update 或通过不使用服务端应用的 patch 操作来更改 .metadata.managedFields
。强烈建议不要这样做,但如果例如 .metadata.managedFields
进入不一致状态(在正常操作中不应发生),这可能是一个合理的尝试选项。
managedFields
的格式在 Kubernetes API 参考中的描述。
注意
.metadata.managedFields
字段由 API 服务器管理。应避免手动更新它。冲突
冲突是一种特殊的状态错误,当 Apply
操作尝试更改另一个管理器也声明要管理的字段时会发生。这可以防止应用者无意中覆盖另一个用户设置的值。发生这种情况时,应用者有 3 个选项来解决冲突
覆盖值,成为唯一管理器: 如果覆盖值是故意的(或者如果应用者是像控制器这样的自动化过程),则应用者应将
force
查询参数设置为 true(对于kubectl apply
,可以使用--force-conflicts
命令行参数),然后再次发出请求。这将强制操作成功,更改字段的值,并从managedFields
中所有其他管理器的条目中删除该字段。不要覆盖值,放弃管理声明: 如果应用者不再关心字段的值,则可以将该字段从其资源的本地模型中移除,并发出省略该特定字段的新请求。 这不会更改值,并且会导致该字段从应用者在
managedFields
中的条目中删除。不要覆盖值,成为共享管理者: 如果应用者仍然关心字段的值,但不希望覆盖它,他们可以更改其资源本地模型中该字段的值,使其与服务器上对象的值匹配,然后发出考虑该本地更新的新请求。这样做不会更改值,并导致该字段的管理权由应用者与所有其他已声明管理它的字段管理器共享。
字段管理器
管理器标识正在修改对象的不同工作流(在发生冲突时特别有用!),可以通过作为修改请求一部分的 fieldManager
查询参数来指定。当你对资源执行 Apply 操作时,必须提供 fieldManager
参数。对于其他更新,API 服务器会从 “User-Agent:” HTTP 标头(如果存在)推断字段管理器的身份。
当你使用 kubectl
工具执行服务器端 Apply 操作时,kubectl
默认将管理器身份设置为 "kubectl"
。
序列化
在协议级别,Kubernetes 将服务器端 Apply 消息体表示为 YAML,媒体类型为 application/apply-patch+yaml
。
注意
无论你提交的是 JSON 数据还是 YAML 数据,都请使用 application/apply-patch+yaml
作为 Content-Type
标头的值。
所有 JSON 文档都是有效的 YAML。但是,Kubernetes 有一个错误,它使用的 YAML 解析器没有完全实现 YAML 规范。一些 JSON 转义可能无法识别。
序列化与 Kubernetes 对象相同,但客户端不需要发送完整的对象。
这是一个服务器端 Apply 消息体(完全指定的意图)的示例
{
"apiVersion": "v1",
"kind": "ConfigMap"
}
(如果将其作为对有效 v1/configmaps
资源的 patch 请求的主体发送,并带有适当的请求 Content-Type
,这将进行无更改更新)。
字段管理范围内的操作
考虑字段管理的 Kubernetes API 操作包括
- 服务器端 Apply(HTTP
PATCH
,内容类型为application/apply-patch+yaml
) - 替换现有对象(Kubernetes 的 update;HTTP 级别上的
PUT
)
这两个操作都会更新 .metadata.managedFields
,但行为略有不同。
除非你指定强制覆盖,否则遇到字段级冲突的 apply 操作始终会失败;相比之下,如果你使用 update 进行更改,该更改会影响受管理的字段,则冲突永远不会导致操作失败。
所有服务器端 Apply patch 请求都必须通过提供 fieldManager
查询参数来标识自己,而 update 操作的查询参数是可选的。最后,当使用 Apply
操作时,你不能在提交的请求主体中定义 managedFields
。
具有多个管理器的示例对象可能如下所示
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
namespace: default
labels:
test-label: test
managedFields:
- manager: kubectl
operation: Apply
time: '2019-03-30T15:00:00.000Z'
apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
f:test-label: {}
- manager: kube-controller-manager
operation: Update
apiVersion: v1
time: '2019-03-30T16:00:00.000Z'
fieldsType: FieldsV1
fieldsV1:
f:data:
f:key: {}
data:
key: new value
在此示例中,名为 kube-controller-manager
的管理器作为 update 运行了第二个操作。更新请求成功并更改了数据字段中的一个值,这导致该字段的管理权更改为 kube-controller-manager
。
如果此更新改为使用服务器端 Apply 尝试,则会因所有权冲突而导致请求失败。
合并策略
使用服务器端 Apply 实现的合并策略,提供了一个通常更稳定的对象生命周期。服务器端 Apply 尝试基于管理字段的操作者合并字段,而不是根据值进行覆盖。这样,多个操作者可以更新同一个对象,而不会引起意外干扰。
当用户向服务器端 Apply 端点发送一个完全指定的意图对象时,服务器会将其与实时对象合并,如果请求主体中指定了该值,则优先使用该值。如果应用配置中存在的项集不是上次同一用户应用的项集的超集,则会删除任何其他应用者未管理的每个缺失项。有关如何使用对象的 schema 在合并时做出决策的更多信息,请参阅 sigs.k8s.io/structured-merge-diff。
Kubernetes API(以及为 Kubernetes 实现该 API 的 Go 代码)允许定义合并策略标记。这些标记描述了 Kubernetes 对象中字段支持的合并策略。对于CustomResourceDefinition,你可以在定义自定义资源时设置这些标记。
Golang 标记 | OpenAPI 扩展 | 可能的值 | 描述 |
---|---|---|---|
//+listType | x-kubernetes-list-type | atomic /set /map | 适用于列表。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 | 适用于结构体;否则与 //+mapType 的用法和 OpenAPI 注释相同。 |
如果缺少 listType
,则 API 服务器将 patchStrategy=merge
标记解释为 listType=map
,并将相应的 patchMergeKey
标记解释为 listMapKey
。
atomic
列表类型是递归的。
(在 Kubernetes 的 Go 代码中,这些标记被指定为注释,代码作者无需将它们重复为字段标记)。
自定义资源和服务器端 Apply
默认情况下,服务器端 Apply 将自定义资源视为非结构化数据。所有键都与结构体字段相同,并且所有列表都被视为原子列表。
如果 CustomResourceDefinition 定义了包含先前合并策略部分中定义的注释的 schema,则在合并此类型的对象时将使用这些注释。
跨拓扑变更的兼容性
在极少数情况下,CustomResourceDefinition (CRD) 或内置资源的作者可能希望更改其资源中字段的特定拓扑,而无需增加其 API 版本。通过升级集群或更新 CRD 来更改类型的拓扑,在更新现有对象时会产生不同的后果。有两种类型的更改:当字段从 map
/set
/granular
变为 atomic
时,以及反之亦然。
当 listType
、mapType
或 structType
从 map
/set
/granular
更改为 atomic
时,现有对象的整个列表、map 或结构体将最终由拥有这些类型元素的参与者拥有。这意味着对这些对象的任何进一步更改都将导致冲突。
当 listType
、mapType
或 structType
从 atomic
更改为 map
/set
/granular
时,API 服务器无法推断这些字段的新所有权。因此,当对象更新这些字段时,不会产生冲突。因此,不建议将类型从 atomic
更改为 map
/set
/granular
。
例如,采用自定义资源
---
apiVersion: example.com/v1
kind: Foo
metadata:
name: foo-sample
managedFields:
- manager: "manager-one"
operation: Apply
apiVersion: example.com/v1
fieldsType: FieldsV1
fieldsV1:
f:spec:
f:data: {}
spec:
data:
key1: val1
key2: val2
在 spec.data
从 atomic
更改为 granular
之前,manager-one
拥有字段 spec.data
以及其中的所有字段(key1
和 key2
)。当 CRD 被更改为使 spec.data
成为 granular
时,manager-one
将继续拥有顶级字段 spec.data
(这意味着没有其他管理器可以在没有冲突的情况下删除名为 data
的 map),但它不再拥有 key1
和 key2
,因此另一个管理器可以修改或删除这些字段而不会发生冲突。
在控制器中使用服务器端 Apply
作为控制器的开发人员,你可以使用服务器端 Apply 来简化控制器的更新逻辑。与读取-修改-写入和/或 patch 的主要区别如下
- 应用的必须包含控制器关心的所有字段。
- 无法删除控制器之前未应用的字段(控制器仍然可以为这些用例发送 patch 或 update)。
- 不必事先读取对象;不必指定
resourceVersion
。
强烈建议控制器始终强制对其拥有和管理的对象发生冲突,因为它们可能无法解决或处理这些冲突。
转移所有权
除了 冲突解决 提供的并发控制之外,服务器端 Apply 还提供了执行用户到控制器的协调字段所有权转移的方法。
这最好用例子来解释。让我们看看如何安全地将 replicas
字段的所有权从用户转移到控制器,同时使用 HorizontalPodAutoscaler 资源及其 accompanying 控制器为 Deployment 启用自动水平缩放。
假设用户定义了一个 Deployment,并将 replicas
设置为所需的值
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
用户使用服务器端应用创建了 Deployment,如下所示
kubectl apply -f https://k8s.io/examples/application/ssa/nginx-deployment.yaml --server-side
然后稍后,为 Deployment 启用自动缩放;例如
kubectl autoscale deployment nginx-deployment --cpu-percent=50 --min=1 --max=10
现在,用户希望从他们的配置中删除 replicas
,这样他们就不会意外地与 HorizontalPodAutoscaler (HPA) 及其控制器发生冲突。但是,这里存在一个竞争条件:HPA 可能需要一些时间才会觉得需要调整 .spec.replicas
;如果用户在 HPA 写入该字段并成为其所有者之前删除了 .spec.replicas
,那么 API 服务器会将 .spec.replicas
设置为 1(Deployment 的默认副本计数)。这不是用户希望发生的情况,即使是临时的 - 它很可能会降低正在运行的工作负载的性能。
有两种解决方案
(基本)将
replicas
保留在配置中;当 HPA 最终写入该字段时,系统会给用户一个关于该字段的冲突。届时,就可以安全地从配置中删除它了。(更高级)但是,如果用户不想等待,例如因为他们希望他们的同事能够轻松理解集群配置,那么他们可以采取以下步骤来安全地从配置中删除
replicas
首先,用户定义一个仅包含 replicas
字段的新清单
# Save this file as 'nginx-deployment-replicas-only.yaml'.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
注意
在这种情况下,SSA 的 YAML 文件仅包含您要更改的字段。如果您只想使用 SSA 修改spec.replicas
字段,则不应提供完全符合要求的 Deployment 清单。用户使用私有字段管理器名称应用该清单。在此示例中,用户选择了 handover-to-hpa
kubectl apply -f nginx-deployment-replicas-only.yaml \
--server-side --field-manager=handover-to-hpa \
--validate=false
如果应用导致与 HPA 控制器发生冲突,则不执行任何操作。冲突表明控制器在过程中的某些时候比其他时候更早地声明了该字段的所有权。
此时,用户可以从他们的清单中删除 replicas
字段
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
请注意,每当 HPA 控制器将 replicas
字段设置为新值时,临时字段管理器将不再拥有任何字段,并将被自动删除。无需进一步清理。
在管理器之间转移所有权
字段管理器可以通过在它们的两个应用配置中将字段设置为相同的值,从而在彼此之间转移字段的所有权,从而导致它们共享该字段的所有权。一旦管理器共享该字段的所有权,其中一个可以从他们的应用配置中删除该字段,以放弃所有权并完成向另一个字段管理器的转移。
与客户端应用的比较
服务器端应用旨在替代 kubectl apply
子命令的原始客户端实现,并且作为 控制器实施更改的一种简单有效机制。
与 kubectl
管理的 last-applied
注释相比,服务器端应用使用更具声明性的方法,它跟踪对象的字段管理,而不是用户上次应用的状态。这意味着,作为使用服务器端应用的副作用,有关哪个字段管理器管理对象中的每个字段的信息也变得可用。
服务器端应用实现的冲突检测和解决的一个结果是,应用器始终在其本地状态中具有最新的字段值。如果不是这样,它们下次应用时会发生冲突。解决冲突的三种选择中的任何一种都会导致应用配置成为服务器字段上对象的最新子集。
这与客户端应用不同,在客户端应用中,已被其他用户覆盖的过时值将保留在应用器的本地配置中。这些值只有在用户更新该特定字段时才会变得准确(如果存在),并且应用器无法知道它们的下一次应用是否会覆盖其他用户的更改。
另一个区别是,使用客户端应用的应用器无法更改它们正在使用的 API 版本,但服务器端应用支持此用例。
在客户端应用和服务器端应用之间迁移
从客户端应用升级到服务器端应用
使用 kubectl apply
管理资源的客户端应用用户可以使用以下标志开始使用服务器端应用。
kubectl apply --server-side [--dry-run=server]
默认情况下,对象的字段管理从客户端应用转移到 kubectl 服务器端应用,而不会遇到冲突。
注意
使 last-applied-configuration
注释保持最新。该注释推断客户端应用管理的字段。任何不由客户端应用管理的字段都会引发冲突。
例如,如果您在客户端应用后使用 kubectl scale
更新了 replicas 字段,则该字段不归客户端应用所有,并且会在 kubectl apply --server-side
上产生冲突。
此行为适用于带有 kubectl
字段管理器的服务器端应用。作为例外,您可以通过指定不同的非默认字段管理器来选择不使用此行为,如下例所示。kubectl 服务器端应用的默认字段管理器是 kubectl
。
kubectl apply --server-side --field-manager=my-manager [--dry-run=server]
从服务器端应用降级到客户端应用
如果您使用 kubectl apply --server-side
管理资源,则可以使用 kubectl apply
直接降级到客户端应用。
降级有效,因为如果您使用 kubectl apply
,kubectl 服务器端应用会使 last-applied-configuration
注释保持最新。
此行为适用于带有 kubectl
字段管理器的服务器端应用。作为例外,您可以通过指定不同的非默认字段管理器来选择不使用此行为,如下例所示。kubectl 服务器端应用的默认字段管理器是 kubectl
。
kubectl apply --server-side --field-manager=my-manager [--dry-run=server]
API 实现
支持服务器端应用的资源的 PATCH
动词可以接受非官方的 application/apply-patch+yaml
内容类型。服务器端应用的用户可以将部分指定的对象作为 YAML 发送到资源的 URI 的 PATCH
请求的正文。在应用配置时,应始终包含对结果很重要的所有字段(例如您要定义的所需状态)。
所有 JSON 消息都是有效的 YAML。某些客户端使用也是有效 JSON 的 YAML 请求正文来指定服务器端应用请求。
访问控制和权限
由于服务器端应用是一种 PATCH
,主体(例如 Kubernetes RBAC 的角色)需要 patch 权限来编辑现有资源,并且还需要 create 动词权限才能使用服务器端应用创建新资源。
清除 managedFields
可以通过使用 patch(JSON 合并补丁、策略性合并补丁、JSON 补丁)或通过 update(HTTP PUT
)覆盖 managedFields
来从对象中删除所有 managedFields
;换句话说,通过 apply 以外的每个写操作都可以做到这一点。可以通过用一个空条目覆盖 managedFields
字段来完成此操作。以下是两个示例
PATCH /api/v1/namespaces/default/configmaps/example-cm
Accept: application/json
Content-Type: application/merge-patch+json
{
"metadata": {
"managedFields": [
{}
]
}
}
PATCH /api/v1/namespaces/default/configmaps/example-cm
Accept: application/json
Content-Type: application/json-patch+json
If-Match: 1234567890123456789
[{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}]
这将用包含单个空条目的列表覆盖 managedFields
,然后导致 managedFields
完全从对象中删除。请注意,将 managedFields
设置为空列表不会重置该字段。这是故意的,因此 managedFields
永远不会被不了解该字段的客户端删除。
在重置操作与对 managedFields
之外的其他字段的更改相结合的情况下,这将导致 managedFields
首先被重置,而其他更改随后被处理。因此,应用器将获得同一请求中更新的任何字段的所有权。
注意
服务器端应用无法正确跟踪不接收资源对象类型的子资源上的所有权。如果您将服务器端应用与此类子资源一起使用,则可能不会跟踪更改的字段。下一步
您可以在 Kubernetes API 参考的 metadata
顶层字段中阅读有关 managedFields
的信息。