跳到内容

第 0 卷 / 第 8 章 / 第 2 课

8.2 让 CI 重复可验证步骤 ​

前置知识:从源码到可运行制品

预计时间:20 分钟

阅读方式:workflow 是权限结构模板,action SHA 占位符不能直接运行

完成标志:能区分 CI、持续交付和持续部署,并审读最小权限的 GitHub Actions workflow

你手动走通了构建、验证、制镜像和清理。第二天只改一行代码,又要重复整套命令。适合自动化的正是这些可重复步骤;权限和放行条件仍要由人明确设计。

三个词不要合并 ​

  • 持续集成(CI):频繁合并变更,并自动编译、测试和检查。
  • 持续交付(Continuous Delivery):候选版本始终达到可部署状态,生产发布可以需要人工批准。
  • 持续部署(Continuous Deployment):通过全部门禁后自动进入生产。

CD 可能指后两者,讨论时要写全称。

先做只读 CI ​

下面是结构模板。<FULL_COMMIT_SHA> 必须替换成对应官方 action 发布版本的完整 commit SHA;GitHub 指出,完整 SHA 是不可变引用。为了教学可读性,不能把占位符误称为可直接运行文件。

yaml
name: ci

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<FULL_COMMIT_SHA>
        with:
          persist-credentials: false
      - uses: actions/setup-java@<FULL_COMMIT_SHA>
        with:
          distribution: temurin
          java-version: "17"
          cache: maven
      - run: ./mvnw --batch-mode verify

把完整 workflow 放在 .github/workflows/ci.yml;示例假定仓库已提交可执行的 Maven Wrapper、POM、源码与测试。checkout 不保留 Git 凭据,降低后续构建脚本取得它的机会;ubuntu-latest 仍会变化,定位环境差异时要记录 runner 镜像和 JDK 版本。

CI 不需要 packages: write,也不应拿生产秘密。pull request 中的不可信代码可能读取工作目录、网络和 runner 状态;特别谨慎对待 fork 与 self-hosted runner。

发布镜像是单独权限域 ​

只在受保护分支或发布标签上运行发布 job,并在 job 级别授予最小权限:

yaml
permissions:
  contents: read
  packages: write

使用短生命周期的 GITHUB_TOKEN 登录 GHCR,不在日志里打印 token。镜像至少打 commit SHA 标签,并记录 push 返回的 digest。:latest 可以作为人类便利别名,但部署不能只靠它识别版本。

yaml
- name: Push an already built image
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    IMAGE_NAME: ghcr.io/owner/repo
  run: |
    printf '%s' "$GH_TOKEN" | docker login ghcr.io \
      --username "$GITHUB_ACTOR" --password-stdin
    docker push "$IMAGE_NAME:sha-${GITHUB_SHA}"

这是发布 job 的 steps 片段,前一步必须已构建并标记同名镜像。GITHUB_ACTOR、GITHUB_SHA 由平台提供,GH_TOKEN 则必须像示例这样显式映射;平台不会自动创建这个别名。owner/repo 要换成允许发布的小写镜像路径。参见 GitHub Token 用法。不要把真实 token 写进 shell 历史或仓库。

部署 job 需要门禁 ​

生产 job 应引用 GitHub environment,例如 production。环境可以限制分支、要求审批,并在门禁通过后才提供 environment secrets。再配以目标环境为单位的 concurrency,避免两个生产部署相互覆盖。部署默认应让进行中的发布完成,显式使用 cancel-in-progress: false;只有确认中断和恢复安全时才取消进行中的部署。它不是发布队列的完整替代品,还要核对待运行任务被替换的规则,见 GitHub 并发文档。

部署前要有:候选 digest、迁移兼容性、回滚版本、健康检查、观测窗口和负责人。部署后先验证实例健康、关键请求和错误率,再扩大流量。

供应链边界 ​

  • 第三方 action 固定完整 commit SHA,并验证 SHA 来自原仓库。
  • GITHUB_TOKEN 只授予 job 所需 scope;未列权限会变为 none。
  • 不把长生命周期云密钥当首选;平台支持时使用 OIDC 换取短期凭据。
  • artifact attestation 能证明由哪个 workflow、commit 和事件构建,但不证明制品没有漏洞。
  • 生产部署尽量引用 digest,并保留 commit、测试、SBOM、签名和审批记录。

常见失败 ​

现象先查
workflow 不触发路径 .github/workflows/、事件、分支过滤、YAML
本地过、CI 失败JDK/Maven 版本、锁定依赖、文件大小写、时区和未提交文件
GHCR 403job 的 packages: write、包与仓库访问关系、事件来源
secret 为空secret scope、environment 门禁、fork 事件限制
两次部署互相覆盖environment 与 concurrency 策略

GitHub environment、审批与并发策略会受仓库方案和账户计划影响,实施前核对 GitHub 部署环境文档;云凭据优先参考 OIDC 配置指南。

远程 registry 与生产权限因项目而异。先在本地把制品、镜像、健康验证和回滚判断练清楚。

→ 下一步:本地发布实验

Built with VitePress | Software Systems Atlas