打上戳!所有程序都必须报告自己的版本
原文由 Michael Stapelberg 于 发布,订阅该博客
最近在一次线上故障的应急处置中,我在不到一小时内就猜中了故障的根本原因(挺酷的!),并提交了一个修复来验证排除,结果却因为缺少对版本号和发布情况的可视性,在黑暗中摸索了好几个小时…… 😞
这次经历让我再次思考起软件版本管理,更具体地说,是构建信息(构建版本、版本戳,怎么叫都行)和版本报告。我意识到,对于 i3 窗口管理器,我在十多年前就已经很好地解决了这个问题,所以在工作中发现这个问题居然完全没有得到解决,实在令人意外。
在本文中,我将说明只需 3 个简单的步骤(打上戳!打通链路!报告出来!),就能在故障应急中为你节省数小时的延误和焦虑。
为什么我们的版本标准这么低?!
每台家用电器都有极其详尽的版本标识!看看这台洗碗机:

(感谢 Feuermurmel 发来这个可爱的例子!)
我观察过几次家电维修,感觉如果维修人员无法识别设备型号,他们很可能根本就不会动手修理。
相比之下,为什么我们在计算机领域的标准就这么低呢?当然,面向消费者的产品通常都算是有版本标识的,一般也够用了(比如 USB 3.2 Gen 1×2 这种例外不算!)。但最近,我遇到了太多没有做好版本标识的开发者构建!
软件版本管理
与贴着冲压金属铭牌的实体家电不同,软件在不断更新,并且运行在我们常常看都看不到的地方和结构中。
让我们来深入看看,要提升我们的版本标准,需要做些什么!
通常,软件都有一个名称和一个粒度各异的版本号:
- Chrome
- Chrome 146
- Chrome 146.0.7680.80
- Chrome f08938029c887ea624da7a1717059788ed95034d-refs/branch-heads/7680_65@{#34}
以上这些都能标识我电脑上的 Chrome 浏览器,只是粒度不同。
它们在不同场景下都是正确且有用的。举例如下:
- “在我这儿 Chrome 上是正常的,你在 Firefox 上测过吗?”
- “Chrome 146 的中键粘贴并跳转功能坏了”
- “我用的是 Chrome 146.0.7680.80,无法复现你的问题”
- “在这个 Chrome 版本 f08938029c887ea624da7a1717059788ed95034d-refs/branch-heads/7680_65@{#34} 上打上补丁,然后按以下步骤复现:[…]”
在创建了i3 窗口管理器之后,我很快就认识到,对于用户支持来说,程序能够清晰地标识自己是多么重要。下面我通过一个案例来说明。
案例研究:i3 的 --version 和 --moreversion
运行 i3 --version 时,你会看到类似这样的输出:
% i3 --version
i3 version 4.24 (2024-11-06) © 2009 Michael Stapelberg and contributors
每一个词都是经过仔细斟酌后才确定的。我来逐一拆解:
i3 version 4.24:我本来可以简写成i3 4.24或i3 v4.24,但我觉得还是明确一点更好,因为i3这个名字实在太短了。用户可能会嘀咕“i-3-4-2-4 是什么?”,但加上“version”,就暗示了 i3 是某种计算机相关的东西(→ 一个计算机程序),当前版本是 4.24。(2024-11-06)是发布日期,这样你就能立刻判断“4.24”是不是最新的版本。© 2009 Michael Stapelberg标明了项目起始时间以及背后的主要负责人。and contributors则向众多贡献者致谢。i3 从来不是一个人的项目,它始终是集体协作的成果。
在做用户支持时,有几个问题在概念上很容易向受影响的用户提出,却能为开发者提供极有价值的答案:
- 问题:“你在用哪个版本的 i3?”
- 由于 i3 不是运行在窗口内的普通程序(而是窗口管理器/桌面环境),所以没有“Help → About”这样的菜单项。
- 因此,我们改为这样问:
i3 --version的输出是什么?
- 问题:“你报告的是新问题还是老问题?为了确认,你能试着回退到之前用的 i3 版本吗?”。“回退”的专业说法是 downgrade、rollback 或 revert。
- 取决于 Linux 发行版,这件事要么轻而易举,要么是场噩梦。
- 在 NixOS 上,这很简单:只需在引导程序中选择旧的系统“generation”启动即可。或者,如果你的配置是用 git 管理的,直接 revert 就行。
- 而在 Debian Linux 或 Arch Linux 这类命令式发行版上,如果你没有做文件系统级别的快照,升级后就没有简单可靠的办法回退。如果你运气好,可以直接用
apt install安装旧版 i3,但可能会遇到依赖冲突(“版本地狱”)。 - 我知道通过snapshot.debian.org 可以运行旧版 Debian,但至少就我上次尝试的体验来说,并不怎么实用。
- 你能确认一下这个问题在最新的 i3 开发版中是否依然存在吗?
- 当然,我也可以先用最新的发布版复现用户的问题,然后再在最新的开发版上额外再试一次。
- 但把验证这一步交给受影响的用户会更好,因为这能筛选出积极性高的 bug 报告者(更有可能最终修好 bug!),而且会让用户复现两次 bug,从而弄清楚这是偶发问题、难以复现的问题,还是复现步骤本身是否正确等等。
- 一个自然的后续问题是:“这个代码改动能否让问题消失?”对于已经搭好开发环境的用户来说,这很容易验证。
根据多次提问这些问题的经验,我注意到这些调试过程有一些固定模式。因此,我在 i3 v4.3(2012 年 9 月发布)中为 i3 引入了另一种报告版本的方式:--moreversion 标志!现在我可以对第一个问题稍作变化来提问:i3 --moreversion 的输出是什么?注意,这个问法在口头交流中也很方便,比如在计算机聚会上:
Michael: 你用的是哪个版本?
User: 怎么查看?
Michael: 运行这个命令:
i3 --versionUser: 显示是 4.24。
Michael: 很好,这个版本已经够新,包含了那个 bug 修复。现在,我们还需要更详细的版本信息!请运行
i3 --moreversion并告诉我你看到了什么。
运行 i3 --moreversion 时,它不仅会报告你所调用的 i3 程序的版本,还会通过其 IPC(进程间通信)接口连接到你 X11 会话中正在运行的 i3 窗口管理器进程,并报告正在运行的 i3 进程的版本,以及其他一些有助于向用户展示的关键细节,比如加载了哪个配置文件以及最后修改时间:
% i3 --moreversion
Binary i3 version: 4.24 (2024-11-06) © 2009 Michael Stapelberg and…
Running i3 version: 4.24 (2024-11-06) (pid 2521)
Loaded i3 config:
/home/michael/.config/i3/config (main)
(last modified: 2026-03-15T23:09:27 CET, 1101585 seconds ago)
The i3 binary you just called:
/nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24/bin/i3
The i3 binary you are running: i3
乍一看,这似乎是很多细节,但让我来具体说明为什么这个输出是如此有价值的调试工具:
通过 IPC 接口连接到 i3 本身就是一项有意义的测试。如果用户能看到
i3 --moreversion的输出,就意味着他们也能运行类似i3-msg -t get_tree > /tmp/tree.json这样的调试命令来捕获完整的布局状态。在调试过程中,运行
i3 --moreversion可以轻松检查你刚刚构建的版本是否真正生效(看Running i3 version这一行)。- 请注意,这与生产环境故障中相关的检查是同一个:验证实际运行的版本是否与预期运行的版本一致。
显示已加载配置文件的完整路径,可以让人一眼看出用户是否改错了文件。如果单看路径还不够,修改时间(同时以绝对时间和相对时间显示)也能提示是否编辑了错误的文件。
顺便说一下,我用的是 NixOS,所以会自动为 i3 的这次特定构建获得一个稳定标识符(0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24)。
% ls -l $(which i3)
lrwxrwxrwx 1 root root 58 1970-01-01 01:00 /run/current-system/sw/bin/i3
-> /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24/bin/i3
要查看生成这个 Nix store 输出(0zn9r4263…-i3-4.24)的构建配方(在 Nix 术语中叫“derivation”),我可以运行 nix derivation show:
% nix derivation show /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24
{
"/nix/store/z7ly4kvgixf29rlz01ji4nywbajfifk4-i3-4.24.drv": {
[…]
点击此处展开完整的 nix derivation show 输出,如果你感兴趣的话
% nix derivation show /nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24
{
"/nix/store/z7ly4kvgixf29rlz01ji4nywbajfifk4-i3-4.24.drv": {
"args": [
"-e",
"/nix/store/l622p70vy8k5sh7y5wizi5f2mic6ynpg-source-stdenv.sh",
"/nix/store/shkw4qm9qcw5sc5n1k5jznc83ny02r39-default-builder.sh"
],
"builder": "/nix/store/6ph0zypyfc09fw6hlc1ygjvk2hv4j9vd-bash-5.3p3/bin/bash",
"env": {
"NIX_MAIN_PROGRAM": "i3",
"name": "i3-4.24",
"out": "/nix/store/0zn9r4263fjpqah6vdzlalfn0ahp8xc2-i3-4.24",
"version": "4.24"
},
[… jakiego}
}
遗憾的是,据我所知,还没有办法从 derivation 反向找到对应的 .nix 源码,但至少可以检查某个源码是否会产生完全相同的 derivation。
开发者构建
到目前为止我描述的版本管理方式对大多数用户来说已经足够了,他们通常不会去关注软件的中间版本,只关心已发布的版本。
但对于开发者,或任何需要更精确信息的用户来说呢?
当从 git 构建 i3 时,它会使用git-describe(1) 报告其构建所基于的 git 修订版本:
~/i3/build % git describe
4.25-23-g98f23f54
~/i3/build % ninja
[110/110] Linking target i3
~/i3/build % ./i3 --version
i3 version 4.25-23-g98f23f54 © 2009 Michael Stapelberg and contributors
被修改过的工作区会在修订版本后用 + 来表示:
~/i3/build % echo '// dirty working copy' >> ../src/main.c && ninja
[104/104] Linking target i3bar
~/i3/build % ./i3 --version
i3 version 4.25-23-g98f23f54+ © 2009 Michael Stapelberg and contributors
报告 git 修订版本(或者更广义地说,VCS 修订版本)是最有用的选择。
这样,我们就能捕捉到以下常见错误:
- 人们基于错误的修订版本进行构建。
- 人们构建了,却忘了安装。
- 人们安装了,但会话却没有加载到它(路径错了?)。
最有用的做法:打上 VCS 修订版本的戳
如上所见,单条最有用的版本信息就是 VCS 修订版本。我们可以从 VCS 仓库中获取所有其他细节(版本号、日期、作者……)。
现在,让我们通过看看 Go 是怎么做的,来展示最佳实践!
Go 总是会打戳!🥳
这些年来,Go 已经成为我最喜欢的编程语言,很大程度上是因为 Go 开发者的良好品味和风格,当然还有高质量的工具链:
因此,我很高兴地说,Go 在软件版本管理方面实现了黄金标准:它默认就会打上 VCS 构建信息!🥳 这一特性是在Go 1.18(2022 年 3 月)中引入的:
此外,go 命令还会嵌入构建相关信息,包括构建和工具标签(通过 -tags 设置)、编译器、汇编器和链接器标志(如 -gcflags)、是否启用了 cgo,以及如果启用了,cgo 环境变量(如 CGO_CFLAGS)的值。
VCS 和构建信息都可以与模块信息一起,通过
go version -m file或runtime/debug.ReadBuildInfo(用于当前运行的二进制文件)或新的debug/buildinfo 包来读取。
注意:在 Go 1.18 之前,标准做法是使用 -ldflags -X main.version=$(git describe) 或类似的显式注入方式。这种方式可行(现在很多地方仍能看到),但需要在应用代码中做改动,而 Go 1.18 之后的打戳则无需任何额外步骤。
这在实践中意味着什么?下图展示了常见情况:从 git 构建:
这涵盖了我的大多数业余项目!
很多工具我直接用 go install 安装,如果想方便地拷贝到其他机器上,就用 CGO_ENABLED=0 go install。不过,我现在有越来越多的软件是用 NixOS 来管理的。
当我发现某个程序还没被完全纳入管理时,我可以用 gops 和 go 工具来识别它:
root@ax52 ~ % nix run nixpkgs#gops
2573594 1 dcs-package-importer go1.26.1 /nix/store/clby54zb003ibai8j70pwad629lhqfly-dcs-unstable/bin/dcs-package-importer
2573576 1 dcs-source-backend go1.26.1 /nix/store/clby54zb003ibai8j70pwad629lhqfly-dcs-unstable/bin/dcs-source-backend
2573566 1 debiman go1.25.5 /srv/man/bin/debiman
[…]
root@ax52 ~ % nix run nixpkgs#go -- version -m /srv/man/bin/debiman
/srv/man/bin/debiman: go1.25.5
path github.com/Debian/debiman/cmd/debiman
mod github.com/Debian/debiman v0.0.0-20251230101540-ac8f5391b43b+dirty
build vcs=git
build vcs.revision=ac8f5391b43bc1a9dbdc99f6179e2fb7d7414a04
build vcs.time=2025-12-30T10:15:40Z
build vcs.modified=true
Go 默认就做了正确的事,这真的很酷!
由 100% Go 软件构成的系统(比如我的gokrazy Go 设备平台)是完全打好戳的!例如,gokrazy 的网页界面会精确地显示我的scan2drive 设备上 gokrazy/rsync 构建所使用的版本和依赖。
尽管已经完全打戳,请注意 gokrazy 目前只显示模块版本,而没有 VCS 构建信息,因为它目前也存在与 Nix 相同的问题:

Go 的版本报告
对于采用滚动发布模式(没有版本号)的 gokrazy packer,我最终用几行 Go 代码(见下文)来实现无论你是将其作为 Go 模块安装,还是从 git 工作区安装,都能显示 git 修订版本。
这段代码要么直接显示 vcs.revision(简单情况;从 git 构建),要么从主模块的 Go 模块版本中提取修订版本(BuildInfo.Main.Version):
还有哪些情况?下面的例子说明了我通常会遇到的几种场景:
| 来源(构建自) | 构建信息(打入程序中的) |
|---|---|
| 目录(无 git) | 模块 (devel) |
| Go 模块 | 模块 v0.3.1-0.20260105212325-5347ac5f5bcb |
| 目录(git) | 模块 v0.0.0-20260131174001-ccb1d233f2a4+dirty |
vcs.revision=ccb1d233f2a43e9118b9146b3c9a5ded1efb7551 | |
vcs.time=2026-01-31T17:40:01Z | |
vcs.modified=true |
以编程方式读取版本的 Go 代码
package version
import (
"runtime/debug"
"strings"
)
func readParts() (revision string, modified, ok bool) {
info, ok := debug.ReadBuildInfo()
if !ok {
return "", false, false
}
settings := make(map[string]string)
for _, s := range info.Settings {
settings[s.Key] = s.Value
}
// When built from a local VCS directory, we can use vcs.revision directly.
if rev, ok := settings["vcs.revision"]; ok {
return rev, settings["vcs.modified"] == "true", true
}
// When built as a Go module (not from a local VCS directory),
// info.Main.Version is something like v0.0.0-20230107144322-7a5757f46310.
v := info.Main.Version // for convenience
if idx := strings.LastIndexByte(v, '-'); idx > -1 {
return v[idx+1:], false, true
}
return "<BUG>", false, false
}
func Read() string {
revision, modified, ok := readParts()
if !ok {
return "<not okay>"
}
modifiedSuffix := ""
if modified {
modifiedSuffix = " (modified)"
}
return "https://github.com/gokrazy/tools/commit/" + revision + modifiedSuffix
}
实际效果如下:
% go install github.com/gokrazy/tools/cmd/gok@latest
% gok --version
https://github.com/gokrazy/tools/commit/8ed49b4fafc7
但从 git 构建的版本则拥有完整的修订版本(→ 可以区分开来):
% (cd ~gokrazy/../tools && go install ./cmd/...)
% gok --version
https://github.com/gokrazy/tools/commit/ba6a8936f4a88ddcf20a3b8f625e323e65664aa6 (modified)
在 NixOS 上获取 VCS 修订版本
在用 Nix 打包 Go 软件时,很容易丢失 Go 的 VCS 修订版本戳:
- 像
fetchFromGitHub这样的 Nix fetcher 是通过从 GitHub 获取归档文件(.tar.gz)来实现的——并不会传输完整的.git仓库,这样更高效。 - 即使存在
.git仓库,Nix 通常也会为了可复现性而有意将其移除:.git目录中包含的打包对象会在git gc等操作后发生变化(例如),这会破坏可复现构建(同一份源码却得到不同的哈希)。
因此,这里的根本矛盾在于可复现性与 VCS 打戳之间的冲突。
幸运的是,有一个两全其美的解决方案:我创建了stapelberg/nix/go-vcs-stamping Nix overlay 模块,你只需引入它,就能让你的 buildGoModule Nix 表达式默认获得可用的 Go VCS 修订版本戳!
深入了解 Nix 上的 Go 构建情况
提示:如果你不是 Nix 用户,可以跳过这一节。我在本文中加入这一节,是为了提供一个在最复杂环境中实现 VCS 打戳的完整示例。
在 Nix 中打包 Go 软件非常简单直接。
例如,Go 的 Protobuf 生成器插件 protoc-gen-go 在 Nix 中只需不到 30 行就能完成打包:官方 nixpkgs 的 protoc-gen-go package.nix。你只需调用buildGoModule,将fetchFromGitHub 的结果作为 src 传入,再加上几行元数据即可。
但要让开发者构建完全打上戳,就没那么简单了!
在打包我自己的软件时,我想打包的是单个修订版本(开发者构建),而不仅仅是已发布的版本。我使用同样的 buildGoModule,如果需要最新版 Go 就用 buildGoLatestModule。我不再使用 fetchFromGitHub,而是通过 Flakes 来提供源码,通常也是来自 GitHub 或其他 Git 仓库。例如,我是这样打包 gokrazy/bull 的:
{
pkgs,
pkgs-unstable,
bullsrc,
...
}:
# Use buildGoLatestModule to build with Go 1.26
# even before NixOS 26.05 Yarara is released
# (NixOS 25.11 contains Go 1.25).
pkgs-unstable.buildGoLatestModule {
pname = "bull";
version = "unstable";
src = bullsrc;
# Needs changing whenever `go mod vendor` changes,
# i.e. whenever go.mod is updated to use different versions.
vendorHash = "sha256-sU5j2dji5bX2rp+qwwSFccXNpK2LCpWJq4Omz/jmaXU=";
}
bullsrc 来自我的 flake.nix:
点击此处展开完整的 flake.nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
stapelbergnix.url = "github:stapelberg/nix";
zkjnastools.url = "github:stapelberg/zkj-nas-tools";
configfiles = {
url = "github:stapelberg/configfiles";
flake = false;
};
bullsrc = {
url = "github:gokrazy/bull";
flake = false;
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
nixpkgs,
nixpkgs-unstable,
disko,
stapelbergnix,
zkjnastools,
bullsrc,
configfiles,
sops-nix,
...
}:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = false;
};
pkgs-unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = false;
};
in
{
nixosConfigurations.keep = nixpkgs.lib.nixosSystem {
inherit system;
inherit pkgs;
specialArgs = { inherit configfiles; };
modules = [
disko.nixosModules.disko
sops-nix.nixosModules.sops
./configuration.nix
{
nixpkgs.overlays = [
(final: prev: {
bull = import ./bull-pkg.nix {
pkgs = final;
pkgs-unstable = pkgs-unstable;
inherit bullsrc;
};
})
];
}
];
};
};
}
Go 会为所有构建打戳,但在这种情况下它没什么可打的:
- 我们是从目录构建的,而不是从 Go 模块,所以模块版本是
(devel)。 - 打入的 buildinfo 中不包含任何
vcs信息。
下面是 gokrazy/bull 的一个完整示例:
% go version -m \
/nix/store/z3y90ck0fp1wwd4scljffhwxcrxjhb9j-bull-unstable/bin/bull
/nix/store/z3y90ck0fp1wwd4scljffhwxcrxjhb9j-bull-unstable/bin/bull: go1.26.1
path github.com/gokrazy/bull/cmd/bull
mod github.com/gokrazy/bull (devel)
dep github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c
build -buildmode=exe
build -compiler=gc
build -trimpath=true
build CGO_ENABLED=0
build GOARCH=amd64
build GOOS=linux
build GOAMD64=v1
要修复 VCS 打戳问题,请将我的 goVcsStamping overlay 添加到你的 nixosSystem.modules 中:
{
nixpkgs.overlays = [
stapelbergnix.overlays.goVcsStamping
];
}
(如果你像我一样在使用 nixpkgs-unstable,则需要在两处都应用这个 overlay。)
重新构建后,你的 Go 二进制文件就应该会新打上 vcs 构建信息:
% go version -m /nix/store/z8mgsf10pkc6dgvi8pfnbb7cs23pqfkn-bull-unstable/bin/bull
[…]
build vcs=git
build vcs.revision=c0134ef21d37e4ca8346bdcb7ce492954516aed5
build vcs.time=2026-03-22T08:32:55Z
build vcs.modified=false
太棒了!🥳 但是……它是怎么工作的?什么时候会生效?你怎么知道该如何修复你的配置?
我先给你看完整的流程图,然后再解释如何阅读它:
根据你在 .nix 文件中写的内容,你可能会进入 Nix 技术栈中的 3 个相关部分之一:
- Fetcher。Flakes 使用的就是它,非 Flakes 场景也会用到。
- 固定输出 derivation(FOD)。
pkgs.fetchgit就是这样实现的,但 FOD 固有的频繁哈希变动(需要更新sha256这一行)很烦人。 - Copier。它们只是将文件拷贝到 Nix store 中,并不感知 git。
为了实现 VCS 修订版本的打戳,你应该:
- 避免使用 Copier!如果你使用 Flakes:
- ❌ 不要将
url = "/home/michael/dcs"作为 Flake 输入 - ✅ 改用
url = "git+file:///home/michael/dcs"以便让其感知 git
- ❌ 不要将
- 我也会避免使用固定输出 derivation(FOD)。
- 在构建时获取 git 仓库既慢又低效。
- 启用这种方式下 VCS 修订版本打戳所需的
leaveDotGit,效率更低,因为必须以确定性的方式重新构建一个新的 Git 仓库,以保持 FOD 的可复现性。
因此,我们将坚持使用最左边这一列:fetcher。
遗憾的是,默认情况下,使用 fetcher 时,存储在 Nix attrset(构建过程中的内存数据)中的 VCS 修订版本信息并不会进入 Nix store,因此,当 Nix derivation 被求值、Go 编译源码时,Go 完全看不到任何 VCS 修订版本。
我的stapelberg/nix/go-vcs-stamping Nix overlay 模块修复了这个问题,启用这个 overlay 后,你就会走上上图中最左边那条路:皆大欢喜的路径,你的 Go 二进制文件现在都打好戳了!
我的变通方案:Nix git 构建信息 overlay
go-vcs-stamping overlay 是如何工作的?它作为 Nix 和 Go 之间的适配器:
- Nix 在内存中的 attrset 的
.rev字段中跟踪 VCS 修订版本。 - 而 Go 期望通过访问
.git/HEAD文件和执行git(1) 命令,在.git仓库中找到 VCS 修订版本。
因此,该 overlay 通过 3 个步骤让 Go 打上正确的信息:
- 合成一个
.git/HEAD文件,以便 Go 的vcs.FromDir()能检测到 git 仓库。 - 在
PATH中注入一个git命令,它仅实现 Go 所使用的那两个命令,对其他任何命令都会大声报错(以防 Go 更新其实现)。 - 在
GOFLAGS环境变量中设置-buildvcs=true。
完整源码请见go-vcs-stamping.nix。
更优雅的修复方案
关于更优雅地修复这一缺口的方法,请参见Go issue #77020 和Go issue #64162:允许包管理器在调用 Go 工具时注入正确的 VCS 信息。
这样就能让 Nix(或 gokrazy)干净地传递构建信息,而无需像我的 go-vcs-stamping 适配器这样的变通方案。
截至撰写本文时,issue #77020 似乎还没有得到太多关注,仍然处于开放状态。
结论:打上戳!打通链路!报告出来!
我的观点很简单:
打上 VCS 修订版本的戳在概念上很简单,但却非常重要!
例如,如果我提到的那次故障中的生产系统能够报告自己的版本,我们本可以节省数小时的应急处置时间!
遗憾的是,许多环境只标识构建产物(有用,但属于正交的维度),却没有打通 VCS 修订版本的链路(而这要有用得多!),或者至少默认情况下没有。
要修复这个问题,你的行动计划只需 3 个简单步骤:
- 打上戳!在你的程序中包含源码的 VCS 修订版本。
- 这不是什么新点子:i3 的构建自 2012 年起就包含了其git-describe(1) 修订版本!
- 打通链路!在构建/打包时,确保 VCS 修订版本不会丢失。
- 上文“在 NixOS 上获取 VCS 修订版本”的案例研究部分说明了 VCS 修订版本可能丢失的几种原因、哪些路径可行,以及如何修复缺失的链路。
- 报告出来!让你的软件在每一个相关的界面上都打印其 VCS 修订版本,例如:
- 可执行程序:在以
--version运行时报告 VCS 修订版本- 对于 Go 程序,你始终可以使用
go version -m
- 对于 Go 程序,你始终可以使用
- 服务和批处理任务:在启动日志中包含 VCS 修订版本。
- 对外 HTTP 请求:在
User-Agent中包含 VCS 修订版本 - HTTP 响应:在响应头中包含 VCS 修订版本(内部使用)
- 远程过程调用(RPC):在 RPC 元数据中包含修订版本
- 用户界面:在便于调试的显眼位置暴露修订版本。
- 可执行程序:在以
在整个系统中实现“版本可观测性”是一个只需一天、回报极高(高 ROI)的项目。
通过我的 Nix 示例,你已经看到 VCS 修订版本在整个技术栈中都是可用的,但在中间环节可能会丢失。希望我的这些资源也能帮助你快速修复自己的技术栈:
- 我的
stapelberg/nix/go-vcs-stampingoverlay,适用于 Nix / NixOS - 我的
stampit仓库是一个收集示例(以 markdown 内容形式)的社区资源,其中还包含一个 Go 模块,提供了几个让版本报告变得轻而易举的辅助工具。
现在就去为你的程序和数据传输打上戳吧!🚀
随机一篇博客
评论
登录后参与讨论