当前位置: 七九推 > IT编程>软件设计>设计模式 > Visitor设计模式及发送pod创建请求实现详解

Visitor设计模式及发送pod创建请求实现详解

2022年11月27日 设计模式 我要评论
确立目标理解kubectl的核心实现之一:visitor design pattern访问者模式理解发送pod创建请求的细节visitor design pattern在设计模式中,访问者模式的定义为

确立目标

  • 理解kubectl的核心实现之一:visitor design pattern 访问者模式
  • 理解发送pod创建请求的细节

visitor design pattern

在设计模式中,访问者模式的定义为:

允许一个或者多个操作应用到对象上,解耦操作和对象本身

那么,对一个程序来说,具体的表现就是:

  • 表面:某个对象执行了一个方法
  • 内部:对象内部调用了多个方法,最后统一返回结果

举个例子,

  • 表面:调用一个查询订单的接口
  • 内部:先从缓存中查询,没查到再去热点数据库查询,还没查到则去归档数据库里查询

visitor

我们来看看kubeadm中的访问者模式的定义:

// visitor 即为访问者这个对象
type visitor interface {
	visit(visitorfunc) error
}
// visitorfunc对应这个对象的方法,也就是定义中的“操作”
type visitorfunc func(*info, error) error

基本的数据结构很简单,但从当前的数据结构来看,有两个问题:

  • 单个操作 可以直接调用visit方法,那多个操作如何实现呢?
  • 在应用多个操作时,如果出现了error,该退出还是继续应用下一个操作呢?

chained

以下内容在staging/src/k8s.io/cli-runtime/pkg/resource

visitorlist和eagervisitorlist是将多个对象聚合为一个对象

decoratedvisitor和continueonerrorvisitor是将多个方法聚合为一个方法

flattenlistvisitor和filteredvisitor是将对象抽象为多个底层对象,逐个调用方法

visitorlist

封装多个visitor为一个,出现错误就立刻中止并返回

// visitorlist定义为[]visitor,又实现了visit方法,也就是将多个[]visitor封装为一个visitor
type visitorlist []visitor
// 发生error就立刻返回,不继续遍历
func (l visitorlist) visit(fn visitorfunc) error {
	for i := range l {
		if err := l[i].visit(fn); err != nil {
			return err
		}
	}
	return nil
}

eagervisitorlist

封装多个visitor为一个,出现错误暂存下来,全部遍历完再聚合所有的错误并返回

// eagervisitorlist 也是将多个[]visitor封装为一个visitor
type eagervisitorlist []visitor
// 返回的错误暂存到[]error中,统一聚合
func (l eagervisitorlist) visit(fn visitorfunc) error {
	errs := []error(nil)
	for i := range l {
		if err := l[i].visit(func(info *info, err error) error {
			if err != nil {
				errs = append(errs, err)
				return nil
			}
			if err := fn(info, nil); err != nil {
				errs = append(errs, err)
			}
			return nil
		}); err != nil {
			errs = append(errs, err)
		}
	}
	return utilerrors.newaggregate(errs)
}

decoratedvisitor

这里借鉴了装饰器的设计模式,将一个visitor调用多个visitorfunc方法,封装为调用一个visitorfunc

// 装饰器visitor
type decoratedvisitor struct {
	visitor    visitor
	decorators []visitorfunc
}
// visitor遍历调用decorators中所有函数,有失败立即返回
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)
	})
}

continueonerrorvisitor

// 报错依旧继续
type continueonerrorvisitor struct {
	visitor
}
// 报错不立即返回,聚合所有错误后返回
func (v continueonerrorvisitor) visit(fn visitorfunc) error {
	errs := []error{}
	err := v.visitor.visit(func(info *info, err error) error {
		if err != nil {
			errs = append(errs, err)
			return nil
		}
		if err := fn(info, nil); err != nil {
			errs = append(errs, err)
		}
		return nil
	})
	if err != nil {
		errs = append(errs, err)
	}
	if len(errs) == 1 {
		return errs[0]
	}
	return utilerrors.newaggregate(errs)
}

flattenlistvisitor

将runtime.objecttyper解析成多个runtime.object,再转换为多个info,逐个调用visitorfunc

type flattenlistvisitor struct {
	visitor visitor
	typer   runtime.objecttyper
	mapper  *mapper
}

filteredvisitor

对info资源的检验

// 过滤的info
type filteredvisitor struct {
	visitor visitor
	filters []filterfunc
}
func (v filteredvisitor) visit(fn visitorfunc) error {
	return v.visitor.visit(func(info *info, err error) error {
		if err != nil {
			return err
		}
		for _, filter := range v.filters {
      // 检验info是否满足条件,出错则退出
			ok, err := filter(info, nil)
			if err != nil {
				return err
			}
			if !ok {
				return nil
			}
		}
		return fn(info, nil)
	})
}

implements

streamvisitor

最基础的visitor

type streamvisitor struct {
  // 读取信息的来源,实现了read这个接口,这个"流式"的概念,包括了常见的http、文件、标准输入等各类输入
	io.reader
	*mapper
	source string
	schema contentvalidator
}

filevisitor

文件的访问,包括标准输入,底层调用streamvisitor来访问

type filevisitor struct {
  // 表示文件路径或者stdin
	path string
	*streamvisitor
}

urlvisitor

http用get方法获取数据,底层也是复用streamvisitor

type urlvisitor struct {
	url *url.url
	*streamvisitor
  // 提供错误重试次数
	httpattemptcount int
}

kustomizevisitor

自定义的visitor,针对自定义的文件系统,customize 定制,是将c转成了k

