读书笔记 · 约 15,416 字 · 预计推送 2 部分

CI/CD 最小闭环精读:检查、测试、构建、发布、回滚与制品留存 —— 结合个人项目 未开始

目录

题记与说明

主题研究 | 精读日期:2026-11-01 | 精读员:#3 | 前缀:knowledge-x8tjinj_-3
交付物:七维提取 + 个人项目最小闭环设计(第七节)

0. 来源清单与检索方式(网络可用,全部在线精读)

#来源类型/年份覆盖环节
1Martin Fowler, *Continuous Integration*方法原典文章(持续更新)检查、测试、闭环的定义与纪律
2Jez Humble, "Deployment pipeline anti-patterns"《Continuous Delivery》作者博文,2010构建、制品唯一性、管线形态
3Humble & Farley, "What Is a Deployment Pipeline?"(InformIT 节选)图书节选,2010管线模式总纲(仅首页可读,见局限)
4Google SRE Book, Ch.8 *Release Engineering*图书章节,2016发布、金丝雀、回滚、审计留存
5GitHub Docs, "Storing and sharing data from a workflow"官方文档(实时)制品留存、retention-days、制品 vs 缓存
6GitHub Docs, "About releases" + Kubernetes Docs, *Deployments*(回滚部分)官方文档发布物、回滚机制、历史保留

检索执行记录(本会话实际运行,2026-11-01):


一、核心问题

本主题回答的实际问题是:一个人的项目,如何用最小成本建立"每次提交都被自动验证、每次发布都可追溯可撤销"的信任闭环? 拆开是四个子问题:

  1. 闭环的最小单位是什么——检查(lint/类型检查)、测试、构建、发布、回滚、制品留存这六件事,哪些是骨架、哪些是后期加装?Fowler 给出的判定:闭环完成的标志不是"代码写完了",而是集成构建变绿:"Only once this integration build is green can the developer consider the integration to be complete."
  2. 为什么"构建一次"如此重要——测试通过的东西和你部署出去的东西必须严格是同一个制品,否则测试在为不存在的东西背书。这是 Humble 反模式一文的中心论点:commit stage 应产出 "an environment-independent packaged version of your application that is used for all later stages of the pipeline (including release)"。
  3. 回滚靠什么——不是靠 revert 代码重走一遍管线,而是靠"把指针拨回上一个好制品"。SRE 书的 MPM label 机制和 K8s 的 revision 机制都是同一思想:回滚是制品层面的操作,前提是历史制品还留着。
  4. 留存留什么、留多久——诊断性产物(日志、覆盖率、失败截图)与分发性制品(二进制、镜像、发布包)的生命周期不同:前者过期即弃,后者是回滚与追溯的锚点,必须长期留存。

对个人项目还有一层放大效应:没有队友兜底、没有值班表,中断成本全部由自己承担,所以"main 随时可发布、出事能一分钟回退"比团队场景更需要纪律化。

