- 网络安全
- 应用安全
- 云原生
- 后端
【免费下载链接】clair
Vulnerability Static Analysis for Containers
Clair 是面向容器的漏洞静态分析工具,其 v4 系列采用"约每三个月发布一次新版本、每个版本积极维护六个月"的节奏。本文以仓库内 Documentation/contribution/releases.md 为骨架,结合.github/workflows下的自动化流水线、Makefile 与 etc 目录下的构建规则,完整还原 Clair 从打 Tag、创建发布分支、生成变更日志,到自动产出源码归档、跨平台二进制与多架构容器镜像的端到端流程。读完本文,你将掌握作为维护者如何亲手切割 Minor/Patch 版本,也能理解发布制品的每一环在仓库中由哪段代码或配置驱动。
发布节奏与维护策略
Clair 的发布策略非常明确:
- 发布周期:新版本(minor release)大约每三个月切割一次;
- 维护窗口:每个发布的版本被积极维护六个月;
- Bugfix 回流:修复类提交应先落在
master(如果适用),然后标记为需要 backport 到某个 minor 版本的发布分支(release branch)。
需要指出的是,文档明确说明"为 release 分支挑选并移植修复提交"的过程目前尚未完全形式化(原文:"The process for doing this is not yet formalized"),这意味着 backport 依赖维护者的判断与人工操作,仓库当前没有对应的自动化机制。这一点从仓库现有 GitHub Actions 列表(.github/workflows)中也能印证:其中只有fast-forward.yml、check-fast-forward.yml这类分支管理辅助工作流,并不存在自动 backport 的 workflow。
版本切割:Minor 与 Patch 两条路线
Clair 的版本号遵循语义化版本(semver)习惯,v4.x.0表示 minor 版本,v4.x.y(y>0)表示 patch 版本。两类版本的操作流程共用同一套工具链,区别只在于是否创建新的发布分支。
Minor 版本:打 Tag + 建分支
切割一个新的 minor 版本需要同时完成两件事:创建带注释的签名 Tag与创建发布分支。文档给出的标准操作如下:
git tag -as v4.x.0 HEAD git push upstream HEAD:release-4.x tag v4.x.0拆解来看:
git tag -as会创建一个 annotated(-a)且带 GPG 签名(-s)的标签,标签消息中应记录版本号与简要说明。使用 annotated 而非 lightweight tag,是为了保证标签对象携带作者、日期与消息,便于工具链(如git describe)正确解析版本信息;- 第二条命令同时推送两条 ref:
HEAD:release-4.x把当前提交推送到名为release-4.x的发布分支(例如release-4.9),tag v4.x.0推送刚创建的标签。
推送完成后,还需要在 GitHub UI 中基于该 Tag 手动创建一个 Release。这一步不可省略,因为后续所有制品发布流程都以此为触发器(见下文"制品自动化"一节)。
Patch 版本:只在发布分支上打 Tag
Patch 版本与 minor 版本的唯一区别是:minor 版本对应的 Tag 只应出现在发布分支上(即release-4.x),且 patch 发布不需要新建分支。操作序列为:
git checkout release-4.x git tag -as v4.x.1 HEAD git push upstream tag v4.x.1注意第三步只推送 Tag,不再推送分支。随后同样需要在 GitHub UI 基于新 Tag 创建 Release。
这里有一个值得强调的约束:minor 版本 Tag(v4.x.0)应当只存在于release-4.x分支上,而不是master/main主线上。结合 prepare-release.yml 中"release 分支只筛选同 minor 版本的提交生成 changelog"的逻辑(见下文),这种分支组织方式保证了每个 minor 系列的历史变更记录干净、独立。
变更日志(Changelog)的生成:Release 之前的准备工作
仓库中存在一个手动触发的辅助工作流 .github/workflows/prepare-release.yml,它在正式打 Tag 之前为发布准备CHANGELOG.md。该工作流通过workflow_dispatch接收两个输入:
branch:准备发布所基于的分支(默认main);tag:将要发布的 Tag 名称。
工作流使用git-chglog工具按指定标签范围生成变更日志,核心逻辑是:
filter_tag="--tag-filter-pattern v4" branch=<输入的分支名> if [[ ${branch%-*} == "release" ]]; then filter_tag="--tag-filter-pattern v${branch#release-}" fi也就是说,如果目标分支是release-4.x形式的发布分支,则只筛选该 minor 版本范围内的提交(例如v4.9);否则在主线只过滤v4前缀的完整历史。生成完毕后,工作流通过peter-evans/create-pull-request自动提交一个标题形如<tag> Changelog Bump的 PR,分支名为ready-<tag>,并启用 sign-off。仓库根目录的 CHANGELOG.md 即这一机制的产物,其头部记录着如v4.9.0 - 2025-12-08的版本条目,并按All、Amqp、Build(Deps)、Chore、Chore(Deps)等类别分组列出提交。
制品自动化:一条由 GitHub Release 驱动的流水线
Clair 的制品发布过程完全自动化,由 GitHub 上的 Release 事件驱动。也就是说,维护者在 UI 中点击"Publish release"的那一刻,后续的源码归档生成、容器镜像构建与推送会全部自动完成。
驱动的核心是 .github/workflows/cut-release.yml。该工作流的触发条件有两类:
on: push: tags: - v4.* workflow_dispatch: {}- 推送
v4.*形式的 Tag(注意分支推送不会触发,只有 Tag push 才会); - 手动触发(
workflow_dispatch,用于演练或补发)。
整个工作流由 7 个 Job 组成,形成一条清晰的依赖链:
config └─→ release-archive ──→ release-binaries ──→ release ──→ publish-container │ (推送 quay 镜像) └─→ publish-binaries(上传 clairctl) └─→ deploy-documentation1. config:集中计算发布元数据
第一个 Job 在quay.io/projectquay/golang:1.25容器中运行,从GITHUB_REF推导出所有下游需要的变量:
version:即 Tag 本身(如v4.9.0);tar_prefix:clair-<tag>/,用于源码归档内的顶层目录前缀;is_prerelease:当 Tag 包含alpha、beta或rc时判定为预发布;image_tag:去掉v前缀后的镜像 Tag(如4.9.0);image_repo:目标仓库(上游自动映射为projectquay/clair);build_go_version/cache_key:由容器内go version推导的构建版本与缓存键。
这些输出通过needs: [config]被后续所有 Job 消费。
2. release-archive:构建完整源码归档
该 Job 先以fetch-depth: 0完整检出仓库,然后执行:
git fetch origin "+${GITHUB_REF}:${GITHUB_REF}" # 修复 checkout action 覆盖 tag 的问题 git archive --prefix 'clair-<tag>/' -o clair.tar "${GITHUB_REF}" go mod vendor tar -rf clair.tar --transform 's,^,clair-<tag>/,' vendor gzip clair.tar mv clair.tar.gz clair-<tag>.tar.gz关键点在于:Clair 的源码归档由git archive生成,而不是把工作目录直接打包——这保证了归档内容严格等于 Tag 指向的提交状态,不包含任何本地未提交的脏文件。由于go mod vendor生成的vendor目录默认不被 git 跟踪,Job 会在归档后把 vendor 目录追加进 tar 包并放到对应的clair-<tag>/前缀下,从而让归档成为一个自包含、可离线构建的完整源码包。该 Job 还会调用git-chglog生成 changelog(手动触发时生成空文件占位),并把clair-<tag>.tar.gz与changelog作为clair-releaseartifact 上传。
3. release-binaries:跨平台编译 clairctl
在config产出的 Go 构建镜像中,该 Job 通过 matrix 组合goarch × goos(arm64/amd64/386/ppc64le/s390x×linux/windows/darwin,并排除不支持的组合如 darwin-386、windows-ppc64le 等),对./cmd/clairctl执行交叉编译:
go build -trimpath -ldflags="-s -w" -buildvcs=false \ -o "clairctl-<goos>-<goarch>" ./cmd/clairctl-trimpath去除构建路径信息,-ldflags="-s -w"剥离符号表与 DWARF,-buildvcs=false禁用 VCS 信息注入(注释指出版本信息应由git archive过程负责)。产物按clairctl-<goos>-<goarch>命名并作为独立 artifact 上传。
4. release:创建 GitHub Release
该 Job 在push事件下执行,通过ncipollo/release-action创建 Release:
- 标题为
<version> Release; - Release 正文(
bodyFile)使用 changelog; prerelease标志来自config的is_prerelease输出;- 将
clair-*归档附加为 Release 资产。
upload_url作为输出暴露给下游,供publish-binaries上传二进制使用。
5. publish-container:构建并推送多架构容器镜像
这是制品分发的重头戏。该 Job 依次执行:
- 下载
clair-releaseartifact 并解包到临时目录作为构建上下文; - 用
docker/setup-qemu-action与docker/setup-buildx-action配置多平台构建能力; - 使用
docker/login-action凭QUAY_USER/QUAY_TOKEN登录quay.io; - 通过
docker/build-push-action一次构建linux/amd64、linux/arm64、linux/ppc64le、linux/s390x四个平台的镜像并推送:
platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x push: true tags: | quay.io/<image_repo>:<image_tag>即最终镜像会被推送到quay.io/projectquay/clair仓库,Tag 为去掉v的版本号(如4.9.0),与文档中"container is pushed to the quay.io/projectquay/clair repository"的描述一致。
- 当
is_prerelease == true时,额外调用.github/actions/set-image-expiration为预发布镜像设置过期时间(QUAY_API_TOKEN),避免 alpha/beta/rc 镜像长期滞留。
6. publish-binaries 与 deploy-documentation
publish-binaries将上一步 matrix 产出的所有clairctl-*二进制通过upload-release-asset追加上传到刚创建的 Release,作为独立的发布资产;deploy-documentation在release完成后触发.github/actions/documentation,把文档部署上线——这也解释了仓库中 Documentation 目录作为 mdBook(见 etc/doc.mk 的booktarget)发布的内容如何随版本更新。
本地复现制品构建:make dist 与 make dist-container
除了完全自动化的 CI 路径,维护者也可以在本地手工构建发布制品。文档明确给出了两个命令及其配置来源:
make dist:生成完整的源码归档;make dist-container:基于该归档生成对应的容器镜像;- 控制这些 target 行为的变量文档化在 etc/config.mk。
make dist 的底层逻辑
查看 etc/dist.mk 可以看到dist的真实定义:
dist: clair-$(VERSION).tar.gz clair-%.tar.gz: vendor/modules.txt tarball=$(subst .gz,,$@) prefix=$(subst .tar.gz,/,$@) $(git_archive) --format tar --prefix "$$prefix" --output "$$tarball" $* ... tar --append --file "$$tarball" --transform "s,^,$${prefix}," ... vendor gzip -n -q -f "$$tarball"它与 CI 中release-archiveJob 的做法完全同构:git archive打出干净源码树,再追加vendor目录,最后 gzip 压缩。VERSION的默认值定义在 etc/config.mk:
VERSION ?= $(shell git describe --match 'v*' --long | sed 's/\(.\+\)-\([0-9]\+-g[a-f0-9]\)/\1+\2/')即默认从git describe推导形如v4.9.0-3-gabcdef1的版本(处理成v4.9.0+3-gabcdef1),也可以通过环境变量VERSION显式覆盖。
make dist-container 与容器构建
etc/container.mk 定义了容器相关 target:
container:基于当前工作树构建clair.oci;container-build:构建并把镜像docker load进本地容器引擎;dist-container:即clair-$(VERSION).oci,它依赖clair-$(VERSION).tar.gz,先解包归档再调用 buildctl 构建,保证"从发布归档构建"而非"从工作树构建";dist-clairctl:构建所有上游支持平台的 clairctl 二进制。
所有容器构建都经由buildctl(BuildKit 客户端)完成,etc/config.mk中相关的可调变量包括:
| 变量 | 默认值 | 说明 |
|---|---|---|
docker | 自动探测 podman/docker | 容器引擎命令 |
buildctl | 自动探测或go run兜底 | BuildKit 客户端 |
VERSION | git describe推导 | 归档/镜像使用的版本 |
IMAGE_NAME | localhost/clair:latest | buildctl 输出镜像名,可用逗号分隔多名称 |
CONTAINER_PLATFORMS | amd64 arm64 ppc64le s390x | 构建的架构(OCI 记法,OS 恒为 linux) |
CLAIR_VERSION/GO_VERSION/GOTOOLCHAIN/SOURCE_DATE_EPOCH | 未设置时跳过 | 透传给 buildctl 的构建参数,前三个用于 Dockerfile |
container.mk还会把--opt platform=linux/<arch>展开为CONTAINER_PLATFORMS各架构,并在GITHUB_ACTIONS环境下启用type=gha的构建缓存。Dockerfile 方面,Dockerfile 采用多阶段构建:build阶段在quay.io/projectquay/golang镜像中交叉编译./cmd/...,ctl阶段从 scratch 导出 clairctl,最终阶段基于ubi8/ubi-minimal以nobody:nobody用户运行,默认ENTRYPOINT ["/usr/bin/clair"]、EXPOSE 6060、环境变量CLAIR_CONF=/config/config.yaml与CLAIR_MODE=combo。
实操核对清单:一次完整的 Minor 发布
把文档与仓库实现串起来,一次完整的 minor 发布应包含以下步骤:
- 在主线完成本周期功能与修复(提交遵循仓库的提交规范,可参考 Documentation/contribution/commit_style.md);
- (可选)手动触发 prepare-release.yml,输入分支与目标 Tag,合并它生成的 Changelog PR,让 CHANGELOG.md 就绪;
- 在最新提交上打签名标签并推送分支与标签:
git tag -as v4.x.0 HEAD git push upstream HEAD:release-4.x tag v4.x.0 - 在 GitHub UI 基于
v4.x.0创建 Release(正文即 changelog); - 观察 cut-release.yml 自动执行:生成
clair-v4.x.0.tar.gz、交叉编译全部 clairctl 二进制、推送quay.io/projectquay/clair:4.x.0多架构镜像、部署文档; - 之后的六个月维护期内,修复提交先落主线,再人工挑选移植到
release-4.x分支,按 Patch 流程滚动发布。
Patch 发布则简化为:git checkout release-4.x→git tag -as v4.x.1 HEAD→git push upstream tag v4.x.1→ UI 创建 Release,其余全部交给流水线。
结语
Clair 的发布体系呈现出"人工只负责打 Tag 与点按钮、其余全部自动化"的设计思路:git tag与 UI Release 是唯一的两个人工动作,其后源码归档、changelog 生成、跨平台二进制、多架构容器镜像与文档发布均由 cut-release.yml 串联完成;而 etc/config.mk、etc/dist.mk、etc/container.mk 与 Makefile 共同保证了 CI 与本地行为的一致性——无论是想参与发布,还是只想本地复现一份与线上完全相同的发布归档,make dist与make dist-container都是最直接的入口。发布本身的技术门槛很低,真正的功夫在于维护期内对 release 分支的持续、规范的 bugfix 回流管理。
- 网络安全
- 应用安全
- 云原生
- 后端
【免费下载链接】clair
Vulnerability Static Analysis for Containers
相关推荐
LobeHub 版本发布工作流:Minor/Patch 双轨自动化与 GitHub Release 编写规范
LobeHub 版本发布工作流:Minor/Patch 双轨自动化与 GitHub Release 编写规范 本文以 LobeHub 仓库内的 version
人工智能AI 应用大模型AI Agent多智能体工具调用前端后端Rook 版本发布全流程指南:从 Minor Release 分支创建到 Release Artifacts 发布
Rook 版本发布全流程指南:从 Minor Release 分支创建到 Release Artifacts 发布 本文基于 Rook 仓库 build/rel
云原生存储容器编排运维LobeHub Minor Release 工作流实战指南:从 canary 分支到 v2.2.0 的自动化发布全流程
LobeHub Minor Release 工作流实战指南:从 canary 分支到 v2.2.0 的自动化发布全流程 本指南以 LobeHub 仓库中的 Mi
人工智能AI 应用大模型AI Agent多智能体工具调用前端后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考