确立目标
- 理解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 client
和object
的大致产生逻辑后,我们再回过头来看发送的方法
// 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实现各类资源对象的解析和校验的核心;
初步了解restclient
和object
这两个对象,它们是贯穿kubernetes的核心概念;
调用逻辑
- cobra匹配子命令
- 用visitor模式构建builder
- 用restclient将object发送到kube-apiserver
以上就是kubernetes visitor设计模式及发送pod创建请求解析的详细内容,更多关于kubernetes visitor发送pod请求的资料请关注代码网其它相关文章!
发表评论