侧边栏壁纸
  • 累计撰写 48 篇文章
  • 累计创建 7 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

【Golang】从人工到智能:编译时自动化注入Git版本信息

Administrator
2024-12-13 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

前言

版本控制是软件开发中的核心环节。传统上,我们通过配置文件控制、数据库记录控制和硬编码来管理版本信息。然而,随着自动化技术的不断发展,这些方法往往需要手动维护,容易受到篡改和人为疏忽的影响,导致版本信息滞后或错误。之前查看kubesphere/kubekey源码时,发现其和<font style="color:rgb(51, 51, 51);">kubernetes</font>都使用了编译时自动注入<font style="color:rgb(51, 51, 51);">Git</font>版本信息。它通过自动化、强一致性和防篡改性,确保版本信息的准确性和可靠性,成为生产环境的首选方案。

对比说明:

‌特性‌ ‌编译时注入 Git 信息‌ ‌配置文件写入版本信息‌ ‌数据库记录版本‌ ‌硬编码版本‌
‌自动化程度‌ ‌完全自动‌:通过构建脚本动态获取 Git 信息,无需人工维护‌ ‌手动维护‌:需人工更新配置文件,易遗忘或出错‌ ⚠️ ‌半自动‌:需应用启动时写入数据库,依赖代码逻辑‌ ‌完全手动‌:版本号直接写在代码中,需修改源码‌
‌准确性‌ ‌实时精准‌:直接关联当前代码的 Commit Hash、分支/标签‌ ‌可能滞后‌:配置文件可能未及时更新‌ ⚠️ ‌依赖写入时机‌:若启动时未更新,可能不准确‌ ‌固定不变‌:代码修改后需重新编译,否则版本信息失效‌
‌防篡改性‌ ‌不可篡改‌:版本信息编译进二进制文件‌ ‌可能被篡改‌:配置文件易被修改或覆盖‌ ⚠️ ‌依赖权限控制‌:数据库记录可能被误删或篡改‌ ‌可修改‌:需重新编译代码才能更新版本‌
‌依赖项‌ ‌依赖构建环境‌:需安装 Git 工具链(如 git describe)‌ ‌无额外依赖‌:仅需读取静态文件‌ ‌依赖数据库‌:需数据库服务可用‌ ‌无依赖‌:版本信息直接内置于代码‌
‌适用场景‌ ‌生产环境‌:需严格追踪版本、审计或快速回滚‌ ⚠️ ‌开发/测试‌:简单场景,无需精准版本控制‌ ‌数据关联场景‌:需与数据库变更记录绑定‌ ‌原型验证‌:仅临时或小型项目适用‌
‌与代码一致性‌ ‌强一致‌:自动绑定当前代码状态(包括未提交的改动)‌ ‌弱一致‌:需手动同步代码与配置文件‌ ⚠️ ‌间接关联‌:依赖应用启动时写入数据库的逻辑‌ ‌完全脱节‌:版本信息与代码更新需手动同步‌
‌持久化与追溯性‌ ⚠️ ‌仅限日志‌:需额外记录日志供审计‌ ‌短期存储‌:文件可能丢失或覆盖‌ ‌长期追溯‌:数据库支持历史版本查询‌ ‌无历史记录‌:无法追溯旧版本信息‌

ldflags简介

ldflags 是 Go 语言编译时的一个重要选项,用于传递链接器(linker)标志。通过使用 ldflags,开发者可以在编译过程中注入变量、修改包的属性或控制链接器的行为。

在 Go 中,ldflags 通常与 go buildgo install 命令一起使用。本文将使用-ldflags传参的形式,在go build时将包中的version变量的值修改为git版本。

涉及的代码

  • 低需求:

只记录version版本信息

在原有代码基础上新增version包,并新增version.go,填写如下代码:

package version

var version = "3.0.0"

func GetVersion() string {
    return version
}

本文将主要介绍该种模式

编译时注入Git版本信息,通过如下命令编译后version.version的值为${GIT_VERSION}的值

go build  -ldflags ="-X 'github.com/gjing1st/hertz-admin/version.version=${GIT_VERSION}" -o ha cmd/ha/main.go
  • 高要求:

记录详细git版本信息,该模块主要参考kubesphere/kubekeyk8s

在原有代码基础上新增version包,并新增version.go,填写如下代码:

package version

import (
    "fmt"
    "runtime"
)

var appName = "ha-server"

func GetAppName() string {
    return appName
}

