K8S 源码探秘 之 命令行解析工具 cobra

一、引言

        最近开始研读 Kubernetes 源码,希望能借此更加深入地理解其运行机理!

        由于 Kubernetes 代码量很庞大,我将分模块分组件地去分析和理解,并把自己的发现共享出来。

        第一篇博客,先从简单的命令行解析工具 cobra 开始。

二、源码解析

        Kubernetes 中的组件(如 kubeadm、kubectl 等)都是命令行方式运行的,命令解析是重要一步

        在 Kubernetes 系统中,cobra 被广泛应用,因为其实在是非常的简单易用

        关于 cobra 的更多资料,可以参考其 GitHub 官网:https://github.com/spf13/cobra

        下面以 kubeadm 为例,看一下 cobra 是怎么被使用的吧

        首先,找到 kubeadm 的入口,cmd/kubeadm/kubeadm.go

package main

import (
	"fmt"
	"os"

	"k8s.io/kubernetes/cmd/kubeadm/app"
)

func main() {
	if err := app.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
	os.Exit(0)
}

        这里,直接调用了 app.Run() 方法,可见这只是个壳子,真正的逻辑在 k8s.io/kubernetes/cmd/kubeadm/app

        然后,我们跟踪到 cmd/kubeadm/app/kubeadm.go,找到其 Run 方法

package app

import (
	"flag"
	"os"

	_ "github.com/golang/glog"
	"github.com/spf13/pflag"

	utilflag "k8s.io/apiserver/pkg/util/flag"
	"k8s.io/kubernetes/cmd/kubeadm/app/cmd"
)

// Run creates and executes new kubeadm command
func Run() error {
	pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

	pflag.Set("logtostderr", "true")
	// We do not want these flags to show up in --help
	// These MarkHidden calls must be after the lines above
	pflag.CommandLine.MarkHidden("version")
	pflag.CommandLine.MarkHidden("google-json-key")
	pflag.CommandLine.MarkHidden("log-flush-frequency")
	pflag.CommandLine.MarkHidden("alsologtostderr")
	pflag.CommandLine.MarkHidden("log-backtrace-at")
	pflag.CommandLine.MarkHidden("log-dir")
	pflag.CommandLine.MarkHidden("logtostderr")
	pflag.CommandLine.MarkHidden("stderrthreshold")
	pflag.CommandLine.MarkHidden("vmodule")

	cmd := cmd.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr)
	return cmd.Execute()
}

        可以看到,Run 方法在设定了一系列的参数属性信息后,创建了一个 cmd 对象,并执行了其 Execute 方法

        经过分析,这里的 cmd 对象就是一个 cobra 命令对象,而 Execute 正是 cobra 提供的执行命令的方法  

        cobra 内部使用了 pflag 库,这里通过设置 pflag 属性,可以对 cobra 的运行产生作用

        pflag 也兼容 golang flag 库,这里通过 AddGoFlagSet(flag.CommandLine) 实现了对 golang flag 的兼容

        紧接着,我们跟踪到 NewKubeadmCommand 的函数实现, cmd/kubeadm/app/cmd/cmd.go

