Kubernetes 源码分析之 Kubectl

1. 概述

本文以Kubernetes 1.9 进行分析。

Kubernetes 是采用微服务以集群的方式运行,并为用户提供服务。而与外界交互则是通过Apiserver模块向外提供接口支持。
kubectl用户与Kubernetes交互的命令行工具。用户使用kubectl工具调用Apiserver的接口来与Kubernetes服务进行交互。

2. 结构分析

Kubectl 依赖于cobra包构建命令行支持,该包是支持通用的命令行构建库。

如下所示

  • Cmds是kubectl中的命令集合,所有命令都会整理在里面。
  • Cmd 是命令的实体,其中主要是具体执行用户命令。每个cmd负责一个命令执行类型(describe,get...)。
  • Builder 是cmd执行操作时的辅助工具,主要是负责封装与Apiserver交互的底层操作,和将Apiserver的返回数据转化为统一数据结构。
Cmds(命令集合)<---Cmd(命令obj)
       |          |
       |          |
       |          | 
       |        Builder
       |          |
       |          |  
       |----------Cmd(命令obj)
       

3. 流程分析

Kubectl 的执行流程分析以describe命令分析。

  1. 用户发起请求
  2. 根据用户执行动作分发给处理对应动作的Cmd (Cmd是执行用户命令的实体)
  3. 解析用户命令
  4. 向Apiserver获取数据
  5. 整理返回为通用的数据集合
  6. 找到解释查询类型数据的句柄
  7. 使用具柄对整理出的数据集合进行打印输出

4. 源码分析

以调用下面的命令 做源码分析

kubectl describe node node1

4.1 操作类型分发命令

首先会根据执行的动作, describe, get, delete…等进行匹配,分发给处理对应操作的cmd。

如下, NewKubectlCommand 方法中cobra会根据命令动作将请求分配给describe注册的cmd。

groups := templates.CommandGroups{
        //...
        {
            Message: "Troubleshooting and Debugging Commands:",
            Commands: []*cobra.Command{
                NewCmdDescribe(f, out, err),    //<------describe操作的cmd
                NewCmdLogs(f, out),
                NewCmdAttach(f, in, out, err),
                NewCmdExec(f, in, out, err),
                NewCmdPortForward(f, out, err),
                NewCmdProxy(f, out),
                NewCmdCp(f, out, err),
                auth.NewCmdAuth(f, out, err),
            },
        },
        {
            Message: "Advanced Commands:",
            Commands: []*cobra.Command{
                NewCmdApply("kubectl", f, out, err),
                NewCmdPatch(f, out),
                NewCmdReplace(f, out),
                NewCmdConvert(f, out),
            },
        },
        // ...
    }
    groups.Add(cmds)

4.2 获取用户输入

Cmd会对获取用户输入数据, 并检查正确性然后使用Run函数处理。