var (
    gitMajor     string // major version, always numeric
    gitMinor     string // minor version, numeric possibly followed by "+"
    gitVersion   string // semantic version, derived by build scripts
    gitCommit    string // sha1 from git, output of $(git rev-parse HEAD)
    gitTreeState string // state of git tree, either "clean" or "dirty"
    buildDate    string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

// Info exposes information about the version used for the current running code.
type Info struct {
    Major        string `json:"major,omitempty"`
    Minor        string `json:"minor,omitempty"`
    GitVersion   string `json:"gitVersion,omitempty"`
    GitCommit    string `json:"gitCommit,omitempty"`
    GitTreeState string `json:"gitTreeState,omitempty"`
    BuildDate    string `json:"buildDate,omitempty"`
    GoVersion    string `json:"goVersion,omitempty"`
    Compiler     string `json:"compiler,omitempty"`
    Platform     string `json:"platform,omitempty"`
}

// Get returns an Info object with all the information about the current running code.
func Get() Info {
    return Info{
       Major:        gitMajor,
       Minor:        gitMinor,
       GitVersion:   gitVersion,
       GitCommit:    gitCommit,
       GitTreeState: gitTreeState,
       BuildDate:    buildDate,
       GoVersion:    runtime.Version(),
       Compiler:     runtime.Compiler,
       Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
    }
}

// String returns info as a human-friendly version string.
func (info Info) String() string {
    return info.GitVersion
}

该阶段编译时Git版本注入可查看博主开源框架hertz-admin:基于字节跳动hertz建的后台管理框架。这里不再介绍。

hertz-admin

获取Git版本信息

获取Git版本信息主要从以下三个方面介绍

  • Makefile中直接获取
  • 通过shell获取
  • CI/CD中获取

Makefile中获取

获取git tag作为版本信息

GIT_VERSION=$(shell git describe --tags --abbrev=14 --match "v[0-9]*" 2>/dev/null | sed 's/^v//')

获取git分支中的版本信息:1.1.0

分支结构如下:

GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
GIT_VERSION := $(shell echo "$(GIT_BRANCH)" | sed -e 's/^v//' -e 's/[^0-9.]*$$//')

通过shell获取

可参考kubesphere/kubekeyk8s中的shell,由于篇幅限制这里不再记录,若需要可查看

version.sh

CI/CD

这里只介绍基于gitlab-runnerci/cd其他可参考编写

GIT_VERSION=`echo $CI_COMMIT_REF_NAME | sed 's/\(v[0-9\x2e]*\).*/\1/'`

编译涉及的阶段

  • 编译可执行文件
  • Docker 镜像打包
  • CI/CD 流水线

接下来介绍每个阶段时如何获取Git版本信息并传递至编译阶段。

直接编译

这里使用Makefile去编译并使用Git tag为例:

# 获取git 版本信息
GIT_VERSION=$(shell git describe --tags --abbrev=14 --match "v[0-9]*" 2>/dev/null | sed 's/^v//')
#编译参数
LDFLAGS="-s -w -extldflags -static -X github.com/gjing1st/hertz-admin/version.VERSION=$(GIT_VERSION)'"
# 编译 go build
.PHONY: build
build:
    go build -ldflags $(LDFLAGS) -o ha-server cmd/ha/main.go

Docker 镜像打包

Makefile中定义docker目标

.PHONY: docker
TAG := $(IMAGE_NAME):$(GIT_VERSION)
docker: ## 打包成docker镜像
    @echo "Building image with tag '$(TAG)'"
    docker build --build-arg LDFLAGS="$(LDFLAGS)" -f ./build/docker/Dockerfile -t $(TAG) .

Dockerfile中代码如下:

ARG GO_VERSION=1.24.0
ARG ALPINE_VERSION=3.21
FROM  golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build
ARG LDFLAGS
WORKDIR /src
ENV GOPROXY https://goproxy.cn,direct
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,source=go.sum,target=go.sum \
    --mount=type=bind,source=go.mod,target=go.mod \
    go mod download -x
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    CGO_ENABLED=0 GOOS=linux  go build -trimpath -ldflags="-s -w ${LDFLAGS}" -o /bin/server ./cmd/ha/main.go

FROM alpine:${ALPINE_VERSION}
COPY --from=build /bin/server /bin/
EXPOSE 9680
ENTRYPOINT [ "/bin/server" ]

CI/CD

这里使用GitLab-Runner实现,并使用git分支中的版本号,将代码提交次数作为最后一位。

如v1.0.0版本第一次提交代码后的版本为:1.0.0.0,第三次提交代码后的版本为:1.0.0.2

.gitlab-ci.yml中部分代码如下:

- VERSION=`echo $CI_COMMIT_REF_NAME | sed 's/\(v[0-9\x2e]*\).*/\1/'`
- echo ${CI_COMMIT_SHA}
# c8679fbb719994631bc6a9930d41617f7a1b382d 是上一步某次提交的哈希值
- COMMIT_COUNT=$(git rev-list --count 8844e6726e5e2c8837e1d5c348864cd97864f951..HEAD)
- echo ${COMMIT_COUNT}
# 版本信息
- echo ${VERSION:1}.${COMMIT_COUNT}
#打包 docker 
- docker build --build-arg APP_VERSION=${VERSION:1}.${COMMIT_COUNT}  -t ha-server:${VERSION}  .

Dockerfile中编译部分

go build -ldflags="-s -w -X has/version.version=${APP_VERSION}"  -o has ./cmd/has/main.go

总结

本文主要对比了 Go 项目版本管理的常见方案,重点解析 基于 Git 的编译时自动化注入 的实践优势。通过各阶段式代码实现(信息提取、清洗、注入与校验),开发者可快速落地自动化流程。最终,以“最小运维成本”实现版本可靠性与可追溯性的平衡。

0

评论区