// NewKubeadmCommand returns cobra.Command to run kubeadm command
func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {
	var rootfsPath string

	cmds := &cobra.Command{
		Use:   "kubeadm",
		Short: "kubeadm: easily bootstrap a secure Kubernetes cluster",
		Long: dedent.Dedent(`
			kubeadm: easily bootstrap a secure Kubernetes cluster.

			    ┌──────────────────────────────────────────────────────────┐
			    │ KUBEADM IS CURRENTLY IN BETA                             │
			    │                                                          │
			    │ But please, try it out and give us feedback at:          │
			    │ https://github.com/kubernetes/kubeadm/issues             │
			    │ and at-mention @kubernetes/sig-cluster-lifecycle-bugs    │
			    │ or @kubernetes/sig-cluster-lifecycle-feature-requests    │
			    └──────────────────────────────────────────────────────────┘

			Example usage:

			    Create a two-machine cluster with one master (which controls the cluster),
			    and one node (where your workloads, like Pods and Deployments run).

			    ┌──────────────────────────────────────────────────────────┐
			    │ On the first machine:                                    │
			    ├──────────────────────────────────────────────────────────┤
			    │ master# kubeadm init                                     │
			    └──────────────────────────────────────────────────────────┘

			    ┌──────────────────────────────────────────────────────────┐
			    │ On the second machine:                                   │
			    ├──────────────────────────────────────────────────────────┤
			    │ node# kubeadm join <arguments-returned-from-init>        │
			    └──────────────────────────────────────────────────────────┘

			    You can then repeat the second step on as many other machines as you like.

		`),

		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if rootfsPath != "" {
				if err := kubeadmutil.Chroot(rootfsPath); err != nil {
					return err
				}
			}
			return nil
		},
	}

	cmds.ResetFlags()

	cmds.AddCommand(NewCmdCompletion(out, ""))
	cmds.AddCommand(NewCmdConfig(out))
	cmds.AddCommand(NewCmdInit(out))
	cmds.AddCommand(NewCmdJoin(out))
	cmds.AddCommand(NewCmdReset(in, out))
	cmds.AddCommand(NewCmdVersion(out))
	cmds.AddCommand(NewCmdToken(out, err))
	cmds.AddCommand(upgrade.NewCmdUpgrade(out))

	// Wrap not yet fully supported commands in an alpha subcommand
	experimentalCmd := &cobra.Command{
		Use:   "alpha",
		Short: "Experimental sub-commands not yet fully functional.",
	}
	experimentalCmd.AddCommand(phases.NewCmdPhase(out))
	cmds.AddCommand(experimentalCmd)

	AddKubeadmOtherFlags(cmds.PersistentFlags(), &rootfsPath)

	return cmds
}

         该函数首先构造了 kubeadm 的根命令对象 cmds(也就是 kubeadm 命令)

         然后依次将 kubeadm 的子命令(如 init、join)等通过 cmds.AddCommand()方法添加到 cmds 对象

         可见,cmd/kubeadm/app/kubeadm.go 中末尾执行的 cmd.Execute() 正是执行的这个 cmds 的 Execute() 方法

         各个子命令的构造大同小异,为了简单起见,我们看一下 NewCmdVersion 是如何实现的吧

         跟踪到 cmd/kubeadm/app/cmd/version.go

// NewCmdVersion provides the version information of kubeadm.
func NewCmdVersion(out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "version",
		Short: "Print the version of kubeadm",
		Run: func(cmd *cobra.Command, args []string) {
			err := RunVersion(out, cmd)
			kubeadmutil.CheckErr(err)
		},
	}
	cmd.Flags().StringP("output", "o", "", "Output format; available options are 'yaml', 'json' and 'short'")
	return cmd
}

         我们看到,NewCmdVersion 函数同样返回了一个 cobra Command 对象,

         而与之前看到的根命令 kubeadm不同的是

         这里 version 子命令的定义中包含了运行实体 Run 函数的具体定义,

         这表明 version 命令不再是父级命令了,它能真正被执行

         至于 version 命令可以识别的参数,可以通过 cmd.Falgs() 设定

         这里把输入的参数直接绑定到 output 上了,使用的时候可以通过 cmd.Flags().GetString(“output”) 获取

三、源码分析总结

        整体而言,kubernetes 命令的执行是通过组合 cobra command 生成父子命令树,

       最终命令的执行是在树的叶节点上通过 Run: func(cmd *cobra.Command, args []string){...} 定义的     

       命令参数通过 cmd.Flags() 设置

       主函数 main 通过调起根命令的 Execute 激活整个命令解析和执行过程

  

  四、实验验证

          实验代码 GitHub 地址:https://github.com/SataQiu/k8s-learn/tree/master/cobra-example

package main

import (
	"fmt"
	"github.com/spf13/cobra"
)

func NewRootCommand() *cobra.Command {
	cmds := &cobra.Command{
		Use:   "cobra-example",
		Short: "cobra-example: my first cmd tool based on cobra.",
	}
	cmds.AddCommand(NewSubCommandOfHello())
	cmds.AddCommand(NewSubCommandOfBye())
	return cmds
}

func NewSubCommandOfHello() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "hello",
		Short: "Say hello.",
		Run: func(cmd *cobra.Command, args []string) {
			name, _ := cmd.Flags().GetString("name")
			fmt.Println("Welcome " + name)
		},
	}
	cmd.Flags().StringP("name", "n", "", "Name: input your name.")
	return cmd
}

func NewSubCommandOfBye() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "bye",
		Short: "Say bye.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Bye!")
		},
	}
	return cmd
}
func main() {
	cmd := NewRootCommand()
	cmd.Execute()
}

          这里,我们首先定义一个根命令 cobra-example,然后添加两个子命令 hello 和 bye,其中 hello 可以接受 name 参数

          试验一下,执行编译后的二进制文件

           

           执行子命令

           

           

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值