kubectl apply 之后客户端做了什么?
前言
在学习 kubernetes
源码过程中,看到 kubectl apply
源码里面有个很有意思的现象。起初我以为当我们执行 kubectl apply -f deployment.yaml
之后,客户端会把 deployment.yaml
完整地发送给 api-server
,然后让 api-server
重新渲染这个 deployment
资源并更替旧的资源。
实际上却并非这样,当我们执行 kubectl apply
的时候,默认是采用的是 client-side apply
模式。客户端会先会生成一个增量的 patch
对象,然后在发送给 api-server
进行 patch
操作。而生成这个增量信息生成的算法,是通过三路数据合并得到的。
本文源码选取
kubectl
的源码本身应该在 git@github.com:kubernetes/kubectl.git
仓库。但是在 kubernetes 大仓里面也有一个用于发布到公共类库的镜像源码。所以我直接下载 kubernetes 大仓 git@github.com:kubernetes/kubernetes.git
,查看源码更加方便一些。我的 kubectl
客户端版本是 v1.21.3
,所以就切换到对应标签 v1.21.3
。
kubectl apply 执行流程
下面用伪代码大概描述下 kubectl apply
的执行流程
在这个流程中,首先是准备了三路合并的资源对象, modified
、current
和 original
。其中 modified
是从用户传入的文件中获取的资源对象。current
则是从 api-server
获取的实际运行的资源对象信息。original
则是从 current
资源对象的 lac(last-applied-configuration)
注解中获取的上一次 apply 的原始拷贝。
这个三路资源对象数据的关系可以这样理解,current
是上一次三路合并里面 modified
资源对象基础上,增加一些必填项、默认值、状态信息以及服务端修改之后的资源对象。由于 current
已经被修改了,所以需要使用一个注解把原本没有修改的资源对象信息给记录下来,所以其实本次三路合并的 original
实际就是上一次三路合并的 modified
的拷贝信息。
接着 kubectl apply
对三路资源对象比较分析,得出一个增量数据 patch
。这个 patch
是遵循 kubernetes
自己定义的 strategic merge patch
格式的增量信息,内容包含了本次需要更改、增加以及删除的字段操作信息,也包含了更新 lac
注解内容。最后通过 api-server
的 patch
接口,完成了对目标资源对象的更新操作。
三路资源对象合并,生成增量 patch
下面我们仔细阅读一下 strategicpatch.CreateThreeWayMergePatch
源码,看看 patch
内容是怎么产生的。
源码解析在代码的注解中
代码语言:go复制func CreateThreeWayMergePatch(original, modified, current []byte, schema LookupPatchMeta,
overwrite bool, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
...
deltaMapDiffOptions := DiffOptions{
IgnoreDeletions: true,
SetElementOrder: true,
}
// 对比 current 与 modified,忽略删除字段的操作,只返回新增和修改字段的操作
deltaMap, err := diffMaps(currentMap, modifiedMap, schema, deltaMapDiffOptions)
if err != nil {
return nil, err
}
deletionsMapDiffOptions := DiffOptions{
SetElementOrder: true,
IgnoreChangesAndAdditions: true,
}
// 对比 original 与 modified, 忽略新增和修改内容,只返回删除的字段的操作
deletionsMap, err := diffMaps(originalMap, modifiedMap, schema, deletionsMapDiffOptions)
if err != nil {
return nil, err
}
mergeOptions := MergeOptions{}
// 合并新增、修改以及删除操作
patchMap, err := mergeMap(deletionsMap, deltaMap, schema, mergeOptions)
if err != nil {
return nil, err
}
...
// 这里 original 和 current 的 diff 可以理解为默认值、状态字段以及服务端的更改。
// 当 overwrite == true ,客户端的更改将会覆盖服务端的更改。
// 当 overwrite == false ,客户端更改如果与服务端更改有冲突,则会报错。
if !overwrite {
changeMapDiffOptions := DiffOptions{}
changedMap, err := diffMaps(originalMap, currentMap, schema, changeMapDiffOptions)
if err != nil {
return nil, err
}
hasConflicts, err := MergingMapsHaveConflicts(patchMap, changedMap, schema)
if err != nil {
return nil, err
}
if hasConflicts {
return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(patchMap), mergepatch.ToYAMLOrError(changedMap))
}
}
// 最后返回这次客户端更改的增量操作集合
return json.Marshal(patchMap)
}
简单来说,CreateThreeWayMergePatch
分为三步来生成增量 patch
的。
第一步是对 current
和 modified
做 diff
操作,忽略删除字段的操作,值保留新增与修改字段的操作,生成 deltaMap
。
- 忽略删除字段的操作,是因为
current
里面包含了一些默认值、状态信息以及服务端对于资源对象的一些更改操作,这些并不包含在用户传入的资源对象里面。如果不忽略删除字段操作,这些默认值、状态值以及一些服务端的修改就会被认为是需要做删除的操作字段。 - 真正需要删除操作的字段,应该是通过 original 和 modified 对比得到的。
- 除了正常的字段更新,这里还会包含注解的更新,因为注解 "lac" 在前面过程中被
modified
资源对象重新赋值了
第二步对 original
和 modified
做 diff
操作,忽略新增与修改的字段的操作,保留删除字段的操作,生成 deleteMap
。
- 真正需要删除的字段操作,是通过
original
和modified
对比才能够得到的 - 这里忽略了新增和修改字段的操作,第一是因为这个在第一步已经计算出来了,第二是因为对于一些默认字段的修改操作,
current
与modified
对比的结果是修改,而original
与modified
对比的结果是新增。显然修改操作比新增操作是更加符合对current
资源对象的操作。
第三步就是合并 deltaMap
和 deleteMap
为 patchMap
。
总结
最后我们了解到,我们经常执行的 kubectl apply
,实际并不是把我们传入的资源文件原封不动的传给 api-server
,而是在客户端提前做好处理,得到增量的patch
再向 api-server
发起对目标对象的 patch
操作。