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 是不可变引用。为了教学可读性,不能把占位符误称为可直接运行文件。
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 级别授予最小权限:
permissions:
contents: read
packages: write使用短生命周期的 GITHUB_TOKEN 登录 GHCR,不在日志里打印 token。镜像至少打 commit SHA 标签,并记录 push 返回的 digest。:latest 可以作为人类便利别名,但部署不能只靠它识别版本。
- 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 403 | job 的 packages: write、包与仓库访问关系、事件来源 |
| secret 为空 | secret scope、environment 门禁、fork 事件限制 |
| 两次部署互相覆盖 | environment 与 concurrency 策略 |
GitHub environment、审批与并发策略会受仓库方案和账户计划影响,实施前核对 GitHub 部署环境文档;云凭据优先参考 OIDC 配置指南。
远程 registry 与生产权限因项目而异。先在本地把制品、镜像、健康验证和回滚判断练清楚。