二、主要主张

  1. CI 的定义是"闭环"而非"某个工具":Fowler 定义 CI 为 "a software development practice where each member of a team merges their changes into a codebase together with their colleagues changes at least daily",每次集成被 "verified by an automated build (including test) to detect integration errors as quickly as possible"。单人项目把"队友的变更"换成"三天前自己写的变更",闭环结构不变。
  2. 管线要"宽不要长"(Humble):"Make your pipeline wide, not long: reduce the number of stages as much as possible, and parallelize each stage as much as you can." commit stage 应在几分钟内完成("ten is the absolute maximum"),因为 "Nothing sucks the blood of Continuous Integration more than a build that takes a long time."(Fowler)
  3. 单一制品贯穿到底:构建在管线头做一次,之后所有阶段(包括发布)消费同一个环境无关的制品;在任何阶段重新构建都会破坏"测过的即部署的"这个保证。
  4. 构建必须 hermetic/可复现:SRE 书:"Our builds are hermetic, meaning that they are insensitive to the libraries and other software installed on the build machine." 两个人在同一 revision 上构建应得到相同结果——这是"任何时刻能重建任何历史版本"的前提。
  5. 频繁发布是减风险手段:"frequent releases result in fewer changes between versions. This approach makes testing and troubleshooting easier."(SRE)小步发布让回滚粒度更细、排查面更窄;全绿即发布("Push on Green")是极端形态。
  6. 回滚是一等公民且必须是制品操作:SRE 用包管理器 label 倒转(dev/canary/production 标签自动移动)实现"拨指针";K8s 用 kubectl rollout undo [--to-revision=N] 基于 revision 历史。二者共同点:可回滚性 = 历史制品留存 + 稳定标识 + 原子切换指针。
  7. 制品留存是分层的(GitHub 文档):CI artifacts 默认 90 天("By default, GitHub stores build logs and artifacts for 90 days, and this retention period can be customized"),用于诊断与 job 间传递;Release assets 基于 Git tag,是面向用户的长期分发物("Releases are based on Git tags, which mark a specific point in your repository's history")。两者不可互替:"Use caching when...build dependencies...Use artifacts when...built binaries or build logs."
  8. 可视性与工作流分离:"The important thing is not to conflate your workflow with your approval process"(Humble);单人项目里审批对象是自己,这条退化为:所有状态(检查/测试/构建/发布)必须在一处可见,而不是散落在本地终端历史里。

三、方法

四、数据与证据(关键原文引述)

4.1 闭环纪律(Fowler, *Continuous Integration*)

"Continuous Integration is a software development practice where each member of a team merges their changes into a codebase together with their colleagues changes at least daily." … "verified by an automated build (including test) to detect integration errors as quickly as possible."

11 条实践清单(原文提取):单一版本控制主线、自动化构建、自测试构建、每人每天提交主线、每次 push 触发构建、立即修复坏构建、保持构建快速、隐藏进行中的工作、在生产环境的克隆中测试、所有人可见结果、自动化部署。

坏构建的处理顺整引用了 Kent Beck:"nobody has a higher priority task than fixing the build";并给出最省力方案:"Usually the best way to fix the build is to revert the faulty commit from the mainline"——这正是个人项目回滚的第一层:先 revert 恢复 main 健康,再慢慢修。

测试哲学:"Imperfect tests, run frequently, are much better than perfect tests that are never written at all.";"any test failing is enough to fail the build, 99.9% green is still red."——检查/测试环节的门槛是二值的。

CI/CD 边界:"The aim of Continuous Delivery is that the product should always be in a state where we can release the latest build."——发布环节的预备状态是"随时可发",发布本身降格为按按钮。

4.2 管线形态与制品唯一性(Humble 反模式 + InformIT 管线总纲)

"Make your pipeline wide, not long: reduce the number of stages as much as possible, and parallelize each stage as much as you can."
commit stage(单元测试 + 代码质量分析)应"在几分钟内"完成,"ten is the absolute maximum";并且应产出 "an environment-independent packaged version of your application that is used for all later stages of the pipeline (including release)"。
"The deployment pipeline is the key pattern that enables continuous delivery." … 部署 "even to production, is often done by a simple click of a button."(InformIT,仅导言页,见局限)
"Prefer visibility to locking down: give people the information they need to make informed decisions, rather than constraining them."

4.3 发布工程与回滚(Google SRE Book Ch.8,全文精读)

"Running reliable services requires reliable release processes. Site Reliability Engineers (SREs) need to know that the binaries and configurations they use are built in a reproducible, automated way so that releases are repeatable and aren't 'unique snowflakes.'"
"Our builds are hermetic, meaning that they are insensitive to the libraries and other software installed on the build machine. Instead, builds depend on known versions of build tools… and dependencies."
"We have embraced the philosophy that frequent releases result in fewer changes between versions. This approach makes testing and troubleshooting easier."(部分团队采用 "Push on Green","deploy every build that passes all tests")

回滚机制(label 倒转):

"Labels can be applied to an MPM package to indicate a package's location in the release process (e.g., dev, canary, or production). If you apply an existing label to a new package, the label is automatically moved from the old package to the new package."

