路线图

16-制品仓库与供应链

星辉 2026-07-02 阅读 4 min 832 字 路线图
16-制品仓库与供应链 封面

镜像仓库只管容器镜像,但一个团队除了镜像还有:Helm Chart、npm/PyPI/Maven 包、二进制 Release、Terraform Module……这些都需要统一管理。本章讲多格式制品仓库和供应链安全两个话题。

制品仓库是什么#

制品(Artifact)是 CI 流水线的所有产出物。制品仓库(Artifact Repository)是统一存储和分发这些产物的系统。

text
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,客户端只配一个地址
text
团队 npm 包请求
    │
    ▼
Nexus npm-group(仓库组)
    ├── npm-hosted(私有包)       ← 命中则返回
    └── npm-proxy(代理 npmjs.com) ← 私有没有则代理上游

部署#

bash
# 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 更全:

维度NexusArtifactory
价格开源免费商业(OSS 版功能受限)
格式支持多最多(30+)
企业特性弱强(HA、复制、Xray 安全扫描)
性能中强
适合中小团队大型企业

Artifactory 的独特能力:

  • Xray:制品安全扫描(CVE + 许可证合规)
  • Distribution:跨地域制品分发
  • Pipelines:内置 CI/CD(可选)
  • SaaS 版本:免运维

选型:小团队用 Nexus,大企业用 Artifactory。两者功能重叠度 80%,核心差异在企业特性。

制品晋级#

"一次构建,多次部署"的工程化落地——同一个制品从 dev → staging → prod 晋级,不重新构建。

晋级模型#

text
CI 构建出制品 abc123
    │
    ├──► [dev] 自动部署
    │
    ├──► [staging] 晋级 + 人工审批
    │
    └──► [prod] 晋级 + 人工审批

✓ 同一个制品字节级一致地穿越所有环境
✗ 反模式:每个环境各 build 一次

镜像晋级实现#

镜像仓库里用 tag 区分环境:

text
myapp:abc123-dev       ← dev 环境部署后打 tag
myapp:abc123-staging   ← staging 晋级时打 tag
myapp:abc123-prod      ← prod 晋级时打 tag

或用不同仓库项目:

text
dev/myapp:abc123
staging/myapp:abc123
prod/myapp:abc123

晋级操作本质是"复制镜像"(retag):

bash
# 从 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):

bash
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:

text
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#

bash
# 安装
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.json

SBOM 作为 OCI Artifact#

把 SBOM 作为 attestation 附加到镜像:

bash
# 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 是一份结构化声明,描述"这个制品是怎么来的":

json
{
  "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#

bash
# 构建镜像
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:v1

L1 的局限:provenance 由构建脚本自己生成,攻击者能伪造。

L2/L3 实现:用官方 generator#

GitHub Actions 用 slsa-github-generator:

yaml
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 的"不可伪造"。

验证#

bash
slsa-verifier verify-image \
  ghcr.io/myorg/myapp@sha256:abc... \
  --source-uri github.com/myorg/myrepo \
  --source-tag v1.2.3

或用 Kyverno 在 K8s 准入时验证:

yaml
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

完整供应链安全闭环#

把本章和前面章节串起来:

text
① 源码 ──► ② CI 构建 ──► ③ 镜像扫描 ──► ④ 签名 ──► ⑤ SBOM ──► ⑥ Provenance ──► ⑦ 准入验证
  Git       BuildKit      Trivy          Cosign     syft       SLSA generator   Kyverno
  1. 源码:受保护分支 + 强制 review
  2. 构建:BuildKit(rootless)/ Buildah + 多阶段 + 缓存(14 章;kaniko 2025-06 已归档)
  3. 扫描:Trivy 扫镜像 + SCA 扫依赖(13 章)
  4. 签名:Cosign keyless 签名
  5. SBOM:syft 生成 + cosign attest 附加
  6. Provenance:SLSA generator 生成(L3)
  7. 准入: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 集群。