从k8s当中学习gocli脚手架开发利器-cobra
- 互联网
- 2025-08-05 10:57:01

1.前言
大部分的项目都会引入cobra来作为项目的命令行解析工具,k8s当中大量使用cobra,学习借鉴一下k8s当中是如何使用cobra,在此记录一下。
2.cobra简介cobra是一个提供简单接口来创建强大的现代CLI界面的库类似git & git tools,cobra也是一个应用程序,它会生成你的应用程序的脚手架来快速开发基于cobra的应用程序 cobra提供:
简单的基于子命令的命令行:app server、app fetch 等等
完全符合POSIX的标志(包含短版本和长版本)
嵌套子命令
全局、本地和级联的标志
使用 cobra init appname和cobra add cmdname 可以很容易生成应用程序和命令
智能提示(app srver... did you mean app server?)
自动生成命令和标志
自动识别 -h --help 等等为help标志
为应用程序自动shell补全(bash、zsh、fish、powershell)
为应用程序自动生成手册
命令别名
灵活定义帮助、用法等等
可选的与viper的紧密集成
3.分析kubernetes当中的组件都是大量使用cobra,这里挑选kubeadm的cora实现来模仿分析。
从入口开始
// cmd/kubeadm/kubeadm.go package main import ( "k8s.io/kubernetes/cmd/kubeadm/app" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" ) func main() { kubeadmutil.CheckErr(app.Run()) }此处直接调用了 app.Run() ,对于golang的工程而言,在cmd的第一层启动目录往往是越薄越好【1】,所以此处包装了将真正的启动逻辑封装到到**app.Run()**当中。
app.Run() 的调用位置在cmd/kubeadm/app/kubeadm.go
package app import ( "flag" "os" "github.com/spf13/pflag" cliflag "k8s.io/component-base/cli/flag" "k8s.io/klog/v2" "k8s.io/kubernetes/cmd/kubeadm/app/cmd" ) // Run creates and executes new kubeadm command func Run() error { klog.InitFlags(nil) pflag.CommandLine.SetNormalizeFunc(cliflag.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("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对象,并执行cmd对象的Execute(),这里的cmd对象就是一个cobra命令对象,而Execute是cobra提供执行命令的方法,cobra内部使用pflag库,通过设置 pflag 属性,可以对 cobra 的运行产生作用。pflag 也兼容 golang flag 库,此处通过 AddGoFlagSet(flag.CommandLine) 实现了对 golang flag 的兼容。
cobra对象如何生成的,是我们需要关心的,**NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr)**的实现在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 │ │ │ │ Please give us feedback at: │ │ https://github.com/kubernetes/kubeadm/issues │ └──────────────────────────────────────────────────────────┘ Example usage: Create a two-machine cluster with one control-plane node (which controls the cluster), and one worker node (where your workloads, like Pods and Deployments run). ┌──────────────────────────────────────────────────────────┐ │ On the first machine: │ ├──────────────────────────────────────────────────────────┤ │ control-plane# kubeadm init │ └──────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────┐ │ On the second machine: │ ├──────────────────────────────────────────────────────────┤ │ worker# kubeadm join <arguments-returned-from-init> │ └──────────────────────────────────────────────────────────┘ You can then repeat the second step on as many other machines as you like. `), SilenceErrors: true, SilenceUsage: true, 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(newCmdCertsUtility(out)) cmds.AddCommand(newCmdCompletion(out, "")) cmds.AddCommand(newCmdConfig(out)) cmds.AddCommand(newCmdInit(out, nil)) cmds.AddCommand(newCmdJoin(out, nil)) cmds.AddCommand(newCmdReset(in, out, nil)) cmds.AddCommand(newCmdVersion(out)) cmds.AddCommand(newCmdToken(out, err)) cmds.AddCommand(upgrade.NewCmdUpgrade(out)) cmds.AddCommand(alpha.NewCmdAlpha()) options.AddKubeadmOtherFlags(cmds.PersistentFlags(), &rootfsPath) cmds.AddCommand(newCmdKubeConfigUtility(out)) return cmds }NewKubeadmCommand() 首先构造了 kubeadm的根命令对象cmds(也就是 kubeadm 命令),然后依次将kubeadm的子命令(例如init、join、version等命令)通过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", RunE: func(cmd *cobra.Command, args []string) error { return RunVersion(out, cmd) }, Args: cobra.NoArgs, } cmd.Flags().StringP("output", "o", "", "Output format; available options are 'yaml', 'json' and 'short'") return cmd } 3.依样画葫芦 3.1目录结构 ➜ cobra_project tree -CL 5 . ├── cmd │ ├── app │ │ ├── cloud.go │ │ └── cmd │ │ ├── cmd.go │ │ ├── util │ │ │ └── chroot_unix.go │ │ └── version.go │ └── cloud.go ├── go.mod └── go.sum 4 directories, 7 files 3.2效果展示 ➜ cobra_project go run cmd/cloud.go version cloud version: "1.5.0" ➜ cobra_project go run cmd/cloud.go version -h Print the version of cloud Usage: cloud version [flags] Flags: -h, --help help for version -o, --output string Output format; available options are 'yaml', 'json' and 'short' ➜ cobra_project go run cmd/cloud.go version -o json { "clientVersion": "1.5.0" } ➜ cobra_project go run cmd/cloud.go ┌──────────────────────────────────────────────────────────┐ │ This is cloud tools description │ │ │ └──────────────────────────────────────────────────────────┘ Usage: cloud [command] Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command version Print the version of cloud Flags: -h, --help help for cloud Use "cloud [command] --help" for more information about a command 3.3实战 mkdir cobra_project/cmd/cloud.go文件
package main import ( "cobra_project/cmd/app" "fmt" "os" ) func main() { if err := app.Run(); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } os.Exit(0) }/cmd/app/cloud.go文件
package app import ( "cobra_project/cmd/app/cmd" "os" ) func Run() error { cmd := cmd.NewCloudCommand(os.Stdin, os.Stdout, os.Stderr) return cmd.Execute() }/cmd/app/cmd/cmd.go文件
package cmd import ( cloudutil "cobra_project/cmd/app/cmd/util" "github.com/spf13/cobra" "io" "regexp" "strings" ) // NewCloudCommand returns cobra.Command to run kubeadm command func NewCloudCommand(in io.Reader, out, err io.Writer) *cobra.Command { var rootfsPath string cmds := &cobra.Command{ Use: "cloud", Short: "cloud is powerful cloud native tool", Long: Dedent(` ┌──────────────────────────────────────────────────────────┐ │ This is cloud tools description │ │ │ └──────────────────────────────────────────────────────────┘ `), SilenceErrors: true, SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if rootfsPath != "" { if err := cloudutil.Chroot(rootfsPath); err != nil { return err } } return nil }, } cmds.AddCommand(newCmdVersion(out)) return cmds } var ( whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$") leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])") ) func Dedent(text string) string { var margin string text = whitespaceOnly.ReplaceAllString(text, "") indents := leadingWhitespace.FindAllStringSubmatch(text, -1) // Look for the longest leading string of spaces and tabs common to all // lines. for i, indent := range indents { if i == 0 { margin = indent[1] } else if strings.HasPrefix(indent[1], margin) { // Current line more deeply indented than previous winner: // no change (previous winner is still on top). continue } else if strings.HasPrefix(margin, indent[1]) { // Current line consistent with and no deeper than previous winner: // it's the new winner. margin = indent[1] } else { // Current line and previous winner have no common whitespace: // there is no margin. margin = "" break } } if margin != "" { text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "") } return text }/cmd/app/cmd/version文件
package cmd import ( "encoding/json" "fmt" "github.com/pkg/errors" "github.com/spf13/cobra" "gopkg.in/yaml.v2" "io" ) // Version provides the version information of cloud type Version struct { ClientVersion string `json:"clientVersion"` } func newCmdVersion(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print the version of cloud", RunE: func(cmd *cobra.Command, args []string) error { return RunVersion(out, cmd) }, Args: cobra.NoArgs, } cmd.Flags().StringP("output", "o", "", "Output format; available options are 'yaml', 'json' and 'short'") return cmd } // RunVersion provides the version information of kubeadm in format depending on arguments // specified in cobra.Command. func RunVersion(out io.Writer, cmd *cobra.Command) error { v := Version{ ClientVersion: "1.5.0", } const flag = "output" of, err := cmd.Flags().GetString(flag) if err != nil { return errors.Wrapf(err, "error accessing flag %s for command %s", flag, cmd.Name()) } switch of { case "": fmt.Fprintf(out, "cloud version: %#v\n", v.ClientVersion) case "short": fmt.Fprintf(out, "%s\n", v.ClientVersion) case "yaml": y, err := yaml.Marshal(&v) if err != nil { return err } fmt.Fprintln(out, string(y)) case "json": y, err := json.MarshalIndent(&v, "", " ") if err != nil { return err } fmt.Fprintln(out, string(y)) default: return errors.Errorf("invalid output format: %s", of) } return nil }/cmd/app/cmd/util/chroot_unix.go文件
package util import ( "os" "path/filepath" "syscall" "github.com/pkg/errors" ) // Chroot chroot()s to the new path. // NB: All file paths after this call are effectively relative to // `rootfs` func Chroot(rootfs string) error { if err := syscall.Chroot(rootfs); err != nil { return errors.Wrapf(err, "unable to chroot to %s", rootfs) } root := filepath.FromSlash("/") if err := os.Chdir(root); err != nil { return errors.Wrapf(err, "unable to chdir to %s", root) } return nil } 4.总结对于云开发者而言,开发的时候可以多借鉴cncf项目当中的一些优秀的用法,笔者在这块相对比较薄弱,最近也在恶补这块的习惯,共勉。
【1】cmd目录下的第一层逻辑通常建议比较薄,可以参考k8当中的所有组件下的cmd目录,以及golang工程标准的项目结构建议https://github.com/golang-standards/project-layout
从k8s当中学习gocli脚手架开发利器-cobra由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“从k8s当中学习gocli脚手架开发利器-cobra”
上一篇
uniappapp更新
下一篇
RustWeb小项目