您现在的位置是:首页 >其他 >Cobra in 云原生网站首页其他

Cobra in 云原生

lu-hongcheng 2026-01-26 12:01:04
简介Cobra in 云原生

官方教程 https://cobra.dev/

cobra-cli 命令行使用

mkdir mycli && cd mycli
go mod init mycli
cobra-cli init [--author "lu-hongcheng" --license mit]
cobra-cli add say
cobra add hello -p sayCmd 

这种方式文件结构比较固定,一般我还是采用手撸的方式

以云原生项目 karpor 为例

1. 项目启动

cmd/karpor/main.go:31

package main

import (
	"os"

	"github.com/KusionStack/karpor/cmd/karpor/app"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/component-base/cli"
)

func main() {
	ctx := genericapiserver.SetupSignalContext()

	cmd := app.NewServerCommand(ctx)
	syncCmd := app.NewSyncerCommand(ctx)
	cmd.AddCommand(syncCmd)

	code := cli.Run(cmd)
	os.Exit(code)
}

SetupSignalContext 返回 context,接受信号 SIGTERM 和 SIGINT 时被取消

var onlyOneSignalHandler = make(chan struct{})

func SetupSignalContext() context.Context {
	close(onlyOneSignalHandler) // panics when called twice

	shutdownHandler = make(chan os.Signal, 2)

	ctx, cancel := context.WithCancel(context.Background())
	signal.Notify(shutdownHandler, shutdownSignals...)
	go func() {
		<-shutdownHandler
		cancel()
		<-shutdownHandler
		os.Exit(1) // second signal. Exit directly.
	}()

	return ctx
}

这其实和很多后端项目的起手式一样

func (a *App) Run(ctx context.Context) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	for _, srv := range a.servers {
		go func(srv server.Server) {
			err := srv.Start(ctx)
			if err != nil {
				log.Printf("Server start err: %v", err)
			}
		}(srv)
	}

	select {
	case <-signals:
		// Received termination signal
		log.Println("Received termination signal")
	case <-ctx.Done():
		// Context canceled
		log.Println("Context canceled")
	}

	// Gracefully stop the servers
	for _, srv := range a.servers {
		err := srv.Stop(ctx)
		if err != nil {
			log.Printf("Server stop err: %v", err)
		}
	}

	return nil
}

2. 创建命令

ok 获得了 ctx,调用 app.NewServerCommand(ctx) 创建一个新的服务器命令

cmd/karpor/app/server.go:97

func NewServerCommand(ctx context.Context) *cobra.Command {
	o, err := NewOptions(os.Stdout, os.Stderr)
	if err != nil {
		klog.Background().Error(err, "Unable to initialize command options")
		klog.FlushAndExit(klog.ExitFlushTimeout, 1)
	}

	expvar.Publish("CoreOptions", expvar.Func(func() interface{} {
		return o.CoreOptions
	}))
	expvar.Publish("StorageOptions", expvar.Func(func() interface{} {
		return o.SearchStorageOptions
	}))
	expvar.Publish("AIOptions", expvar.Func(func() interface{} {
		displayOpts := *o.AIOptions
		displayOpts.AIAuthToken = "[hidden]"
		return &displayOpts
	}))
	expvar.Publish("Version", expvar.Func(func() interface{} {
		return version.GetVersion()
	}))

	cmd := &cobra.Command{
		Use:   "karpor",
		Short: "Launch an API server",
		Long:  "Launch an API server",
		RunE: func(c *cobra.Command, args []string) error {
			if o.CoreOptions.Version {
				fmt.Println(version.GetVersion())
				return nil
			}
			if err := o.Complete(); err != nil {
				return err
			}
			if err := o.Validate(args); err != nil {
				return err
			}
			if err := o.RunServer(ctx.Done()); err != nil {
				return err
			}
			return nil
		},
	}

	o.AddFlags(cmd.Flags())
	return cmd
}

创建 option -> 补全 option -> 校验 option -> 用 option 启动 server (和 k8s-apiserver 代码格式一模一样)

AIOptions 配置

这里看一下怎么配置 AIOption 的,我对这方面比较感兴趣
cmd/karpor/app/options/ai.go:43 创建 AIOptions

type AIOptions struct {
	AIBackend     string
	AIAuthToken   string
	AIBaseURL     string
	AIModel       string
	AITemperature float32
	AITopP        float32
	// proxy options
	AIProxyEnabled bool
	AIHTTPProxy    string
	AIHTTPSProxy   string
	AINoProxy      string
}

func NewAIOptions() *AIOptions {
	return &AIOptions{}
}

参数和标志

参数 : go run main.go add 3 5 args 数组将会包含以下内容 args := []string{"3", "5"},这个很少用,一般使用命令标志。

cmd/karpor/app/options/ai.go:67

o.AIOptions.AddFlags(fs)

// AddFlags adds flags for a specific Option to the specified FlagSet
func (o *AIOptions) AddFlags(fs *pflag.FlagSet) {
	if o == nil {
		return
	}

	fs.StringVar(&o.AIBackend, "ai-backend", defaultBackend, "The ai backend")
	fs.StringVar(&o.AIAuthToken, "ai-auth-token", "", "The ai auth token")
	fs.StringVar(&o.AIBaseURL, "ai-base-url", "", "The ai base url")
	fs.StringVar(&o.AIModel, "ai-model", defaultModel, "The ai model")
	fs.Float32Var(&o.AITemperature, "ai-temperature", defaultTemperature, "The ai temperature")
	fs.Float32Var(&o.AITopP, "ai-top-p", defaultTopP, "The ai top-p")
	fs.BoolVar(&o.AIProxyEnabled, "ai-proxy-enabled", false, "The ai proxy enable")
	fs.StringVar(&o.AIHTTPProxy, "ai-http-proxy", "", "The ai http proxy")
	fs.StringVar(&o.AIHTTPSProxy, "ai-https-proxy", "", "The ai https proxy")
	fs.StringVar(&o.AINoProxy, "ai-no-proxy", "", "The ai no-proxy")
}

3. 运行命令

然后创建子命令 syncCmd 添加到 rootCmd 中 cmd.AddCommand(syncCmd)

code := cli.Run(cmd)
os.Exit(code)

这个写法也很典型,它确保日志记录已正确设置。在日志记录设置之前,它将错误作为纯文本打印到标准错误输出。这涵盖了命令行标志解析错误和未知命令。之后,它会使用 klog 记录这些错误,这涵盖了运行时错误。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。