promu是prometheus组件的二进制构建工具,包括prometheus、node-exporter都是使用promu构建出来的二进制文件。

具体来说,promu封装了go命令,为目标定义了使用go命令进行编译、构建的流程(比如构建几个binary,从哪里构建),添加了编译的参数(flags/ldflags)。

一. promu的使用

1. promu构建prometheus

prometheus源码中Makefile.common文件,描述了使用promu构建prometheus的过程:

  • common-build:

    • 首先,common-build依赖promu;
    • 然后,使用promu build命令,构建prometheus的二进制文件(包括prometheus/promtool);
  • promu:

    • 使用curl从github下载promu二进制文件,然后copy到~/go/bin中;
.PHONY: common-build
common-build: promu
    @echo ">> building binaries"
    GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
PROMU_VERSION ?= 0.5.0
PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz

.PHONY: promu
promu: $(PROMU)

$(PROMU):
    $(eval PROMU_TMP := $(shell mktemp -d))
    curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP)
    mkdir -p $(FIRST_GOPATH)/bin
    cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu
    rm -r $(PROMU_TMP)

2. promu的配置文件

promu使用的配置文件,若未指定,则默认使用当前目录的.promu.yml文件。

以prometheus的.promu.yml文件为例:

# cat .promu.yml
go:
    # Whenever the Go version is updated here,
    # .circle/config.yml should also be updated.
    version: 1.14
repository:
    path: github.com/prometheus/prometheus
build:
    binaries:
        - name: prometheus
          path: ./cmd/prometheus
        - name: promtool
          path: ./cmd/promtool
        - name: tsdb
          path: ./tsdb/cmd/tsdb
    flags: -mod=vendor -a -tags netgo,builtinassets
    ldflags: |
        -X github.com/prometheus/common/version.Version={{.Version}}
        -X github.com/prometheus/common/version.Revision={{.Revision}}
        -X github.com/prometheus/common/version.Branch={{.Branch}}
        -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
        -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
tarball:
    files:
        - consoles
        - console_libraries
        - documentation/examples/prometheus.yml
        - LICENSE
        - NOTICE
        - npm_licenses.tar.bz2
crossbuild:
    platforms:
        - linux/amd64
        - linux/386
...

由于构建时使用promu build命令,我们重点关注配置文件中的build部分:

  • binary:定义build哪些二进制

    • 从配置文件可以看出,build了prometheus/promtool/tsdb
  • flags: 定义go build时候的参数

    • -mod=vendor指定使用go module的本地vendor编译;
  • ldflags: 定义go build时候的动态链接参数;

promu通过.promu.yml规定编译构建的流程,具体来说:

  • 它最终还是调用go build进行编译;
  • 它给go build增加了各种编译参数,比如-mod vendor,ldflags等;
  • 它定义了编译的目标,比如prometheus/promtool/tsdb等;

二. promu的源码分析

promu最终调用的还是go的命令,比如promu build调用go build。

1. 配置文件解析

默认从.promu.yml读配置文件:

const (
    // DefaultConfigFilename contains the default filename of the promu config file
    DefaultConfigFilename = ".promu.yml"
)
configFile = app.Flag("config", "Path to config file").Short('c').
            Default(DefaultConfigFilename).String()

文件解析过程:

// cmd/promu.go
func initConfig(filename string) {
    info(fmt.Sprintf("Using config file: %v", filename))
    configData, err := ioutil.ReadFile(filename)
    checkError(err, "Unable to read config file: "+filename)
    config = NewConfig()
    err = yaml.Unmarshal(configData, config)
    checkError(err, "Unable to parse config file: "+filename)
}

所有的配置,最终被反序列化到Config对象中:

type Config struct {
    Build struct {
        Binaries   []Binary
        Flags      string
        LDFlags    string
        ExtLDFlags []string
        Prefix     string
        Static     bool
    }
    Crossbuild struct {
        Platforms []string
    }
    Repository struct {
        Path string
    }
    Go struct {
        CGo     bool
        Version string
    }
    Tarball struct {
        Files  []string
        Prefix string
    }
}

我们重点关注promu build命令,以及.promu.yml中build参数的使用。