回滚就是把 production 标签指回旧版本包——不改代码、不重新构建。

测试对象是打包制品而非源码:"we use an independent testing environment that runs system-level tests on packaged build artifacts";发布时刻重跑测试留痕:"During the release process, we re-run the unit tests using the release branch and create an audit trail showing that all the tests passed."

审计留存与制品同归档:"Our automated release system produces a report of all changes contained in a release, which is archived with other build artifacts." … "A report of all changes since the last release is created."

结论句:"Releases can be as painless as simply pressing a button."

4.4 制品留存(GitHub Docs)

"Artifacts allow you to persist data after a job has completed, and share that data with another job in the same workflow."
"By default, GitHub stores build logs and artifacts for 90 days, and this retention period can be customized." … "The retention period for a pull request restarts each time someone pushes a new commit to the pull request."
"The retention-days value cannot exceed the retention limit set by the repository, organization, or enterprise."
"Use caching when you want to reuse files that don't change often between jobs or workflow runs, such as build dependencies from a package management system. Use artifacts when you want to save files produced by a job to view after a workflow run has ended, such as built binaries or build logs."

v4 制品不可变(跨 job 传递时"artifacts are considered immutable in v4",改数据需换名字重新上传);run 内 "You can only download artifacts that were uploaded during the same workflow run.";进阶供应铟能力:"Artifact attestations enable you to create unfalsifiable provenance and integrity guarantees for the software you build."

4.5 发布物(GitHub Docs, About releases)

"Releases are deployable software iterations you can package and make available for a wider audience to download and use." … "Releases are based on Git tags, which mark a specific point in your repository's history."

配额:每个 release 最多 1000 个 assets、单文件 <2 GiB;"There is no limit on the total size of a release, nor bandwidth usage." 自动附源码 zip/tarball;release notes 可按模板自动生成。

4.6 回滚机制与历史保留(Kubernetes Docs)

"By default, all of the Deployment's rollout history is kept in the system so that you can rollback anytime you want (you can change that by modifying revision history limit)."

命令:kubectl rollout undo deployment/nginx-deployment(回上一版)、kubectl rollout undo deployment/nginx-deployment --to-revision=2(回指定版)、kubectl rollout history deployment/nginx-deployment(查看历史)。

"You can set .spec.revisionHistoryLimit field in a Deployment to specify how many old ReplicaSets for this Deployment you want to retain. The rest will be garbage-collected in the background. By default, it is 10."

留存即回滚能力的生死线:

"The configuration of each Deployment revision is stored in its ReplicaSets; therefore, once an old ReplicaSet is deleted, you lose the ability to rollback to that revision of Deployment." … "Explicitly setting this field to 0, will result in cleaning up all the history of your Deployment thus that Deployment will not be able to roll back."

五、结论

  1. 最小闭环 = 一条管线 + 一个制品 + 一个回滚按钮 + 两层留存。检查与测试是闭环的验证层(二值门槛,绿即通过);构建是闭环的铸币机(一次性铸出唯一制品);发布是制品获得稳定标识(tag/release/label)并上线的过程;回滚是拨回旧制品的指针操作;制品留存同时支撑前三者的证据链与回滚的可能性。
  2. 闭环的信任来自"制品不换手":从 commit stage 产出环境无关制品起,测试、发布、回滚的对象都是它。任何一环重新构建,闭环的信任链即断裂。
  3. 可回滚性不是发布时才考虑的事:它是留存的函数——90 天 artifacts、release assets、revisionHistoryLimit,留多久决定了能回多远(K8s 明示:历史删了就回不去)。
  4. 对单人项目,闭环的意义从"防集成冲突"转为"对未来的自己负责":单人集成冲突少,但"三天前的我"与"今天的我"同样会互相破坏;main 随时可发布 + 一分钟回滚,是单人维护项目能在中断后快速恢复的结构性保障。

六、局限

七、对我的启发:个人项目最小闭环设计(综合各来源)

