Kubernetes 源码分析之 Kubectl

1. 概述

本文以Kubernetes 1.9 进行分析。

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

2. 结构分析

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

如下所示

  • mds是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

首先会根据执行的动作, 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)
        // ...
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值