6.1. CI/CD 流水线

CI 的目标不是「每次提交都跑一遍脚本」,而是让每个 commit 都能产出一个可验证、可追溯、可部署的制品。CD 的目标不是「自动 apply」,而是把已验证的制品安全地推进到环境里。

层级:

L4 · 实战

难度:

进阶

预计:

50 分钟

前置:

Git、Docker、Kubernetes、Helm/Kustomize

6.1.1. 流水线的六个阶段

阶段

产出

失败时

检出

commit + 依赖缓存

立即失败,不进入构建

构建

镜像 digest + SBOM

阻断合并

测试

单测/集成/E2E 报告

阻断合并

扫描

漏洞/许可证/密钥报告

按阈值阻断

签名

Cosign 签名 + 证明

阻断推送

发布

GitOps commit / Helm values

阻断部署

流水线要遵循「构建一次,处处部署」:dev/staging/prod 使用同一个镜像 digest,环境差异只通过配置注入。

6.1.2. 分支与触发

  • Trunk-based:短分支、频繁合并,适合持续交付;用 feature flag 隔离未完成功能。

  • GitFlow:发布分支多,适合版本化交付,但合并成本高。

  • PR 流水线:构建 + 测试 + 扫描,不推送生产镜像。

  • main 流水线:构建、签名、推送制品,更新 GitOps 仓库。

不要把「合并到 main」和「部署到生产」绑定成同一个动作;中间应有环境晋级与审批。

6.1.3. 一个可落地的流水线

.github/workflows/build.yaml
name: build
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - name: Build
        run: |
          docker build -t ghcr.io/example/demoapp:${{ github.sha }} .
      - name: Test
        run: |
          docker run --rm ghcr.io/example/demoapp:${{ github.sha }} make test
      - name: Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/example/demoapp:${{ github.sha }}
          exit-code: "1"
          severity: CRITICAL,HIGH
      - name: Push
        if: github.ref == 'refs/heads/main'
        run: |
          echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
          docker push ghcr.io/example/demoapp:${{ github.sha }}

生产环境用 digest 部署:

kubectl set image deploy/demoapp demoapp=ghcr.io/example/demoapp@sha256:<digest>

6.1.4. 测试分层

  • 单元测试:最快,覆盖业务逻辑,PR 必跑。

  • 集成测试:数据库/消息队列/外部依赖,可用 Testcontainers 或临时命名空间。

  • E2E:部署到临时集群,验证关键路径;成本高,按需运行。

  • 契约测试:服务间接口变更时,防止消费者被破坏。

  • 策略测试:Kyverno/Gatekeeper 策略用 kyverno test/conftest 验证。

测试要并行、缓存依赖、失败可复现;不要把 E2E 当唯一质量门禁。

6.1.5. 制品与缓存

  • 镜像 tag 用 commit SHA 或语义版本,禁止 latest。

  • 推送后记录 digest、SBOM、签名、扫描报告。

  • 构建缓存用 registry cache 或 GitHub Actions cache,避免每次全量。

  • 依赖锁定:package-lock.json、go.sum、requirements.txt 必须提交。

6.1.6. 密钥与权限

  • CI 使用短生命周期 OIDC 身份(GitHub Actions → 云 IAM),不要长期 AK/SK。

  • 生产部署权限只给流水线的 deploy job,不给所有 PR。

  • 密钥用 Vault/ESO/Sealed Secrets 注入,不写进仓库。

  • Runner 尽量 ephemeral,避免上一个任务的残留污染。

6.1.7. 实验:从 PR 到 GitOps

  1. 在 PR 中修改代码,触发构建 + 测试 + 扫描。

  2. 合并后构建镜像并推送,记录 digest。

  3. 流水线更新 GitOps 仓库中的 kustomization.yaml 或 values.yaml 的 image digest。

  4. Argo CD 检测到 Git 变更,同步到 dev 环境。

  5. 验证通过后,提升到 staging/prod。

6.1.8. 验证清单

  • 每个 commit 都能构建出唯一镜像 digest。

  • PR 流水线包含测试与扫描,失败会阻断合并。

  • 生产部署使用 digest,不使用可变 tag。

  • 镜像已签名,SBOM 已归档。

  • CI 使用 OIDC/短期凭证,无长期密钥。

  • 发布失败能在 5 分钟内回滚。

6.1.9. 排错表

现象

原因

处理

构建成功但部署失败

镜像架构/tag/digest 错

检查 docker manifest 与部署清单

测试在 CI 失败本地通过

环境/依赖/时区差异

用容器化测试环境,锁定依赖

扫描阻断发布

基础镜像漏洞

升级基础镜像或加例外审批

流水线很慢

无缓存/串行任务

并行 job、缓存依赖、分层构建

生产凭证泄漏

密钥进仓库/日志

轮换、改用 OIDC、加 secret scanning

6.1.10. 进阶挑战

  1. 给流水线加 SBOM 生成与 Cosign 签名。

  2. 用矩阵构建多架构镜像(amd64/arm64)。

  3. 把部署改成 GitOps 更新 digest,而不是直接 kubectl set image。

  4. 给流水线加策略测试,阻止无 requests/limits 的清单。

6.1.11. 小结

  • CI 产出可追溯的制品,CD 把制品安全推进环境。

  • 构建一次、处处部署;环境差异用配置,不用重新构建。

  • 测试、扫描、签名是制品的一部分,不是可选步骤。

  • 自动化不等于无人值守;生产晋级要有审批与回滚。

6.1.12. 延伸阅读