7.1 六环节的最小充分实现(单人规模)

环节最小实现来源依据
检查push 触发 lint + 类型检查,与本地 pre-commit 同一套命令(复用既有 uv/Ruff/pyright 与 pnpm/Biome 模板笔记的配置)Fowler:自测试构建;Humble:commit stage ≤10 分钟
测试同一 workflow 内跑 pytest/vitest,失败截图/覆盖率作为 artifact 上传Fowler:"any test failing is enough to fail the build";GitHub artifacts 文档列举 "test results, failures, and screenshots"
构建只在管线头构建一次,产出 dist/镜像并 upload-artifact(带 retention-days),后续所有 job 下载它Humble:"environment-independent packaged version… used for all later stages (including release)";SRE:hermetic 构建
发布tag push 触发 release job:从 artifact 下载制品 → 生成 release notes → 附到 GitHub ReleaseGitHub:releases 基于 tag,assets 无总量上限;SRE:报告与制品同归档
回滚部署对象是不可变制品(镜像/包),回滚 = 把部署指针指回上一个 release 的制品;代码层的 fallback 是 git revert 恢复 mainSRE:MPM label 倒转;K8s:rollout undo;Fowler:revert 是修坏构建的首选
留存CI artifacts 短留存(如 7–14 天,省配额);release assets 长期;绝不设 0GitHub:默认 90 天、retention-days 可调;K8s:历史删除即失去回滚能力

7.2 单 workflow 骨架(综合上述文档示例改写,非逐字引用)

name: ci
on:
  push: { branches: [main] }
  pull_request:
jobs:
  check-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make check   # lint + typecheck,与本地 pre-commit 同一套
      - run: make test    # 测试失败即整条构建失败(二值门槛)
      - run: make build   # 只在此处构建一次
      - uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
          retention-days: 14   # 短留存的诊断/回滚窗口
  release:
    if: startsWith(github.ref, 'refs/tags/')   # tag 触发
    needs: check-test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with: { name: "dist-${{ github.sha }}" }  # 用的是测过的那个制品
      - run: gh release create "${{ github.ref_name }}" dist/* --generate-notes

要点:needs 串行保证"绿了才发布";制品名带 github.sha 实现制品↔源码的唯一绑定(对应 SRE 的 build ID);发布不从源码重新构建(对应 Humble 的"一次构建贯穿")。

7.3 三条纪律

  1. 制品唯一性纪律:能下载制品的场景,禁止重新构建——测试报告、发布、回滚全部消费同一制品;制品用不可变标识(SHA/tag)命名。
  2. 回滚演练纪律:K8s 文档的教训("once an old ReplicaSet is deleted, you lose the ability to rollback")迁移到个人项目 = 定期确认上一个好制品还在、还能部署(与既有"备份与恢复演练"笔记同构:没有演练过的回滚等于没有回滚)。
  3. main 可发布纪律:坏构建最高优先级修(revert 优先),单人项目里等价于"睡前 main 必须是绿的",否则中断恢复成本不可控。

7.4 与既有知识体系的接口


附:七维速览

维度一句话
核心问题单人项目如何用最小成本做到"每次提交被验证、每次发布可追溯、出事能回退"
主要主张闭环=一条管线+一个制品+一个回滚按钮+两层留存;测试过的制品就是部署的制品
方法概念原典×工程方法书×官方文档三层互证,按环节提取引语,失败来源如实降级
数据与证据Fowler/Humble/SRE/GitHub/K8s 六源原文引述(§4.1–4.6),90天默认留存、revisionHistoryLimit=10 等硬数字
结论信任来自"制品不换手";可回滚性是留存的函数;单人项目闭环从防冲突转为对未来的自己负责
局限InformIT 只读到导言;Fly/Vercel 官方回滚文档 404 未引;发布健康验证超出本主题
对我的启发§7:单 workflow 骨架 + 三条纪律(制品唯一、回滚演练、main 可发布)+ 与既有四篇笔记的接口
← 资料库今日播报 →