前言
本篇文章主要想讨论一下,Kubernetes 的 kubectl 命令中的使用到到的一个编程模式 – Visitor(注:其实,kubectl 主要使用到了两个一个是Builder,另一个是Visitor)。本来,Visitor 是面向对象设计模英中一个很重要的设计模款(参看Wikipedia Visitor Pattern词条),这个模式是一种将算法与操作对象的结构分离的一种方法。这种分离的实际结果是能够在不修改结构的情况下向现有对象结构添加新操作,是遵循开放/封闭原则的一种方法。这篇文章我们重点看一下 kubelet 中是怎么使用函数式的方法来实现这个模式的。
更新历史
2020 年 12 月 27 日 - 初稿
一个简单示例
我们还是先来看一个简单设计模式的Visitor的示例。
- 我们的代码中有一个
Visitor的函数定义,还有一个Shape接口,其需要使用Visitor函数做为参数。 - 我们的实例的对象
Circle和Rectangle实现了Shape的接口的accept()方法,这个方法就是等外面给我传递一个Visitor。
1 | package main |
然后,我们实现两个Visitor,一个是用来做JSON序列化的,另一个是用来做XML序列化的
1 | func JsonVisitor(shape Shape) { |
下面是我们的使用Visitor这个模式的代码
1 | func main() { |
其实,这段代码的目的就是想解耦 数据结构和 算法,使用 Strategy 模式也是可以完成的,而且会比较干净。但是在有些情况下,多个Visitor是来访问一个数据结构的不同部分,这种情况下,数据结构有点像一个数据库,而各个Visitor会成为一个个小应用。 kubectl就是这种情况。
k8s相关背景
接下来,我们再来了解一下相关的知识背景:
- 对于Kubernetes,其抽象了很多种的Resource,比如:Pod, ReplicaSet, ConfigMap, Volumes, Namespace, Roles …. 种类非常繁多,这些东西构成为了Kubernetes的数据模型(点击 Kubernetes Resources 地图 查看其有多复杂)
kubectl是Kubernetes中的一个客户端命令,操作人员用这个命令来操作Kubernetes。kubectl会联系到 Kubernetes 的API Server,API Server会联系每个节点上的kubelet,从而达到控制每个结点。kubectl主要的工作是处理用户提交的东西(包括,命令行参数,yaml文件等),然后其会把用户提交的这些东西组织成一个数据结构体,然后把其发送给 API Server。- 相关的源代码在
src/k8s.io/cli-runtime/pkg/resource/visitor.go中(源码链接)
kubectl 的代码比较复杂,不过,其本原理简单来说,它从命令行和yaml文件中获取信息,通过Builder模式并把其转成一系列的资源,最后用 Visitor 模式模式来迭代处理这些Reources。
下面我们来看看 kubectl 的实现,为了简化,我用一个小的示例来表明 ,而不是直接分析复杂的源码。
kubectl的实现方法
Visitor模式定义
首先,kubectl 主要是用来处理 Info结构体,下面是相关的定义:
1 | type VisitorFunc func(*Info, error) error |
我们可以看到,
- 有一个
VisitorFunc的函数类型的定义 - 一个
Visitor的接口,其中需要Visit(VisitorFunc) error的方法(这就像是我们上面那个例子的Shape) - 最后,为
Info实现Visitor接口中的Visit()方法,实现就是直接调用传进来的方法(与前面的例子相仿)
我们再来定义几种不同类型的 Visitor。
Name Visitor
这个Visitor 主要是用来访问 Info 结构中的 Name 和 NameSpace 成员
1 | type NameVisitor struct { |
我们可以看到,上面的代码:
- 声明了一个
NameVisitor的结构体,这个结构体里有一个Visitor接口成员,这里意味着多态。 - 在实现
Visit()方法时,其调用了自己结构体内的那个Visitor的Visitor()方法,这其实是一种修饰器的模式,用另一个Visitor修饰了自己(关于修饰器模式,参看《https://www.amoyw.com/2020/12/24/go-decoration/)》)
Other Visitor
这个Visitor主要用来访问 Info 结构中的 OtherThings 成员
1 | type OtherThingsVisitor struct { |
实现逻辑同上,我就不再重新讲了
Log Visitor
1 | type LogVisitor struct { |
使用方代码
现在我们看看如果使用上面的代码:
1 | func main() { |
上面的代码,我们可以看到
- Visitor们一层套一层
- 我用
loadFile假装从文件中读如数据 - 最后一条
v.Visit(loadfile)我们上面的代码就全部开始激活工作了。
上面的代码输出如下的信息,你可以看到代码的执行顺序是怎么执行起来了
1 | LogVisitor() before call function |
我们可以看到,上面的代码有以下几种功效:
- 解耦了数据和程序。
- 使用了修饰器模式
- 还做出来pipeline的模式
所以,其实,我们是可以把上面的代码重构一下的。
Visitor修饰器
下面,我们用修饰器模式来重构一下上面的代码。
1 | type DecoratedVisitor struct { |
上面的代码并不复杂,
- 用一个
DecoratedVisitor的结构来存放所有的VistorFunc函数 NewDecoratedVisitor可以把所有的VisitorFunc转给它,构造DecoratedVisitor对象。DecoratedVisitor实现了Visit()方法,里面就是来做一个for-loop,顺着调用所有的VisitorFunc
于是,我们的代码就可以这样运作了:
1 | info := Info{} |
是不是比之前的那个简单?注意,这个DecoratedVisitor 同样可以成为一个Visitor来使用。
好,上面的这些代码全部存在于 kubectl 的代码中,你看懂了这里面的代码逻辑,相信你也能够看懂 kubectl 的代码了。