0. 前言

kubectl 看了也有一段时间,期间写了两篇设计模式的文章,是时候对 kubectl 做个回顾了。

1. kubectl 入口:Cobra

kubectlkubernetes 的命令行工具,通过 kubectl 实现资源的增删改查。kubectl 通过 client-gokube-apiserver 进行交互,其背后封装了 https,配置文件为 kubeconfig

kubectl 的命令行框架为 Cobra。首先,将外部参数,配置统统赋给 KubectlOptions 对象:

// NewDefaultKubectlCommand creates the `kubectl` command with default arguments
func NewDefaultKubectlCommand() *cobra.Command {
return NewDefaultKubectlCommandWithArgs(KubectlOptions{
PluginHandler: NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes),
Arguments: os.Args,
ConfigFlags: defaultConfigFlags,
IOStreams: genericiooptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr},
})
}

该对象包含四个属性:

  • PluginHandler: PluginHandler is capable of parsing command line arguments and performing executable filename lookups to search for valid plugin files, and execute found plugins.
  • Arguments: os.Args;
  • ConfigFlags: ConfigFlags composes the set of values necessary for obtaining a REST client config;
  • IOStreams: IOStreams provides the standard names for iostreams. This is useful for embedding and for unit testing. Inconsistent and different names make it hard to read and review code;

接着通过 ConfigFlags 属性创建工厂,工厂提供了与 kube-apiserver 的交互方式,以及验证资源对象等方法:

kubeConfigFlags := o.ConfigFlags
if kubeConfigFlags == nil {
kubeConfigFlags = defaultConfigFlags
}
kubeConfigFlags.AddFlags(flags)
matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
matchVersionKubeConfigFlags.AddFlags(flags) f := cmdutil.NewFactory(matchVersionKubeConfigFlags)

1.1 创建命令

这里以创建 get 为例 getCmd := get.NewCmdGet("kubectl", f, o.IOStreams),工厂 fIOStreams 作为参数传给 get 包的 NewCmdGet 函数,在函数内实现 get 命令的创建。

创建 GetOptions 对象,该对象包含和 get 命令相关的输入。

func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
o := NewGetOptions(parent, streams) cmd := &cobra.Command{
...
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run(f, args))
},
SuggestFor: []string{"list", "ps"},
}
...

CobraRun 函数实现运行 get 命令的行为。

首先,o.Complete(f, cmd, args) 补全 GetOptions 对象的输入:

func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
...

需要注意的是,f.ToRawKubeConfigLoader().Namespace() 调用工厂的 ToRawKubeConfigLoader() 方法解析 kubeconfig 中的配置,然后调用 Namespace() 方法将 kubeconfig 中定义的 namespace 解析出来,解析 kubeconfig 的过程是反序列化 kubeconfig 文件的过程。这一过程太长,这里就不多做介绍了。

完成了输入补全,在 o.Validate() 中对输入做验证。最后,通过 o.Run(f, args) 运行命令:

