16-制品仓库与供应链
镜像仓库只管容器镜像,但一个团队除了镜像还有:Helm Chart、npm/PyPI/Maven 包、二进制 Release、Terraform Module……这些都需要统一管理。本章讲多格式制品仓库和供应链安全两个话题。
制品仓库是什么#
制品(Artifact)是 CI 流水线的所有产出物。制品仓库(Artifact Repository)是统一存储和分发这些产物的系统。
CI 流水线产出的制品类型
├── 容器镜像 → Harbor / ACR / Docker Hub
├── Helm Chart → Harbor / ChartMuseum
├── npm 包 → Nexus / Artifactory / npm registry
├── PyPI 包 → Nexus / Artifactory / PyPI
├── Maven 包 → Nexus / Artifactory / Maven Central
├── Go module → ATHENS / proxy.golang.org
├── 二进制 Release → GitHub Release / Artifactory
└── Terraform Module → Terraform Registry / Artifactory用多个专用仓库(Harbor 管镜像、npm registry 管 JS 包……)还是用一个多格式仓库(Nexus/Artifactory 全管)?这是核心选型。
Nexus#
Sonatype 出品的开源制品仓库,支持多种格式:
| 格式 | 支持 |
|---|---|
| Maven | ✓(最强项) |
| npm | ✓ |
| PyPI | ✓ |
| Docker | ✓ |
| Helm | ✓ |
| NuGet | ✓ |
| RubyGems | ✓ |
| APT/YUM | ✓(OS 包) |
核心能力#
- 代理仓库(Proxy):缓存上游公共仓库(如 npm registry、PyPI),团队拉包快、不依赖外网
- 托管仓库(Hosted):存自己的私有包
- 仓库组(Group):把 proxy + hosted 组合成一个 URL,客户端只配一个地址
团队 npm 包请求
│
▼
Nexus npm-group(仓库组)
├── npm-hosted(私有包) ← 命中则返回
└── npm-proxy(代理 npmjs.com) ← 私有没有则代理上游部署#
# Docker 部署
docker run -d \
--name nexus \
-p 8081:8081 \
-v nexus-data:/nexus-data \
sonatype/nexus3:latest
# 初始密码在容器内
docker exec nexus cat /nexus-data/admin.password适用场景#
- Java/Maven 重度团队:Nexus 的 Maven 支持最完整
- 多语言混合:一个仓库管所有格式,运维简单
- 代理公共仓库:国内访问 npm/PyPI/Maven 慢,Nexus 做代理加速
JFrog Artifactory#
商业产品(有开源版 OSS,格式支持有限),功能比 Nexus 更全:
| 维度 | Nexus | Artifactory |
|---|---|---|
| 价格 | 开源免费 | 商业(OSS 版功能受限) |
| 格式支持 | 多 | 最多(30+) |
| 企业特性 | 弱 | 强(HA、复制、Xray 安全扫描) |
| 性能 | 中 | 强 |
| 适合 | 中小团队 | 大型企业 |
Artifactory 的独特能力:
- Xray:制品安全扫描(CVE + 许可证合规)
- Distribution:跨地域制品分发
- Pipelines:内置 CI/CD(可选)
- SaaS 版本:免运维
选型:小团队用 Nexus,大企业用 Artifactory。两者功能重叠度 80%,核心差异在企业特性。
制品晋级#
"一次构建,多次部署"的工程化落地——同一个制品从 dev → staging → prod 晋级,不重新构建。
晋级模型#
CI 构建出制品 abc123
│
├──► [dev] 自动部署
│
├──► [staging] 晋级 + 人工审批
│
└──► [prod] 晋级 + 人工审批
✓ 同一个制品字节级一致地穿越所有环境
✗ 反模式:每个环境各 build 一次镜像晋级实现#
镜像仓库里用 tag 区分环境:
myapp:abc123-dev ← dev 环境部署后打 tag
myapp:abc123-staging ← staging 晋级时打 tag
myapp:abc123-prod ← prod 晋级时打 tag或用不同仓库项目:
dev/myapp:abc123
staging/myapp:abc123
prod/myapp:abc123晋级操作本质是"复制镜像"(retag):
# 从 dev 晋级到 staging
docker pull harbor.example.com/dev/myapp:abc123
docker tag harbor.example.com/dev/myapp:abc123 \
harbor.example.com/staging/myapp:abc123
docker push harbor.example.com/staging/myapp:abc123或用 Harbor 的复制功能(不依赖本地 docker):
curl -u admin:Harbor12345 -X POST \
"https://harbor.example.com/api/v2.0/replications/executions" \
-d '{"policy_id": <dev-to-staging-policy-id>}'制品晋级 vs GitOps#
现代 GitOps 流水线里,"晋级"的概念被弱化——同一个镜像 tag 贯穿所有环境,靠改 GitOps 仓库里的 newTag 来推进部署,不需要 retag:
qa overlay: newTag: abc123-2026-06-09-10-30
pre overlay: newTag: abc123-2026-06-09-10-30
prod overlay: newTag: abc123-2026-06-09-10-30镜像不变,变的是"哪个环境的 overlay 指向这个 tag"。这比 retag 更纯粹——一个镜像就是它自己,不需要"环境标签"。
SBOM:软件物料清单#
SBOM(Software Bill of Materials)是制品的"成分表"——记录镜像里所有软件组件的来源、版本、许可证。
为什么需要 SBOM#
- 漏洞追踪:某个 CVE 爆出时,能快速查"哪些镜像用了这个库"
- 合规审计:金融、医疗等行业要求软件成分可追溯
- 许可证管理:避免无意中用了 GPL 等限制性许可证
生成工具:syft#
# 安装
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh
# 生成 SBOM(SPDX 格式)
syft myapp:latest -o spdx-json > myapp-sbom.spdx.json
# CycloneDX 格式(更广泛支持)
syft myapp:latest -o cyclonedx-json > myapp-sbom.cdx.json
# 扫描 SBOM 中的漏洞(结合 grype)
grype sbom:./myapp-sbom.spdx.jsonSBOM 作为 OCI Artifact#
把 SBOM 作为 attestation 附加到镜像:
# Cosign 签名并附加 SBOM
cosign attest --key cosign.key \
--predicate myapp-sbom.cdx.json \
--type cyclonedx \
registry.example.com/myapp:latest
# 验证 SBOM
cosign verify-attestation --key cosign.pub \
--type cyclonedx \
registry.example.com/myapp:latest下游可以验证:"这个镜像确实有 SBOM,且 SBOM 没被篡改"。
SLSA:供应链安全等级#
SLSA(Supply-chain Levels for Software Artifacts)是 OpenSSF 的供应链安全等级框架。它不是工具,是评估标准。
下面是 SLSA v1.0 Build Track 的等级(v1.0 起 Build Track 只有 L1–L3;早期 v0.1 的 L4 已移除,其"双人评审 / two-party review"要求归入独立的 Source Track,不再是构建等级)。
等级定义#
| Level | 简述 | 核心要求 |
|---|---|---|
| L0 | 无保障 | 没有任何供应链信号 |
| L1 | 有 provenance | 构建过程输出"我是怎么构建的"声明 |
| L2 | 托管构建服务 | 构建在受信任的托管服务上,provenance 被签名 |
| L3 | 隔离与可验证 | 构建作业相互隔离,provenance 不可伪造 |
Provenance 是什么#
Provenance 是一份结构化声明,描述"这个制品是怎么来的":
{
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/actions/workflow/v1",
"externalParameters": {
"workflow": {
"ref": "refs/heads/main",
"repository": "https://github.com/myorg/myrepo"
}
},
"resolvedDependencies": [
{"uri": "git+https://github.com/myorg/myrepo",
"digest": {"gitCommit": "abcdef1234"}}
]
},
"runDetails": {
"builder": {"id": "https://github.com/actions/runner"},
"metadata": {"startedOn": "...", "finishedOn": "..."}
}
}
}签名 ≠ Provenance#
- 签名(Cosign):证明"这个制品被某人签名过"
- Provenance:证明"这个制品是怎么来的"
SLSA 要求 provenance 本身被签名,形成"可验证的构建声明"。所以 SLSA 实施通常是 Provenance + Cosign 组合。
L1 实现:手写 provenance#
# 构建镜像
docker build -t myapp:v1 .
# 生成 provenance JSON(手工)
cat > provenance.json << EOF
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "myapp", "digest": {"sha256": "abc123..."}}],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {...}
}
EOF
# 用 cosign 附加 provenance
cosign attest --predicate provenance.json \
--type slsaprovenance1 \
myapp:v1L1 的局限:provenance 由构建脚本自己生成,攻击者能伪造。
L2/L3 实现:用官方 generator#
GitHub Actions 用 slsa-github-generator:
jobs:
build:
steps:
- uses: docker/build-push-action@v5
with:
provenance: false # 用 SLSA generator,不用 buildx 自带
provenance:
needs: build
permissions:
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ${{ needs.build.outputs.image }}
digest: ${{ needs.build.outputs.digest }}generator 运行在独立 runner,构建脚本无法污染 provenance 内容——这就是 L3 的"不可伪造"。
验证#
slsa-verifier verify-image \
ghcr.io/myorg/myapp@sha256:abc... \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3或用 Kyverno 在 K8s 准入时验证:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
spec:
validationFailureAction: Enforce
rules:
- verifyImages:
- imageReferences: ["ghcr.io/myorg/**"]
attestors:
- entries:
- keyless:
subject: "https://github.com/slsa-framework/slsa-github-generator/..."
issuer: https://token.actions.githubusercontent.com完整供应链安全闭环#
把本章和前面章节串起来:
① 源码 ──► ② CI 构建 ──► ③ 镜像扫描 ──► ④ 签名 ──► ⑤ SBOM ──► ⑥ Provenance ──► ⑦ 准入验证
Git BuildKit Trivy Cosign syft SLSA generator Kyverno- 源码:受保护分支 + 强制 review
- 构建:BuildKit(rootless)/ Buildah + 多阶段 + 缓存(14 章;kaniko 2025-06 已归档)
- 扫描:Trivy 扫镜像 + SCA 扫依赖(13 章)
- 签名:Cosign keyless 签名
- SBOM:syft 生成 + cosign attest 附加
- Provenance:SLSA generator 生成(L3)
- 准入:Kyverno VerifyImages 验证签名 + SBOM + Provenance
任何一步不通过,K8s 拒绝部署这个镜像。
实战要点#
- 小团队用 Nexus,大企业用 Artifactory。两者功能重叠 80%,核心差异在企业特性。
- 制品晋级优先用 GitOps 模型。改 GitOps overlay 的 newTag 比 retag 更纯粹。
- SBOM 不是负担,是资产。CVE 爆出时能秒查"哪些镜像受影响",没有 SBOM 只能逐个扫。
- SLSA 不要追求一步到位。先 L1(手写 provenance),再靠托管构建平台 + 签名 provenance 达到 L2,核心服务用官方 generator(独立 runner、provenance 不可伪造)上 L3。注意:GitHub Actions +
slsa-github-generator直接就是 L3,不是 L2。 - 供应链安全是组合拳。签名 + SBOM + Provenance + 准入验证,缺一不可。单一工具覆盖不了整个链路。
- Kyverno VerifyImages 是 K8s 准入验签的标准方案。比 Sigstore Policy Controller 配置更直观。
- 国内合规场景:Cosign/SLSA 工具链全部可自建,不依赖境外 SaaS。
小结#
制品仓库和供应链安全是 DevOps 工具链的"最后一公里"。Nexus/Artifactory 解决多格式制品统一管理,制品晋级(或 GitOps newTag)保证"测的=发的",SBOM + SLSA + Cosign + Kyverno 构成供应链安全闭环。
到本章为止,DevOps 工程实践的前三个阶段(理念与协作、持续集成 CI、制品与镜像)全部讲完。下一阶段进入持续部署 CD——怎么把这些制品安全、可控地部署到 K8s 集群。