前言

前段时间接触了k8s operator开发,在开发的过程中碰到了很多问题,现将开发过程中碰到的问题及经验进行总结。

一、开发流程步骤

1.开发前提

  • 了解k8s中内置资源的使用及client-go(client-go不是必须项如果了解更好,笔者也是在开发结束后通过阅读《kubernetes源码剖析》第五章补上这一课的)
  • 有Go语言基础
  • 了解k8s operator编程模式
  • 了解kubebuilder(kubebuilder官方链接),熟悉如何构建operator项目以及kubebuilder官方链接中的示例demo

2.开发某个产品的operator时,在进行开发前最好先在k8s集群中手动部署待开发的产品,熟悉该产品相关运维操作的同时也了解该operator最终是在对哪些k8s内置资源进行管理以及内置资源的相关配置;

3.理清楚operator中涉及的操作包含哪些,例如集群构建、节点迁移、资源配额管理等;此外还需要好好设计spec和status;

4.经过前面步骤后我们就进入实际开发过程了,在第三步的基础上我们可以知道operator中涉及到的操作最终都是对k8s内置资源的操作,所以第一步可以对涉及到的k8s内置资源的相关操作进行开发,然后开发集群构建流程、维护流程等。


二、开发总结

1.在对k8s内置资源相关操作进行开发时,可以将所有内置资源相同的方法抽象出一个接口,比如创建资源、判断资源是否存在等

2.在创建内置资源时,例如一个Pod,可以对照k8s源码中对应资源的结构体定义来构建

3.在创建内置资源时,可以使用OwnerRefrence来做资源关联,这样当owner资源被删除时,被own的资源会级联删除,其本质是k8s GC机制。
具体调用controllerutil的SetControllerReference如下函数:

// SetControllerReference sets owner as a Controller OwnerReference on owned.
// This is used for garbage collection of the owned object and for
// reconciling the owner object on changes to owned (with a Watch + EnqueueRequestForOwner).
// Since only one OwnerReference can be a controller, it returns an error if
// there is another OwnerReference with Controller flag set.
SetControllerReference = controllerutil.SetControllerReference

这里涉及到k8s GC原理,下图是k8s GC的GC是Garbage Collector的简称,它是负责删除集群中的资源对象,GC由三个部分组成,分别是:Scanner、Garbage Processor和Propagator。

  • Scanner
    Scanner主要完成的任务是周期性地扫描系统中所有资源对象,并将这些资源对象放到Dirty Queue中

  • Garbage Processor
    Garbage Processor由两部分组成,分别是Dirty Queue和workers,每个worker的任务有三个:
    (1)从Dirty Queue中取出资源
    (2)判断该资源的OwnerReferences是否为空,如果为空则继续处理Dirty Queue中下一个资源
    (3)如果资源的OwnerReferences不为空,则检查OwnerReferences中的每个条目是否存在,如果至少有一个存在则什么也不做,如果都不存在则请求API Server删除该资源

  • Propagator
    Propagator是由Event Queue、一个worker和一个表示owner-dependant关系的DAG组成的。DAG只存储name/uid/orphan三元组。Propagator会监控所有资源的创建/更新/删除事件,并将事件放入到Event Queue中。worker负责从Event Queue中取出事件并根据事件类型做出对应的处理。如果是创建或者更新事件,则会更新DAG(如果资源对象有一个owner且owner不存在于DAG中,除了会将该资源添加到DAG中还会将该资源添加到Dirty Queue中)。如果是删除事件则会从DAG中删除该资源,并且还会将该资源依赖的资源添加到Dirty Queue中。

在这里插入图片描述

有关k8s GC相关设计可以参考:官方设计方案
在k8s中资源之间有owner-dependant关系,详见:Owners and Dependents

4.在开发过程中会碰到类似于给pod添加/删除label、修改PVC存储大小、修改pod镜像等问题,这些问题实质上是对k8s内置资源的修改,在对资源修改时有两种选择,分别是update和patch,这时就需要清楚两者的区别:

  • 在k8s的内置资源对象中,有一个全局唯一的版本号metadata.resourceVersion,这个版本号从资源对象创建开始就会被分配一个。如果是使用update对资源进行更新时,需要将整个修改后的对象提交给k8s,而被提交的对象中需要包含resourceVersion,当k8s收到该请求时会先比较请求中的resourceVersion与当前k8s中的该对象的resourceVersion是否一致,如果一致才会执行update操作,否则会拒绝该请求并返回版本冲突的问题。此外还需要注意:当资源对象更新成功后,其resourceVersion会发生变化。
  • 对于patch请求,只需要将待修改资源对象中的某些字段的修改提交给k8s即可,但是这里需要注意:在k8s中patch有四种类型分别是:JSONPatchType、MergePatchType、StrategicMergePatchType和ApplyPatchType。比较常用的是JSONPatchType和MergePatchType
    (1)JSONPatchType
    JSONPatchType的数据格式如下:
[
	{
		“op”:“XXX”,
		“path”:“xxx”,
		“value”:“xxx”
	}
	...
]

其中op表示对资源对象的操作类型,主要包括三种:add、remove、replace;path表示待修改的数据的属性的路径;value表示修改后的值。

(2)MergePatchType
MergePatchType需要包含对资源对象的部分描述,该请求发送到k8s后会和集群中的当前对象进行合并从而创建新的对象。例如在对pod的label进行修改时可以进行以下格式数据拼接:

patchdate, err:= json.Marshal(
	map[string]interface{}{
		"metadata": map[stirng]interface{}{
			"labels": map[string]interface{}{
				"a": "b"
			}	
		},
	},
)

这里需要注意以下两点,一个是如果value值为nil,则表示要删除对应的键;另一个是MergePatchType没有办法单独更新一个列表或者数组中的某个元素,所以如果是要在containers中新增容器、修改容器的image等,需要用整个containers列表来提交。

5.判断k8s内置资源是否删除的依据
在某些场景下需要判断资源对象是否删除完成,这种情况下可以通过判断该资源对象的deletionTimestamp字段是否为空,如果不为空则表示该资源正在删除中。

//判断pod是否处于删除状态中
pod.DeletionTimestamp.IsZero()

在这里插入图片描述

7.对pod的资源进行修改时需要删除pod进行重建

8.修改PVC大小时需要注意只能扩容不能缩容,且对其修改时可以采用JSONPatchType

9.在对pod的image进行修改时,旧的container不会经历running和ready这两个状态而是直接替换为新的container

10.最重要的一点是所有的逻辑需要保持幂等

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