2. promu build命令解析

promu支持不同的子命令,比如build子命令的执行流程:

// cmd/promu.go
func Execute() {
    var err error
    projInfo, err = NewProjectInfo()
    checkError(err, "Unable to initialize project info")

    command := kingpin.MustParse(app.Parse(os.Args[1:]))    // 子命令
    sh.Verbose = *verbose
    initConfig(*configFile)

    info(fmt.Sprintf("Running command: %v %v", command, os.Args[2:]))

    switch command {
    case buildcmd.FullCommand():        // build子命令
        runBuild(optArg(*binariesArg, 0, "all"))
    ...
    case versioncmd.FullCommand():       // version子命令
        runVersion()
    }
}

典型的,prometheus中.promu.yml的build参数的定义:

build:
    binaries:
        - name: prometheus
          path: ./cmd/prometheus
        - name: promtool
          path: ./cmd/promtool
        - name: tsdb
          path: ./tsdb/cmd/tsdb
    flags: -mod=vendor -a -tags netgo,builtinassets
    ldflags: |
        -X github.com/prometheus/common/version.Version={{.Version}}
        -X github.com/prometheus/common/version.Revision={{.Revision}}
        -X github.com/prometheus/common/version.Branch={{.Branch}}
        -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
        -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}

build.binaries参数的处理

build.binary指定了build的目标main.go;

对每个binary配置,执行go build -o binaryName命令:

// cmd/build.go
func buildAll(ext string, prefix string, ldflags string, binaries []Binary) {
    for _, binary := range binaries {
        buildBinary(ext, prefix, ldflags, binary)
    }
}
func buildBinary(ext string, prefix string, ldflags string, binary Binary) {
    ...
    binaryName := fmt.Sprintf("%s%s", binary.Name, ext)
    fmt.Printf(" >   %s\n", binaryName)
    ...
    params := []string{"build",
        "-o", path.Join(prefix, binaryName),
        "-ldflags", ldflags,
    }
    ...
    // 执行 go build命令
    if err := sh.RunCommand("go", params...); err != nil {
        fatal(errors.Wrap(err, "command failed: "+strings.Join(params, " ")))
    }
}

build.flags参数的处理

build.flags指定了build的参数,比如-mod vendor;

// cmd/build.go
func buildBinary(ext string, prefix string, ldflags string, binary Binary) {
    info("Building binary: " + binary.Name)
    binaryName := fmt.Sprintf("%s%s", binary.Name, ext)
    fmt.Printf(" >   %s\n", binaryName)

    repoPath := config.Repository.Path
    flags := config.Build.Flags

    params := []string{"build",
        "-o", path.Join(prefix, binaryName),
        "-ldflags", ldflags,
    }
    // flags的参数被添加到 go build的参数列表中
    params = append(params, sh.SplitParameters(flags)...)
    params = append(params, path.Join(repoPath, binary.Path))
    info("Building binary: " + "go " + strings.Join(params, " "))
    if err := sh.RunCommand("go", params...); err != nil {
        fatal(errors.Wrap(err, "command failed: "+strings.Join(params, " ")))
    }
}

build.ldflags参数的处理

build.ldflags指定了build的动态参数,比如-X version=v1等:

// cmd/build.go
func buildBinary(ext string, prefix string, ldflags string, binary Binary) {
    info("Building binary: " + binary.Name)
    binaryName := fmt.Sprintf("%s%s", binary.Name, ext)
    fmt.Printf(" >   %s\n", binaryName)

    repoPath := config.Repository.Path
    flags := config.Build.Flags

    params := []string{"build",
        "-o", path.Join(prefix, binaryName),
        "-ldflags", ldflags,
    }
    params = append(params, sh.SplitParameters(flags)...)
    params = append(params, path.Join(repoPath, binary.Path))
    info("Building binary: " + "go " + strings.Join(params, " "))
    if err := sh.RunCommand("go", params...); err != nil {
        fatal(errors.Wrap(err, "command failed: "+strings.Join(params, " ")))
    }
}

参考:

1.https://ost.51cto.com/posts/1...
2.https://github.com/prometheus...


a朋
63 声望38 粉丝