从 Kubernetes 1.18 开始,可以看到一个明显的变化就是资源的 YAML 在 metadata 部分多了很多信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2021-02-18T03:03:40Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:status:
f:phase: {}
manager: kube-apiserver
operation: Update
time: "2021-02-18T03:03:40Z"
name: default
resourceVersion: "199"
uid: 2f4536b5-7302-4dc1-9620-052a567c917c
spec:
finalizers:
- kubernetes
status:
phase: Active
|
比如这个 NS 的例子, 其中大部分都是 mangedFields
部分.简单来讲,managedFields
字段是用来声明一个资源的各个字段的具体的管理者是谁.我们可以参考一个有点类似的案例, Node 的 conditions 字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
| conditions:
- lastHeartbeatTime: "2021-02-18T03:05:25Z"
lastTransitionTime: "2021-02-18T03:05:25Z"
message: Flannel is running on this node
reason: FlannelIsUp
status: "False"
type: NetworkUnavailable
- lastHeartbeatTime: "2021-02-18T07:34:05Z"
lastTransitionTime: "2021-02-18T03:03:36Z"
message: kubelet has sufficient memory available
reason: KubeletHasSufficientMemory
status: "False"
type: MemoryPressure
|
Contaions 是一个列表,不同的 Component 负责上报自己所观察到的信息,最终汇总到 Node 的 Status 下面.然后再合并汇总出一个整体的 Ready
状态. mangedFieds
也是有点类似的思路, 一个资源的不同部分是由不同组件/人负责维护的,显式的声明这种维护关系有助于各方协同地维护一个资源完整的状态.更直观的,我们可以看一个 Deployment 的例子. 首先,我们使用如下的 deployment 文件来创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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
|
创建命令是:
1
2
| # -n 后面是希望使用的 namespace, 非关键信息
kubectl apply -f d.yaml -n ssa
|
最终生成的 deployment 如下所示(部分信息):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
| apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx-deployment","namespace":"ssa"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}},"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx"}]}}}}
creationTimestamp: "2021-02-18T07:41:40Z"
generation: 1
labels:
app: nginx
managedFields:
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:labels:
.: {}
f:app: {}
f:spec:
f:progressDeadlineSeconds: {}
f:replicas: {}
f:revisionHistoryLimit: {}
f:selector: {}
f:strategy:
f:rollingUpdate:
.: {}
f:maxSurge: {}
f:maxUnavailable: {}
f:type: {}
f:template:
f:metadata:
f:labels:
.: {}
f:app: {}
f:spec:
f:containers:
k:{"name":"nginx"}:
.: {}
f:image: {}
f:imagePullPolicy: {}
f:name: {}
f:resources: {}
f:terminationMessagePath: {}
f:terminationMessagePolicy: {}
f:dnsPolicy: {}
f:restartPolicy: {}
f:schedulerName: {}
f:securityContext: {}
f:terminationGracePeriodSeconds: {}
manager: kubectl-client-side-apply
operation: Update
time: "2021-02-18T07:41:40Z"
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
f:deployment.kubernetes.io/revision: {}
f:status:
f:availableReplicas: {}
f:conditions:
.: {}
k:{"type":"Available"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
k:{"type":"Progressing"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
f:observedGeneration: {}
f:readyReplicas: {}
f:replicas: {}
f:updatedReplicas: {}
manager: kube-controller-manager
operation: Update
time: "2021-02-18T07:41:42Z"
name: nginx-deployment
|
其中 f
后面跟的是 field 的名字,其他部分都比较直观.可以看到 managedFieds
包含两个 item:
- 第一个manager 是
kubectl-client-side-apply
.因为这个 deploy 是我直接用 kubectl apply
创建的. 默认 kubectl apply 使用的是 Client-Side Apply 而不是 Server-Side Apply. - 第二个manager 是
kube-controller-manager
, 比较直观.对比两个 manager 所管理的字段列表来看, kube-controller-manager
管理的都是我们通常看到的由 系统
自动填充的信息, 比如 conditions
, .status.repliacs
等.而kubectl-client-side-apply
则是管理那些用户自己输入的信息.
如上所展示的 manager 机制即是 Server-Side Apply的核心, 下面将详述细节.
kubectl apply
首先我们需要大概了解 kubectl apply
做了什么,分为如下几步:
- GET 查询目标 Object
- 如果 Object 不存在,则提交到API创建,并且将用户提供的 input 信息放到 新创建的 Object 的 annotations里(key 为
kubectl.kubernetes.io/last-applied-configuration
) - 如果 Object 已存在, 先读取到旧的 Object 的
kubectl.kubernetes.io/last-applied-configuration
的值, 然后与当前的做对比, 生成一个 diff (strategic merge patch), 用 PATCH 发给 API Server. 其中 diff 里面也会附带上最新的 kubectl.kubernetes.io/last-applied-configuration
.
其中, strategic merge patch
需要单独提一下,它的主要特点就是对于 list 的 update 提供了 patchStrategy
以便对不同的字段做不同的处理.比如我们常见的 Pod 的 containers 列表:
1
2
3
| type PodSpec struct {
...
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" ...`
|
举个例子.最初始的 Pod Template 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| apiVersion: apps/v1
kind: Deployment
metadata:
name: patch-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: patch-demo-ctr
image: nginx
tolerations:
- effect: NoSchedule
key: dedicated
value: test-team
|
假设想用如下的 yaml PATCH 这个 deploy
1
2
3
4
5
6
| spec:
template:
spec:
containers:
- name: patch-demo-ctr-2
image: redis
|
1
| kubectl patch deployment patch-demo --patch "$(cat patch-file.yaml)"
|
依赖于上面的 containers patch 策略的声明,我们能得到如下的最终结果:
1
2
3
4
5
6
7
8
9
| containers:
- image: redis
imagePullPolicy: Always
name: patch-demo-ctr-2
...
- image: nginx
imagePullPolicy: Always
name: patch-demo-ctr
...
|
这样更符合用户的期望.而其他的一些字段,如果没有声明 patchStrategy
, 那么默认操作是 replace.
何为 Service-Side Apply
Server-Side Apply
可以字面地理解为将 kubectl apply
的工作迁移到 Server 端来进行, 准确地可以定义为一种新的Merge算法.它的好处如下:
--dry-run
如果是在 client 端进行,那么因为 Server 端存在各种 webhook以及 injector,很难模拟真实的情况.如果 –dry-run 放在服务端, 如果出错了,可以通过新加的 mangedFields
机制给用户更明确的提示- 如果不用 kubectl 而是 API 开发则很难利用到 kubectl 提供的功能
- 比
kubectl apply
使用的 kubectl.kubernetes.io/last-applied-configuration
更加声明式一些.用户发送一个资源的manifest,其中包含了自己关心的字段的期待状态. - 防止用户误修改一些字段
- 多个组件/用户对资源的协作管理
- 其他一些在 Server 端实现起来更简单的地方
Server-Side Apply 利用 managedFields
字段,追踪了各个字段的归属,并且能在更新的时候提供冲突检测(manager不匹配),并提供更为准确的提示.
managedFields 详解
现在我们可以详细看下 managedFields的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| - apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:deployment.kubernetes.io/revision: {}
f:status:
f:availableReplicas: {}
f:conditions:
.: {}
k:{"type":"Available"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
k:{"type":"Progressing"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
f:observedGeneration: {}
f:readyReplicas: {}
f:replicas: {}
f:updatedReplicas: {}
manager: kube-controller-manager
operation: Update
time: "2021-02-18T04:58:50Z"
|
其中:
f
: 代表字段名v
: 代表 value, 字段的值k
: 代表 key, map object, 包含 f/vi
: index, 数组的索引
k
不太好理解, 对应的资源字段示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| status:
availableReplicas: 3
conditions:
- lastTransitionTime: "2021-02-18T04:58:50Z"
lastUpdateTime: "2021-02-18T04:58:50Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2021-02-18T04:56:00Z"
lastUpdateTime: "2021-02-18T04:58:50Z"
message: ReplicaSet "nginx-deployment-877f48f6d" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 3
replicas: 3
updatedReplicas: 3
|
另外:
manager
: 操作主体operator
: 动作time
: 操作时间
如下所示,我们将上面用到的 deployment 修改一下,然后用不同的 manager 去 apply 一下试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
| ➜ server-side-apply git:(master) ✗ kubectl apply -f d.yaml --server-side --field-manager='test1'
error: Apply failed with 1 conflict: conflict with "kubectl-client-side-apply" using apps/v1: .spec.replicas
Please review the fields above--they currently have other managers. Here
are the ways you can resolve this warning:
* If you intend to manage all of these fields, please re-run the apply
command with the `--force-conflicts` flag.
* If you do not intend to manage all of the fields, please edit your
manifest to remove references to the fields that should keep their
current managers.
* You may co-own fields by updating your manifest to match the existing
value; in this case, you'll become the manager if the other manager(s)
stop managing the field (remove it from their configuration).
See http://k8s.io/docs/reference/using-api/api-concepts/#conflicts
|
上面的错误提示也展示了在冲突时可能的几种解决办法:
- 覆盖: 如果确定是要修改,可以使用foce参数,强制覆盖,并且将其manager变更为自己
- 忽略: 如果本身并不关心这个字段的值,可以将其从自己的 manifest 中移除然后重试
- 共享: 将冲突字段改为原来的值,这种情况下更新后将与原来的manager共享管理.
同时需要注意的时,通常的两种更新操作对 manager 的处理也不同:
- PATCH: content type 为
application/apply-patch+yaml
, 冲突逻辑如上所示 - UPDATE: 其他更新方式,完全忽略冲突
Controller 的使用
目前常用的对 resource 的操作基本上都遵循一个 READ-UPDATE 的步骤,因为 resourceVersion 的冲突问题. Service-Side Apply 有望能去掉 READ 这步,直接进行 UPDATE 操作.
一般也推荐 controller 在遇到冲突的时候执行 force 操作.
现状
功能仍有缺陷, 接口复杂,未到 stable 状态,尽量不用使用.
Links
- Kubernetes 1.18 Feature Server-side Apply Beta 2
- Understanding OpenKruise Kubernetes Resource Update Mechanisms
- Strategic Merge Patch
- Update API Objects in Place Using kubectl patch
- Break Down Kubernetes Server-Side Apply