写点什么

TiUP 源码初探

  • 2024-05-31
    北京
  • 本文字数:4807 字

    阅读完需:约 16 分钟

作者: madcoder 原文来源:https://tidb.net/blog/e5bc84dc


想必 tidber 都使用过 tiup,tiup 给部署带来很大的便利性,在日常开发中也会涉及到分布式的部署,自己难免也会写一些工具,本文抛砖引玉,分享一下自己看 tiup 源码的一些步骤,希望您在看源码或写工具时多一个思考方向。环境部署可参考:启航 TiDB:调试环境搭建(vscode+wsl+pd)

Cobra

Cobra 是一个 Go 语言开发的命令行(CLI)框架,是由 Go 团队成员 spf13Hugo 项目创建的,并已被许多流行的 Go 项目所采用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。而 Tiup 也是以 cobra 为基础,进行开发的。网上有大量的介绍文章,这里就不赘述了,直接放一个简单的 demo。


demo├── cmd│   └── root.go|   └── version.go├── go.mod├── go.sum└── main.go
复制代码


// root.govar (  rootCmd *cobra.Command)
func init() { rootCmd = &cobra.Command{ Use: "demo", Short: "Demo is a Cobra application", Long: `This is a demo application to illustrate the use of Cobra library.`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Hello, Cobra!") }, } rootCmd.AddCommand(newVersionCmd())}func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) }}
// version.gofunc newVersionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print the version number of Demo", Long: `All software has versions. This is Demo's`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Demo v0.1 -- HEAD") }, } return cmd}
func init() { rootCmd.AddCommand(versionCmd)}// main.gofunc main() { cmd.Execute()}
复制代码


编译后运行


> .\\demo.exe -hThis is a demo application to illustrate the use of Cobra library.
Usage: demo [flags] demo [command]
Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command version Print the version number of Demo
Flags: -h, --help help for demo
Use "demo [command] --help" for more information about a command.
复制代码


同样,Tiup 的组织结构也很简单明确,在 cmd 文件夹下放着各种命令,root.go 为注册命令的地方,目录结构如下:


├── cmd│   ├── env.go│   ├── list.go│   ├── mirror.go│   ├── root.go   # 命令注册├── components  # 组件│   ├── bench│   ├── cluster   |   |   |── command|   |  │   ├── clean.go|   |  │   ├── deploy.go|   |  │   ├── root.go # cluster 命令注册|   |   |—— main.go│   └── playground├── pkg│   ├── cluster│   │   ├── ansible│   │   ├── api│   │   ├── audit│   │   ├── clusterutil│   │   ├── ctxt│   │   ├── executor   # 执行器,easyssh和nativessh│   │   ├── manager    # 任务的生成,步骤的生成所有的管理都在这里│   │   ├── module│   │   ├── operation│   │   ├── spec       # 拓扑结构--安装的说明书│   │   ├── task       # 各种任务│   │   └── template   # ├── main.go
复制代码


和 demo 不同的是,Tiup 的二级命令是通过调用对应的执行文件来实现,这些执行文件在 $TIUPHOME/components/cluster/vx.x.x/ 下,在执行的时候如果没有对应的子命令就会下载(只用 tiup-cluster, 就不会有 tiup-dm),另一个好处是每一个子命令都是一个独立的命令行工具—可插拔组件。


// root.gofunc init() {  cobra.EnableCommandSorting = false  _ = os.Setenv(localdata.EnvNameTelemetryEventUUID, eventUUID)
rootCmd = &cobra.Command{ Use: `tiup [flags] <command> [args...]` // ... }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // InitEnv return nil }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return cmd.Help() } env := environment.GlobalEnv() // ... return tiupexec.RunComponent(env, tag, componentSpec, binPath, args) }, }}
复制代码


而这些命令最小的执行单位是任务。

任务

任务会有执行和回滚两个操作,在操作的时候有顺序和并行的两种任务,builder 来构建不同的任务


type (  // Task represents a operation while TiUP execution  Task interface {    fmt.Stringer    Execute(ctx context.Context) error    Rollback(ctx context.Context) error  }
// Serial will execute a bundle of task in serialized way Serial struct { ignoreError bool hideDetailDisplay bool inner []Task }
// Parallel will execute a bundle of task in parallelism way Parallel struct { ignoreError bool hideDetailDisplay bool inner []Task })
复制代码


任务也可以作为一种方法为其它任务调用比如:CopyComponent,任务最后都会调用执行器来结束任务,其实就是 linux 上的命令。


func (m *Mkdir) Execute(ctx context.Context) error { exec, found := ctxt.GetInner(ctx).GetExecutor(m.host)  // ...  _, _, err := exec.Execute(ctx, cmd, m.sudo) // use root to create the dir  if err != nil {    return errors.Trace(err)  }  // ...  return nil}
func (c *CopyComponent) Execute(ctx context.Context) error { // ... install := &InstallPackage{ srcPath: srcPath, host: c.host, dstDir: c.dstDir, } return install.Execute(ctx)}
func (c *InstallPackage) Execute(ctx context.Context) error { // Install package to remote server exec, found := ctxt.GetInner(ctx).GetExecutor(c.host) if !found { return ErrNoExecutor } dstDir := filepath.Join(c.dstDir, "bin") dstPath := filepath.Join(dstDir, path.Base(c.srcPath))
err := exec.Transfer(ctx, c.srcPath, dstPath, false, 0, false) if err != nil { return errors.Annotatef(err, "failed to scp %s to %s:%s", c.srcPath, c.host, dstPath) } cmd := fmt.Sprintf(`tar --no-same-owner -zxf %s -C %s && rm %s`, dstPath, dstDir, dstPath)
_, stderr, err := exec.Execute(ctx, cmd, false) if err != nil { return errors.Annotatef(err, "stderr: %s", string(stderr)) } return nil}
复制代码

执行器

执行器放在上下文上的,它包含两个方法,执行器的设置是在需要 SSH 的时候,比如下面的 RootSSH


Executor interface {    Execute(ctx context.Context, cmd string, sudo bool, timeout ...time.Duration) (stdout []byte, stderr []byte, err error)    Transfer(ctx context.Context, src, dst string, download bool, limit int, compress bool) error  }
// Execute implements the Task interfacefunc (s *RootSSH) Execute(ctx context.Context) error { // ... e, err := executor.New(s.sshType, s.sudo, sc) if err != nil { return err }
ctxt.GetInner(ctx).SetExecutor(s.host, e) return nil}
复制代码


执行器有easyssh和 nativessh 两种


// Deploy a cluster.func (m *Manager) Deploy(  name string,  clusterVersion string,  topoFile string,  opt DeployOptions,  afterDeploy func(b *task.Builder, newPart spec.Topology, gOpt operator.Options),  skipConfirm bool,  gOpt operator.Options,) error
复制代码

操作

可以看到 tiup 的基础就是任务,任务结束于执行器—ssh,多个任务就合并成一个步骤,多个步骤组合成一个操作:task.NewBuilder(m.logger).Atask().Btask.AS,下面我们看一个实际的操作 Deploy


每一个操作都放在 manager 文件夹下,这里以 Deploy 为例,可以看到任务组一般都是以 ssh 任务开始,显示任务结束。


// command/root.gofunc init() {  rootCmd.AddCommand(    newCheckCmd(),    newDeploy(),    // ...    )}// command/deploy.gofunc newDeploy() *cobra.Command {  opt := manager.DeployOptions{    IdentityFile: path.Join(utils.UserHome(), ".ssh", "id_rsa"),  }  cmd := &cobra.Command{    Use:          "deploy <cluster-name> <version> <topology.yaml>",    Short:        "Deploy a cluster for production",    Long:         "Deploy a cluster for production. SSH connection will be used to deploy files, as well as creating system users for running the service.",    SilenceUsage: true,    RunE: func(cmd *cobra.Command, args []string) error {      // ...      return cm.Deploy(clusterName, version, topoFile, opt, postDeployHook, skipConfirm, gOpt)    },  }  // ...  cmd.Flags().BoolVarP(&opt.NoLabels, "no-labels", "", false, "Don't check TiKV labels")  return cmd}// manager/deploy.gofunc (m *Manager) Deploy(  name string,  clusterVersion string,  topoFile string,  opt DeployOptions,  afterDeploy func(b *task.Builder, newPart spec.Topology, gOpt operator.Options),  skipConfirm bool,  gOpt operator.Options,) error {// ...var (    envInitTasks      []*task.StepDisplay // tasks which are used to initialize environment    downloadCompTasks []*task.StepDisplay // tasks which are used to download components    deployCompTasks   []*task.StepDisplay // tasks which are used to copy components to remote host  )// ...     t := task.NewBuilder(m.logger).      RootSSH(        host,        hostInfo.ssh,        opt.User,        sshConnProps.Password,        // ...,      ).      EnvInit(host, globalOptions.User, globalOptions.Group, opt.SkipCreateUser || globalOptions.User == opt.User, sudo).      Mkdir(globalOptions.User, host, sudo, dirs...).      BuildAsStep(fmt.Sprintf("  - Prepare %s:%d", host, hostInfo.ssh))    envInitTasks = append(envInitTasks, t)  // ...  builder := task.NewBuilder(m.logger).    Step("+ Generate SSH keys",      task.NewBuilder(m.logger).        SSHKeyGen(m.specManager.Path(name, "ssh", "id_rsa")).        Build(),      m.logger).    ParallelStep("+ Download TiDB components", false, downloadCompTasks...).    ParallelStep("+ Initialize target host environments", false, envInitTasks...).    ParallelStep("+ Deploy TiDB instance", false, deployCompTasks...).    ParallelStep("+ Copy certificate to remote host", gOpt.Force, certificateTasks...).    ParallelStep("+ Init instance configs", gOpt.Force, refreshConfigTasks...).    ParallelStep("+ Init monitor configs", gOpt.Force, monitorConfigTasks...)    // ...    m.logger.Infof("Cluster `%s` deployed successfully, you can start it with command: `%s`", name, hint)  return nil}
复制代码


发布于: 刚刚阅读数: 4
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
TiUP 源码初探_集群管理_TiDB 社区干货传送门_InfoQ写作社区