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