Kubernetes Visitor 模式
关 注 微 信 公 众 号 《 云 原 生 C T O 》 更 多 云 原 生 干 货 等 你 来 探 索
专
注
于
云原生技术
分
享
提
供
优
质
云原生开发
视
频
技
术
培
训
面试技巧
,
及
技术疑难问题
解答
云
原
生
技
术
分
享
不
仅
仅
局
限
于
Go
、
Rust
、
Python
、
Istio
、
containerd
、
CoreDNS
、
Envoy
、
etcd
、
Fluentd
、
Harbor
、
Helm
、
Jaeger
、
Kubernetes
、
Open Policy Agent
、
Prometheus
、
Rook
、
TiKV
、
TUF
、
Vitess
、
Argo
、
Buildpacks
、
CloudEvents
、
CNI
、
Contour
、
Cortex
、
CRI-O
、
Falco
、
Flux
、
gRPC
、
KubeEdge
、
Linkerd
、
NATS
、
Notary
、
OpenTracing
、
Operator Framework
、
SPIFFE
、
SPIRE
和
Thanos
等
Kubernetes Visitor 模式
说到那些为我打开了高效编程大门的人,我想说的是四人组设计模式(Design Pattern by Gang of four)是第一个推动我的人
http://www.gofpatterns.com/index.php
帮助我更好地理解各种代码结构,更合理地编码。当然是基于 Java
的,还有很多其他设计模式的文章,因为设计模式是很多 Java
开源框架所奉行的原则,比如 springframework
中常见的 factory
模式、 proxy
模式和 visitor
模式。
幸运的是,您所学到的和牢记于心的东西将会得到回报——我从 Java
得到的“ key
”似乎也适用于 Kubernetes
。读完 Kubectl
和 k8s
的源代码后,您会发现它们拥有相似的设计模式,尽管实现方式不同。
好了,让我们回到主题,深入探究 Visitor
模式,看看“ key
”在 Kubectl
和 Kubernetes
中是如何工作的,以促进日常的编码和开发。
什么是Visitor模式
用 Visitor
模式编码的工作流是最好的答案。
在 Gof
中,还解释了为什么引入 Visitor
模式。
在设计跨类层次结构的异构对象集合的操作时, Visitor
模式非常有用。 Visitor
模式允许在不更改集合中任何对象的类的情况下定义操作。为了实现这一点, Visitor
模式建议在一个单独的类(称为 Visitor
类)中定义操作。这将操作与它所操作的对象集合分开。对于每一个要定义的新操作,都会创建一个新的 Visitor
类。因为操作要跨一组对象执行,所以 Visitor
需要一种访问这些对象的公共成员的方法。这个需求可以通过实现以下两个设计思想来解决。
在实际场景中解释它:基于接口特性,动态地解耦对象和它们的动作,以实现更稳定的代码、更少的维护和更快的添加新函数(添加一个新的 ConcreteVisitor
)的迭代。
在 Go
中, Visitor
模式的应用程序可以做同样的改进,因为界面是它的主要特性之一。
Kubectl和Kubernetes中的访客模式
Kubernetes
是一个容器编排平台,上面有各种不同的资源。 kubectl
是一个命令行工具,它使用以下命令格式操作资源。
kubectl get {resourcetype} {resource_name}
kubectl edit {resourcetype} {resource_name}
…
kubectl
将这些命令组合到 APIServer
接收到的数据中,发起一个请求,然后返回结果,实际执行一个构建器方法,该方法封装了各种 Visitor
来处理请求的参数和结果,最后得到我们在命令行上看到的结果。
构建器方法: http://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go#L94
func (f *factoryImpl) NewBuilder() *resource.Builder {
return resource.NewBuilder(f.clientGetter)
}
这里的大多数 Visitor
都是在 Visitor
中定义的。 Go
,源文件的名称不言自明。
这段代码有 700
多行,它使用了 builder
模式( builder.go
)和 visitor
模式来连接 Visitor
,并通过调用它们各自的 VisitorFunc
方法来实现函数,同时在 builder.go
中封装了 VisitorFunc
的具体实现。
visitor.go: http://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go
builder.go: http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/builder.go
VisitorFunc: http://github.com/kubernetes/kubernetes/blob/ea0764452222146c47ec826977f49d7001b0ea8c/staging/src/k8s.io/cli-runtime/pkg/resource/interfaces.go#L103
type VisitorFunc func(*Info, error) error
type Visitor interface {
Visit(VisitorFunc) error
}
type Info struct {
Namespace string
Name string
OtherThings string
}
func (info *Info) Visit(fn VisitorFunc) error {
return fn(info, nil)
}
那些执行 Visit
方法的 visitor
被认为是合格的 visitor
。让我们来看看一些典型的 visitor
。
Selector
在 kubectl
中,我们默认访问默认名称空间。但是有 -n/ -namespace
选项来指定我们想要访问的名称空间,还有 -l/ -label
用于选择合格的资源。命令如下所示。
kubectl get pod pod1 -n test -l abc=true
通过 Selector
访问器查看实现。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/selector.go#L27
type Selector struct {
Client RESTClient
Mapping *meta.RESTMapping
Namespace string
LabelSelector string
FieldSelector string
LimitChunks int64
}
当然,我们实现了 Visit
方法,最终为 api
访问构造了合理的 Info
对象。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/selector.go#L67
list, err := helper.List(
r.Namespace,
r.ResourceMapping().GroupVersionKind.GroupVersion().String(),
&options,
)
if err != nil {
return nil, EnhanceListError(err, options, r.Mapping.Resource.String())
}
resourceVersion, _ := metadataAccessor.ResourceVersion(list)
info := &Info{
Client: r.Client,
Mapping: r.Mapping,
Namespace: r.Namespace,
ResourceVersion: resourceVersion,
Object: list,
}
if err := fn(info, nil); err != nil {
return nil, err
}
DecoratedVisitor
DecoratedVisitor
包含一个 Visitor
和一组装饰器( VisitorFunc
),在执行 Visit
方法时按顺序执行所有的装饰器。
http://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/staging/src/k8s.io/cli-runtime/pkg/resource/visitor.go#L309
// Visit implements Visitor
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
if err != nil {
return err
}
for i := range v.decorators {
if err := v.decorators[i](info, nil); err != nil {
return err
}
}
return fn(info, nil)
})
}
当在生成器中初始化它时, Visitor
将被添加到由结果处理的 Visitor
列表中。你可以在命名空间处理后调用 get
方法。
http://github.com/kubernetes/kubernetes/blob/fafbe3aa51473a70980e04ae19f7db2d32d7365b/staging/src/k8s.io/cli-runtime/pkg/resource/builder.go#L1119
if b.latest {
// must set namespace prior to fetching
if b.defaultNamespace {
visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
}
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
}
func RetrieveLatest(info *Info, err error) error {
if err != nil {
return err
}
if meta.IsListType(info.Object) {
return fmt.Errorf("watch is only supported on individual resources and resource collections, but a list of resources is found")
}
if len(info.Name) == 0 {
return nil
}
if info.Namespaced() && len(info.Namespace) == 0 {
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
}
return info.Get()
}
在代码中,也有一些类似的 Visitor
处理不同的逻辑。这种设计模式的一个明显优点是操作简单。基本上,所有的资源都符合这个基于 gkv
的操作。所以当添加 Visitor
时,不修改 Visitor
。去是必要的。相反,只要实现了 VisitorFunc
接口,就可以直接添加新的 go
文件。然后在构建器构建过程中添加相关逻辑。
实际的Visitor模式
回顾我最近写的代码,我已经不知不觉地发现了 Visitor
模式的踪迹。我和我的同事定制了许多 crd
,编写了 operator
,并在集群中运行它们,提供不同的服务,如安全、 RBAC
自动添加、 SA
自动创建等。
这些 cd
都有不同的字段属性,例如, groupprbac
包含组名、电子邮件和用户列表。标识,包含组名和关联的 Rolebindings
的状态。由于厌倦了重复的 kubectl get groupbac xxx
和 kubectl get identity xxx
,我决定创建一个 kubectl
插件来使用 kubectl groupget {groupName}
获取它们。那么如何?我首先想到的是 Bash
,但如果添加更多的资源,它将很难维护和扩展。
回到 Visitor
模式本身。在处理资源访问时,我定义了一组 Visitor
,可以用来访问不同的资源。代码结构如下所示。
type VisitorFunc func(*Info, error) error
type GroupRbacVisitor struct {
visitor Visitor
results map[string]GroupResult
}
func (v GroupRbacVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
// ...
}
}
type IdentityVisitor struct {
visitor Visitor
results map[string]IdentityResult
}
func (v IdentityVisitor) Visit(fn VisitorFunc) error {
return v.visitor.Visit(func(info *Info, err error) error {
// ...
}
}
每次获得的结果存储在各自的结果中,并最终被收集和处理。每当添加新资源时,我只需要定义一个新的 Visitor
,编写相应的 visit
方法,并可能调整最终的显示逻辑。超级方便!
func FetchAll(c *cobra.Command, visitors []Visitor) error {
// ...
for _, visitor := range visitors {
v.Visit(func(*Info, error) error {
//...
})
}
// ...
}
结束
我们从未停止寻找编写更易于阅读、维护和扩展的代码的方法。我相信学习、理解和实践设计模式是让我们更接近目标的途径之一。
直到我研究了源代码,我才意识到我已经在实践中使用了设计模式。这就像你终于认识了一个每天都在见面的人一个外表变了的老朋友。这种认知让我们感觉更亲近——现在,我知道如何更好地使用 Go
中的设计模式。感谢你的阅读!
更多文档
请关注微信公众号 云原生CTO [1]
参考资料
参考地址: http://medium.com/geekculture/visitor-pattern-in-kubernetes-d1b58c6d5cd5
- Go 中的 Kubernetes GraphQL 动态查询
- 揭开云原生数据管理的神秘面纱:操作层级
- 云原生数据库,激活数智创新之力
- 企业考虑云原生分布式数据库的三个原因
- 一组用于 Kubernetes 的现代 Grafana 仪表板
- Go 中的构建器模式
- 让我们使用 Go 实现基本的服务发现
- 云原生下一步的发展方向是什么?
- 用更云原生的方式做诊断|大规模 K8s 集群诊断利器深度解析
- 多个维度分析k8s多集群管理工具,到底哪个才真正适合你
- 使用 Kube-capacity CLI 查看 Kubernetes 资源请求、限制和利用率
- 使用 Go 在 Kubernetes 中构建自己的准入控制器
- 云原生数仓如何破解大规模集群的关联查询性能问题?
- 云原生趋势下的迁移与灾备思考
- 2022 年不容错过的六大云原生趋势!
- 使用 Prometheus 监控 Golang 应用程序
- 云原生时代下的机遇与挑战 DevOps如何破局
- 如何在云原生格局中理解Kubernetes合规性和安全框架
- 设计云原生应用程序的15条基本原则
- 使用 Operator SDK 为 Pod 标签编写Controller