func (o *GetOptions) Run(f cmdutil.Factory, args []string) error {
...
r := f.NewBuilder().
Unstructured().
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
LabelSelectorParam(o.LabelSelector).
FieldSelectorParam(o.FieldSelector).
Subresource(o.Subresource).
RequestChunksOf(chunkSize).
ResourceTypeOrNameArgs(true, args...).
ContinueOnError().
Latest().
Flatten().
TransformRequests(o.transformRequests).
Do()
...

这里涉及到 建造者设计模式。通过 f 创建建造者,建造者通过一系列方法补全自身属性,在 Do 方法中根据这些属性建造 resource.Result 对象:

func (b *Builder) Do() *Result {
r := b.visitorResult()
...
return r
}

Do 方法值得重点关注,其实现了 访问者设计模式,且是嵌套的访问者,访问的对象为 info 结构体。

首先,b.visitorResult() 方法通过 visit 多个 item 创建 resource.Result。这里以 visit resource name 为例:

func (b *Builder) visitByName() *Result {
result := &Result{
singleItemImplied: len(b.names) == 1,
targetsSingleItems: true,
} client, err := b.getClient(mapping.GroupVersionKind.GroupVersion())
if err != nil {
result.err = err
return result
}
...
visitors := []Visitor{}
for _, name := range b.names {
info := &Info{
Client: client,
Mapping: mapping,
Namespace: selectorNamespace,
Name: name,
Subresource: b.subresource,
}
visitors = append(visitors, info)
}
result.visitor = VisitorList(visitors)
result.sources = visitors
return result
}

visitByName() 方法内创建了一组 info 对象,其中保存了 resource 的信息。该对象保存在存储访问者 Visitorvisitors 列表,并赋值给 result.visitorresult.sources

关于 result.visitor 要注意的一点是,其中的 VisitorList 也实现了 Visit 方法,它是横向的调用 info, info 是主体,fn 是这里的访问者:

type VisitorList []Visitor

// Visit implements Visitor
func (l VisitorList) Visit(fn VisitorFunc) error {
for i := range l {
if err := l[i].Visit(fn); err != nil {
return err
}
}
return nil
}

得到 resource.Result 之后,通过各个访问者访问 info 资源:

func (b *Builder) Do() *Result {
r := b.visitorResult() if b.flatten {
r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, b.mapper)
}
helpers := []VisitorFunc{}
if b.defaultNamespace {
helpers = append(helpers, SetNamespace(b.namespace))
}
if b.requireNamespace {
helpers = append(helpers, RequireNamespace(b.namespace))
}
helpers = append(helpers, FilterNamespace)
if b.requireObject {
helpers = append(helpers, RetrieveLazy)
}
if b.continueOnError {
r.visitor = ContinueOnErrorVisitor{Visitor: r.visitor}
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
return r
}

其中,FlattenListVisitor, ContinueOnErrorVisitorDecoratedVisitor 是纵向的访问者嵌套关系,SetNamespace, RequireNamespaceRetrieveLazy 是横向的嵌套关系。

这里关于访问者模式和访问者嵌套的调用顺序就不过多介绍,有兴趣的话可以参考 浅析访问者模式

Do 方法返回 Result,接着调用 infos, err := r.Infos() 方法实现 resource 的访问:

func (r *Result) Infos() ([]*Info, error) {
...
infos := []*Info{}
err := r.visitor.Visit(func(info *Info, err error) error {
if err != nil {
return err
}
infos = append(infos, info)
return nil
})
return infos, err
}

这里 infos 是一组 info 对象访问 kube-apiserver 获得的返回结果集合。那么,哪里有定义访问 kube-apiserver 的地方呢?

答案在 RetrieveLazy 访问者:

func RetrieveLazy(info *Info, err error) error {
if err != nil {
return err
}
if info.Object == nil {
return info.Get()
}
return nil
} // Get retrieves the object from the Namespace and Name fields
func (i *Info) Get() (err error) {
obj, err := NewHelper(i.Client, i.Mapping).WithSubresource(i.Subresource).Get(i.Namespace, i.Name)
if err != nil {
if errors.IsNotFound(err) && len(i.Namespace) > 0 && i.Namespace != metav1.NamespaceDefault && i.Namespace != metav1.NamespaceAll {
err2 := i.Client.Get().AbsPath("api", "v1", "namespaces", i.Namespace).Do(context.TODO()).Error()
if err2 != nil && errors.IsNotFound(err2) {
return err2
}
}
return err
}
i.Object = obj
i.ResourceVersion, _ = metadataAccessor.ResourceVersion(obj)
return nil
} func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
req := m.RESTClient.Get().
NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource).
Name(name).
SubResource(m.Subresource)
return req.Do(context.TODO()).Get()
}

RetrieveLazy 中定义如果 info.Object 没有信息,则调用 infoGet 方法,在 Get 方法中根据 i.Clienti.Mapping 创建 Helper,通过 HelperGet 方法通过 client-go 实现同 kube-apiserver 的交互,获得 info 的资源信息。

1.2 UML 交互图

通过上例分析给出 UML 交互图如下:


Kubernetes: Kubectl 源码分析的更多相关文章

  1. Kubernetes client-go 源码分析 - Reflector

    概述入口 - Reflector.Run()核心 - Reflector.ListAndWatch()Reflector.watchHandler()NewReflector()小结 概述 源码版本: ...

  2. Kubernetes Deployment 源码分析(二)

    概述startDeploymentController 入口逻辑DeploymentController 对象DeploymentController 类型定义DeploymentController ...

  3. client-go客户端自定义开发Kubernetes及源码分析

    介绍 client-go 是一种能够与 Kubernetes 集群通信的客户端,通过它可以对 Kubernetes 集群中各资源类型进行 CRUD 操作,它有三大 client 类,分别为:Clien ...

  4. Kubernetes Deployment 源码分析(一)

    概述Deployment 基础创建 DeploymentReplicaSet滚动更新失败回滚历史版本回滚其他特性小结 概述 Deployment 是最常用的 Kubernetes 原生 Workloa ...

  5. Kubernetes client-go 源码分析 - ListWatcher

    概述ListWatch 对象的创建GetterListWatchList() & Watch() 概述 源码版本信息 Project: kubernetes Branch: master La ...

  6. Kubernetes client-go Informer 源码分析

    概述ControllerController 的初始化Controller 的启动processLoopHandleDeltas()SharedIndexInformersharedIndexerIn ...

  7. Kubernetes client-go workqueue 源码分析

    概述Queue接口和结构体setAdd()Get()Done()DelayingQueue接口和结构体waitForNewDelayingQueuewaitingLoop()AddAfter()Rat ...

  8. Kubernetes client-go DeltaFIFO 源码分析

    概述Queue 接口DeltaFIFO元素增删改 - queueActionLocked()Pop()Replace() 概述 源码版本信息 Project: kubernetes Branch: m ...

  9. kubernetes垃圾回收器GarbageCollector Controller源码分析(二)

    kubernetes版本:1.13.2 接上一节:kubernetes垃圾回收器GarbageCollector Controller源码分析(一) 主要步骤 GarbageCollector Con ...

  10. Kubernetes client-go Indexer / ThreadSafeStore 源码分析

    Kubernetes client-go Indexer / ThreadSafeStore 源码分析   请阅读原文:原文地址   Contents 概述 Indexer 接口 ThreadSafe ...

