用途
本技能面向 Go 项目的构建与发布环节,目标是把“发布”做成一个可被验证的承诺:用户运行的二进制就是源码与流水线产出的那一个。核心思路是先做到可复现(任何人都能重新构建出相同结果),再做到可验证(任何人都能核对),最后才自动化发布流程。
参考资料按需加载:references/goreleaser.md 提供完整的 .goreleaser.yaml 和由 tag 触发的发布工作流;references/supply-chain.md 涵盖 SBOM、cosign 签名与验证、来源证明(provenance attestations)以及容器镜像签名。仅在摘要不足以解决问题时再读取对应参考文件。
一、版本标记
优先使用构建信息(build info),它不需要额外标志,也不会与源码产生偏差。Go 1.24+ 从 git 检出构建时,Main.Version 为 tag(如 v1.4.0)或伪版本,vcs.* 设置携带提交哈希与 dirty 标记:
info, _ := debug.ReadBuildInfo()
fmt.Println(info.Main.Version, info.GoVersion)
检查某个二进制由什么构建而来:
go version -m ./bin/api
当发布工具从没有 .git 的归档中构建时,需要注入这些值:-ldflags "-X main.version=v1.4.0 -X main.commit=$SHA"。
不要写入墙上时钟的构建时间——那会让每次构建都唯一。如果需要日期,使用提交时间。
二、可复现构建
相同源码、工具链和标志必须产生逐字节一致的二进制:
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/api ./cmd/api
sha256sum bin/api # 在干净检出中重新构建并比对
-trimpath 会从二进制中移除本机路径。
固定工具链:go.mod 中的 go 与 toolchain 行、CI 中相同的镜像或 setup-go 版本。Go 自 1.21 起其自身工具链是可复现的。
除非确实需要 cgo,否则使用 CGO_ENABLED=0——C 工具链是又一个在不同机器间变化的输入。
构建前执行 go mod download 和 go mod verify,使被篡改的模块缓存导致构建失败。
三、交叉编译
列出全部 GOOS/GOARCH 组合:
go tool dist list
示例:
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -o bin/api-linux-arm64 ./cmd/api
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o bin/api.exe ./cmd/api
纯 Go 可在任意宿主机上交叉编译。使用 cgo 时,每个目标平台都需要对应的 C 交叉编译器;当需要面向多个平台发布时,优先选择纯 Go 依赖(例如纯 Go 的 SQLite 驱动)。只有在所有目标 CPU 都支持时才提升 GOAMD64(v2/v3)——否则二进制在较老的 CPU 上将无法启动。
四、用 GoReleaser 自动化发布
一份配置即可构建所有目标、归档、校验和、SBOM 与签名:
# .goreleaser.yaml(节选——完整文件见 references/goreleaser.md)
version: 2
builds:
- main: ./cmd/api
env: [CGO_ENABLED=0]
flags: [-trimpath]
ldflags: ["-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}"]
mod_timestamp: "{{ .CommitTimestamp }}" # 可复现的文件时间
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
checksum:
name_template: checksums.txt
校验配置与完整干跑:
goreleaser check # 校验配置
goreleaser release --snapshot --clean # 完整干跑,不发布任何内容
在 CI 中由 tag 推送(v1.4.0)触发发布,绝不要在个人笔记本上发布:流水线是唯一记录输入来源的地方。
五、供应链工件
各工件回答的问题与产出方式:
| 工件 | 回答的问题 | 产出方式 |
|---|---|---|
| checksums.txt | 我的下载是否完整无损? | GoReleaser checksum |
| SBOM(SPDX 或 CycloneDX) | 这个二进制里有什么? | syft、GoReleaser sboms |
| 签名包(.sigstore.json) | 是否由本项目的流水线发布? | cosign sign-blob --bundle,CI 中 keyless |
| 来源证明(provenance attestation) | 由哪个工作流、提交和 runner 构建? | actions/attest(SLSA provenance) |
对校验和文件签名,而不是给每个归档单独签名——一个签名即可覆盖全部。Keyless 签名使用 CI 任务的 OIDC 身份,因此没有私钥可泄露或轮换。在发布说明中公布确切的验证命令;没人验证的签名保护不了任何人。具体命令见 references/supply-chain.md。
六、容器镜像
纯 Go 服务使用 ko:无需 Dockerfile,直接从 Go 源码构建,基于 distroless 基础镜像,多架构,带 SBOM,且可复现。
KO_DOCKER_REPO=ghcr.io/acme ko build --bare --platform=linux/amd64,linux/arm64 ./cmd/api
其他情况使用多阶段 Dockerfile:在 golang:<version> 中构建,把二进制复制到 gcr.io/distroless/static:nonroot,以非 root 运行。
在部署中以 digest 引用镜像,并用 cosign 为其签名。镜像体积由 go-binary-size 技能负责。
七、版本管理
tag 使用带 v 前缀的语义化版本:v1.4.0。绝不要移动或删除已发布的 tag——模块代理已缓存它,用户的 go.sum 也固定了其哈希。
对库的破坏性变更应作为新的主版本并使用 /v2 模块路径,而不仅仅是打一个 v2.0.0 的 tag。
有问题的发布通过 go.mod 中的 retract 撤回(见 go-dependency-audit 技能),随后在新的补丁版本中修复。
由 Conventional Commits 生成发布说明(见 git-commit 技能);GoReleaser 的 changelog.groups 会将其归类为功能与修复。
验证清单
- 对发布二进制执行 go version -m,能看到 tag 与 commit
- 同一 tag 的两次干净构建产生相同的 SHA-256
- 使用 -trimpath、CGO_ENABLED=0(或有正当理由的 cgo 构建)、固定工具链
- 无构建时间戳;文件时间来自提交
- 发布来自 tag 触发的 CI 任务,绝不在个人笔记本上
- checksums.txt 已签名;SBOM 已发布;来源已证明
- 发布说明包含 cosign verify-blob / gh attestation verify 命令
- 镜像用 ko 或多阶段构建于 distroless、非 root、以 digest 引用
- 已发布的 tag 绝不移动;有问题的发布予以撤回