kubectl apply 之后客户端做了什么?

2023-01-03 22:43:26 浏览数 (3)

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 的执行流程

WechatIMG222.jpegWechatIMG222.jpeg

在这个流程中,首先是准备了三路合并的资源对象, modifiedcurrentoriginal。其中 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-serverpatch 接口,完成了对目标资源对象的更新操作。

三路资源对象合并,生成增量 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 的。

第一步是对 currentmodifieddiff 操作,忽略删除字段的操作,值保留新增与修改字段的操作,生成 deltaMap

  • 忽略删除字段的操作,是因为 current 里面包含了一些默认值、状态信息以及服务端对于资源对象的一些更改操作,这些并不包含在用户传入的资源对象里面。如果不忽略删除字段操作,这些默认值、状态值以及一些服务端的修改就会被认为是需要做删除的操作字段。
  • 真正需要删除操作的字段,应该是通过 original 和 modified 对比得到的。
  • 除了正常的字段更新,这里还会包含注解的更新,因为注解 "lac" 在前面过程中被 modified 资源对象重新赋值了

第二步对 originalmodifieddiff 操作,忽略新增与修改的字段的操作,保留删除字段的操作,生成 deleteMap

  • 真正需要删除的字段操作,是通过 originalmodified 对比才能够得到的
  • 这里忽略了新增和修改字段的操作,第一是因为这个在第一步已经计算出来了,第二是因为对于一些默认字段的修改操作,currentmodified 对比的结果是修改,而 originalmodified 对比的结果是新增。显然修改操作比新增操作是更加符合对 current 资源对象的操作。

第三步就是合并 deltaMapdeleteMappatchMap

总结

最后我们了解到,我们经常执行的 kubectl apply ,实际并不是把我们传入的资源文件原封不动的传给 api-server,而是在客户端提前做好处理,得到增量的patch 再向 api-server 发起对目标对象的 patch 操作。

0 人点赞