随机推荐

  1. spring事务传播的Propagation.REQUIRES_NEW以及NEVER MANDATORY验证,及其失效的诡异问题

    NEVER 不使用事务,如果当前事务存在,则抛出异常 验证: @Service public class PrService { @Autowired PrDao dao; @Transactiona ...

  2. 2022-09-05:作为国王的统治者,你有一支巫师军队听你指挥。 :给你一个下标从 0 开始的整数数组 strength , 其中 strength[i] 表示第 i 位巫师的力量值。 对于连续的一

    2022-09-05:作为国王的统治者,你有一支巫师军队听你指挥. :给你一个下标从 0 开始的整数数组 strength , 其中 strength[i] 表示第 i 位巫师的力量值. 对于连续的一 ...

  3. 2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定“认为”关系有传递性,所以A也认为C是红人, 给定一张有向图,方式是给定M个有

    2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定"认为"关系有传递性,所以A也认为C是红 ...

  4. 2021-02-25:给定一个正数数组arr,请把arr中所有的数分成两个集合。如果arr长度为偶数,两个集合包含数的个数要一样多;如果arr长度为奇数,两个集合包含数的个数必须只差一个。请尽量让两个集合的累加和接近,返回最接近的情况下,较小集合的累加和。

    2021-02-25:给定一个正数数组arr,请把arr中所有的数分成两个集合.如果arr长度为偶数,两个集合包含数的个数要一样多:如果arr长度为奇数,两个集合包含数的个数必须只差一个.请尽量让两个 ...

  5. 2021-03-29:无序数组arr,子数组-1和1的数量一样多,请问最长子数组的长度是多少?

    2021-03-29:无序数组arr,子数组-1和1的数量一样多,请问最长子数组的长度是多少? 福大大 答案2021-03-29: [1, -1, 2, 3, -4, -1, 9]变成[1, -1, ...

  6. 2021-12-18:找到字符串中所有字母异位词。 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成

    2021-12-18:找到字符串中所有字母异位词. 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引.不考虑答案输出的顺序. 异位词 指由相同字母重排列形成 ...

  7. 2021-11-08:扁平化嵌套列表迭代器。给你一个嵌套的整数列表 nestedList 。每个元素要么是一个整数,要么是一个列表;该列表的元素也可能是整数或者是其他列表。请你实现一个迭代器将其扁平化

    2021-11-08:扁平化嵌套列表迭代器.给你一个嵌套的整数列表 nestedList .每个元素要么是一个整数,要么是一个列表:该列表的元素也可能是整数或者是其他列表.请你实现一个迭代器将其扁平化 ...

  8. MVC 三层架构案例详细讲解

    MVC 三层架构案例详细讲解 @ 目录 MVC 三层架构案例详细讲解 每博一文案 1. MVC 概述 2. MVC设计思想 3. 三层架构 4. MVC 与 三层架构的关系: 5. 案例举例:用户账户 ...

  9. 【GiraKoo】面向对象开发系列之【为什么要用面向对象】

    开源项目:https://girakoo.com/ 问答 为什么要有面向对象开发? 面向过程开发的C语言,往往有以下几个问题: 不同的开发人员需要使用功能完全相同,或者大部分相同的函数.如果某个算法存 ...

  10. 使用增强版 singleflight 合并事件推送,效果炸裂!

    hello,大家好啊,我是小楼. 最近在工作中对 Go 的 singleflight 包做了下增强,解决了一个性能问题,这里记录下,希望对你也有所帮助. singleflight 是什么 single ...