func NewCmdDescribe(f cmdutil.Factory, out, cmdErr io.Writer) *cobra.Command {
    options := &resource.FilenameOptions{}
    describerSettings := &printers.DescriberSettings{}

    validArgs := printersinternal.DescribableResources()
    argAliases := kubectl.ResourceAliases(validArgs)

    cmd := &cobra.Command{
        Use:     "describe (-f FILENAME | TYPE [NAME_PREFIX | -l label] | TYPE/NAME)",
        Short:   i18n.T("Show details of a specific resource or group of resources"),
        Long:    describeLong + "\n\n" + cmdutil.ValidResourceTypeList(f),
        Example: describeExample,
        Run: func(cmd *cobra.Command, args []string) {   // <------处理回调函数
            err := RunDescribe(f, out, cmdErr, cmd, args, options, describerSettings)
            cmdutil.CheckErr(err)
        },
        ValidArgs:  validArgs,     //<-----------------合法性检查 
        ArgAliases: argAliases,
    }
    usage := "containing the resource to describe"
    cmdutil.AddFilenameOptionFlags(cmd, options, usage)
    
    // 下面主要是输入参数检查 
    
    cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
    cmd.Flags().Bool("all-namespaces", false, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
    cmd.Flags().BoolVar(&describerSettings.ShowEvents, "show-events", true, "If true, display events related to the described object.")
    cmdutil.AddInclude3rdPartyFlags(cmd)
    cmdutil.AddIncludeUninitializedFlag(cmd)
    return cmd
}

4.3 执行命令

如下, 在 RunDescribe 中时对该命令的具体处理

  • Builder(), Unstructured(), ContinueOnError().
    NamespaceParam(), FilenameParam(), LabelSelectorParam() ... Flatten() 的链式调用流程主要是为执行命令做准备。
  • Do() 函数是注册具体向Apiserver请求数据,和讲返回数据转化为通用结构的方法。
  • 最后的 describer.Describe() 函数是将提取出的返回数据 打印出来做可视化接口。
func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions, describerSettings *printers.DescriberSettings) error {
    
    // ...

    // include the uninitialized objects by default
    // unless user explicitly set --include-uninitialized=false
    includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, true)
    r := f.NewBuilder().
        Unstructured().
        ContinueOnError().
        NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
        FilenameParam(enforceNamespace, options).
        LabelSelectorParam(selector).    // 设置用户的标签选择
        IncludeUninitialized(includeUninitialized).
        ResourceTypeOrNameArgs(true, args...). // 提取用户选择操作的对象类型
        Flatten().                             //决定以何种方式从K8s的返回数据中提取信息                     
        Do()                                   //执行命令获取数据
    
    // ...
    
    infos, err := r.Infos()                     
    if err != nil {
        if apierrors.IsNotFound(err) && len(args) == 2 {
            return DescribeMatchingResources(f, cmdNamespace, args[0], args[1], describerSettings, out, err)
        }
        allErrs = append(allErrs, err)
    }

    errs := sets.NewString()
    first := true
    for _, info := range infos {
        mapping := info.ResourceMapping()
        describer, err := f.Describer(mapping)
        if err != nil {
            if errs.Has(err.Error()) {
                continue
            }
            allErrs = append(allErrs, err)
            errs.Insert(err.Error())
            continue
        }
        // 下面通过describe 方法将提取到的数据 打印出来
        s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
        if err != nil {
            if errs.Has(err.Error()) {
                continue
            }
            allErrs = append(allErrs, err)
            errs.Insert(err.Error())
            continue
        }
        if first {
            first = false
            fmt.Fprint(out, s)
        } else {
            fmt.Fprintf(out, "\n\n%s", s)
        }
    }

    return utilerrors.NewAggregate(allErrs)
}

4.4 获取数据

下面具体分析获取数据的流程,获取数据包括从Apiserver请求数据以及从返回信息中提取有用数据两个操作。

RetrieveLazy 中注册了从Apiserver获取数据的操作。
NewDecoratedVisitor 中注册了从获取到的数据结构中转化出通用数据的方法。

// inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
// for further iteration.
func (b *Builder) Do() *Result {
    r := b.visitorResult()
    //... 
    
    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 {
        //注册从Apiserver获取数据的方法
        helpers = append(helpers, RetrieveLazy) 
    }
    //注册从返回数据中提取信息的方法
    r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
    if b.continueOnError {
        r.visitor = ContinueOnErrorVisitor{r.visitor}
    }
    return r
}

如下所示 RetrieveLazy中有获取数据的操作

// RetrieveLazy updates the object if it has not been loaded yet.
func RetrieveLazy(info *Info, err error) error {
    if err != nil {
        return err
    }
    if info.Object == nil {
        return info.Get()     //从Apiserver获取数据
    }
    return nil
}

而 NewDecoratedVisitor 方法注册了数据处理的关键函数 Visit, 这个函数可以使用户可以将来自Apiserver的数据转化为通用数据集合。

// NewDecoratedVisitor will create a visitor that invokes the provided visitor functions before
// the user supplied visitor function is invoked, giving them the opportunity to mutate the Info
// object or terminate early with an error.
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
    if len(fn) == 0 {
        return v
    }
    return DecoratedVisitor{v, fn}
}

// 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)
    })
}

4.5 打印数据

打印提取到的数据主要是调用注册的describe方法,会根据用户的请求如下获取对应的describe

describer, err := f.Describer(mapping)

Describe 集合中注册了 对K8s各种数据的打印方法(针对visit转化后的通用数据)

func init() {
    d := &Describers{}
    err := d.Add(
        describeLimitRange,
        describeQuota,
        describePod,
        describeService,
        describeReplicationController,
        describeDaemonSet,
        describeNode,              //打印节点
        describeNamespace,
    )
    if err != nil {
        glog.Fatalf("Cannot register describers: %v", err)
    }
    DefaultObjectDescriber = d
}

使用获取到的对应的Describe作打印

//遍历整理出的返回信息
for _, info := range infos {
        // 执行打印操作
        s, err := describer.Describe(info.Namespace, info.Name, *describerSettings)
        // ...
    }

5. 更多

本文是作为Kubernetes源码分析的一部分,转载请注明出处。
勘误可直接简书或者邮件至 xiyanxiyan10@hotmail.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258