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命令分析。
- 用户发起请求
- 根据用户执行动作分发给处理对应动作的Cmd (Cmd是执行用户命令的实体)
- 解析用户命令
- 向Apiserver获取数据
- 整理返回为通用的数据集合
- 找到解释查询类型数据的句柄
- 使用具柄对整理出的数据集合进行打印输出
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
网友评论