type kustomizevisitor struct {
	path string
	*streamvisitor
}

发送创建pod请求的实现细节

kubectl是怎么向kube-apiserver发送请求的呢?

send request

// 在runcreate函数中,关键的发送函数
obj, err := resource.
				newhelper(info.client, info.mapping).
				dryrun(o.dryrunstrategy == cmdutil.dryrunserver).
				withfieldmanager(o.fieldmanager).
				create(info.namespace, true, info.object)
// 进入create函数,查看到
m.createresource(m.restclient, m.resource, namespace, obj, options)
// 对应的实现为
func (m *helper) createresource(c restclient, resource, namespace string, obj runtime.object, options *metav1.createoptions) (runtime.object, error) {
	return c.post().
		namespaceifscoped(namespace, m.namespacescoped).
		resource(resource).
		versionedparams(options, metav1.parametercodec).
		body(obj).
		do(context.todo()).
		get()
}
/*
到这里,我们发现了2个关键性的定义:
1. restclient 与kube-apiserver交互的restful风格的客户端 这个restclient是来自于builder时的传入,生成的result,底层是一个newclientwithoptions生成的
2. runtime.object 资源对象的抽象,包括pod/deployment/service等各类资源
3. 我们是传入的文件,是filevisitor来执行的,底层builder.mapper调用decode来生成obj(unstructured())
*/

restful client

我们先来看看,与kube-apiserver交互的client是怎么创建的

// 从传入参数来看,数据来源于info这个结构
r.visit(func(info *resource.info, err error) error{})
// 而info来源于前面的builder,前面部分都是将builder参数化,核心的生成为do函数
r := f.newbuilder().
		unstructured().
		schema(schema).
		continueonerror().
		namespaceparam(cmdnamespace).defaultnamespace().
		filenameparam(enforcenamespace, &o.filenameoptions).
		labelselectorparam(o.selector).
		flatten().
		do()
// 大致看一下这些函数,我们可以在unstructured()中看到getclient函数,其实这就是我们要找的函数
func (b *builder) getclient(gv schema.groupversion) (restclient, error) 
// 从返回值来看,client包括默认的rest client和配置选项
newclientwithoptions(client, b.requesttransforms...)
// 这个client会在kubernetes项目中大量出现,它是与kube-apiserver交互的核心组件,以后再深入。

object

object这个对象是怎么获取到的呢?因为我们的数据源是来自文件的,那么我们最直观的想法就是filevisitor

func (v *filevisitor) visit(fn visitorfunc) error {
	// 省略读取这块的代码,底层调用的是streamvisitor的逻辑
	return v.streamvisitor.visit(fn)
}
func (v *streamvisitor) visit(fn visitorfunc) error {
	d := yaml.newyamlorjsondecoder(v.reader, 4096)
	for {
		// 这里就是返回info的地方
		info, err := v.infofordata(ext.raw, v.source)
  }
}
// 再往下一层看,来到mapper层,也就是kubernetes的资源对象映射关系
func (m *mapper) infofordata(data []byte, source string) (*info, error){
  // 这里就是我们返回object的地方,其中gvk是group/version/kind的缩写,后续我们会涉及
  obj, gvk, err := m.decoder.decode(data, nil, nil)
}

这时,我们想回头去看,这个mapper是在什么时候被定义的?

// 在builder初始化中,我们就找到了
func (b *builder) unstructured() *builder {
	b.mapper = &mapper{
		localfn:      b.islocal,
		restmapperfn: b.restmapperfn,
		clientfn:     b.getclient,
    // 我们查找资源用到的是这个decoder
		decoder:      &metadatavalidatingdecoder{unstructured.unstructuredjsonscheme},
	}
	return b
}
// 逐层往下找,对应的decode方法的实现,就是对应的数据解析成data:
func (s unstructuredjsonscheme) decode(data []byte) (runtime.object, error) {
	// 细节暂时忽略
}

post

了解了rest clientobject的大致产生逻辑后,我们再回过头来看发送的方法

// restful接口风格中,post请求对应的就是create方法
c.post().
		namespaceifscoped(namespace, m.namespacescoped).
		resource(resource).
		versionedparams(options, metav1.parametercodec).
		body(obj).
		do(context.todo()). 
		get() 
// do方法,发送请求
err := r.request(ctx, func(req *http.request, resp *http.response) {
		result = r.transformresponse(resp, req)
	})
// get方法,获取请求的返回结果,用来打印状态
switch t := out.(type) {
	case *metav1.status:
		if t.status != metav1.statussuccess {
			return nil, errors.fromobject(t)
		}
	}

站在前人的肩膀上,向前辈致敬,respect!

summary

通过visitor的设计模式,从传入的参数中解析出内容,然后在factory进行newbuilder的时候进行配置实现restclient,mapper,obj的生成,do()拿到result,组装好post请求发送到apiserver。

到这里我们对kubectl的功能有了初步的了解,以下是关键内容所在:

命令行采用了cobra库,主要支持7个大类的命令;

掌握visitor设计模式,这个是kubectl实现各类资源对象的解析和校验的核心;

初步了解restclientobject这两个对象,它们是贯穿kubernetes的核心概念;

调用逻辑

  • cobra匹配子命令
  • 用visitor模式构建builder
  • 用restclient将object发送到kube-apiserver

以上就是visitor设计模式及发送pod创建请求实现详解的详细内容,更多关于visitor 发送pod创建请求的资料请关注七九推其它相关文章!

(0)
打赏 微信扫一扫 微信扫一扫

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2023  七九推 保留所有权利. 粤ICP备17035492号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com