🐎 第7章 无为而治:Headless 模式与 CI/CD 集成
📄 一句话概括

命令是同一句,变的是外面那层壳:触发怎么写、密钥怎么传、产物怎么留。 真解析两份配置,阶段数完全对得上。所谓「支持三个平台」,代价全在那三处映射上。

📝 本章和前面几章的关系

上一节是 GitHub 那一套,这一节看 GitLab 和 Jenkins。 这一节有真解析:一份 YAML 用解析器读,一份 Jenkinsfile 用正则抽阶段名。

一、GitLab CI 的形状

顶层声明 stages 列表,下面每个作业挂一个 stage,script 里写命令。 产物用 artifacts.paths 声明,流水线会把它们传到下一阶段。 密钥走 CI 变量,在平台界面上配,不进仓库。

它和 GitHub 的思路几乎一样:阶段列表 + 作业 + 产物。

二、Jenkins 的形状

它是 Groovy 脚本:pipeline 块里套 stages,每个 stage 里有 steps。 密钥走 credentials(),从 Jenkins 的凭据库取,同样不进仓库。 产物用 archiveArtifacts 收,不声明就跟流水线一起消失。

它最不一样的地方是「它是代码」——能写 if、能循环,也就能写出别人看不懂的流水线。

三、真解析:两边的骨架

演示里把两份都解析了一遍:GitLab 抽出两个阶段,Jenkins 抽出两个,名字都能对上。 能对上是因为它们描述的是同一件事:先审查、再测试。 所以「跨平台」这件事没有想象中难——难的是那三处映射,不是流程本身。

四、三处必须对照着改的地方

触发:GitHub 用 on、GitLab 用 only 或 rules、Jenkins 用 triggers 块。 密钥:GitHub 用 secrets 上下文、GitLab 用 CI 变量、Jenkins 用 credentials。 产物:GitHub 用 upload-artifact、GitLab 用 artifacts.paths、Jenkins 用 archiveArtifacts。

三行就是全部差异。做法也简单:命令只写一份(放脚本里),三份配置各自调那个脚本。 这一条比「写三份配置」重要得多:命令写三遍,改一次要改三处,早晚会漏。

五、本地脚本这一档别忘了

同一句命令放进一个 shell 脚本,本地也能跑:排查问题时不用推到 CI 上试。 能本地跑是调试的前提。CI 上试一次要等几分钟,本地几秒。 所以落地顺序应该是:先写能本地跑的脚本,再包 GitHub,最后才补另外两个平台。

反过来做,你会在还没验证流程的时候就先花时间处理三个平台的差异。

六、动手 Demo:同一套命令,三种平台各要包一层什么

命令是同一句:跑 CLI、要 JSON 输出。变的是外面那层壳。 这里把 GitLab 的 YAML 和 Jenkins 的 Groovy 各解析一遍,看骨架对不对得上。

d = yaml(open(u"gl.yml", encoding=u"utf-8").read())
stages = d.get(u"stages")

jstages = re.findall(r"stage\('([^']+)'\)", jf)   # Jenkinsfile 里的阶段名

实跑输出:

--------------------------------------------------------------------
同一套命令,三种平台各要包一层什么
--------------------------------------------------------------------

  命令是同一句:claude -p ... --output-format json。
  变的是外面那层壳——触发怎么写、密钥怎么传、产物怎么留。

  ① GitLab CI 的写法
  --------------------------------------------------------------------

  [教材原样 · .gitlab-ci.yml,本沙箱不执行]
  | stages: [review, test]
  | 
  | review:
  |   stage: review
  |   script:
  |     - claude -p "审查这次改动" --output-format json > review.json
  |   artifacts:
  |     paths: [review.json]
  | 
  | test:
  |   stage: test
  |   script:
  |     - pytest -q


  ② Jenkins 的写法
  --------------------------------------------------------------------

  [教材原样 · Jenkinsfile,本沙箱不执行]
  | pipeline {
  |   agent any
  |   environment {
  |     API_KEY = credentials('anthropic-key')
  |   }
  |   stages {
  |     stage('Review') {
  |       steps { sh 'claude -p "审查这次改动" --output-format json > review.json' }
  |     }
  |     stage('Test') {
  |       steps { sh 'pytest -q' }
  |     }
  |   }
  | }


  ③ 真解析:两边的骨架
  --------------------------------------------------------------------
| 平台 | 阶段数 | 阶段名 |
|---|:---:|---|
| GitLab | 2 个 | review、test |
| Jenkins | 2 个 | Review、Test |

  两边的阶段名和数量都能对上——因为它们描述的是同一件事。
  真正不同在别处:GitLab 用 artifacts 声明产物,Jenkins 靠工作目录自己留着;
  GitLab 的密钥走 CI 变量,Jenkins 走 credentials()。

  ④ 三处必须对照着改的地方
  --------------------------------------------------------------------
| 这一项 | GitHub | GitLab | Jenkins |
|---|---|---|---|
| 触发 | on: [pull_request] | only: [merge_requests] | triggers { ... } |
| 密钥 | ${{ secrets.X }} | CI 变量 | credentials('x') |
| 产物 | upload-artifact | artifacts.paths | archiveArtifacts |

  三行就是全部差异。所以「支持三个平台」的代价不在命令,在这三处映射。
  做法也简单:命令只写一份(放脚本里),三份配置各自调那个脚本。

  ⑤ 本地脚本这一档别忘了
  --------------------------------------------------------------------
  同一句命令放进一个 shell 脚本,本地也能跑:排查问题时不用推到 CI 上试。
  能本地跑是调试的前提。CI 上试一次要等几分钟,本地几秒。
  ! 三份配置是形状演示,字段以各平台文档为准;本沙箱没有这些平台,跑不起来。

①两边骨架能对上:GitLab 两个阶段、Jenkins 两个,名字都是审查加测试。因为它们描述的是同一件事。 ②真正的差异在三处:触发(on / only / triggers)、密钥(secrets / CI 变量 / credentials)、 产物(upload-artifact / artifacts.paths / archiveArtifacts)。 ③三行就是全部差异。 做法是命令只写一份放脚本里,三份配置各自调那个脚本——命令写三遍,改一次要改三处,早晚会漏。 ④别忘本地脚本这一档:能本地跑是调试的前提。CI 上试一次要等几分钟,本地几秒。 所以落地顺序是先写本地脚本,再包 GitHub,最后补另外两个平台。

30 秒实验

  1. 给 GitLab 那份加一个「只在 main 分支跑」的规则,重跑解析。它该写在哪一层? 和 GitHub 的 on 差几行?
  2. 把 Jenkinsfile 里的 stage 名改成中文,重跑正则。抽取还成立吗? 这说明「用正则抽结构」的风险在哪?
  3. 把命令从三份配置里抽出来放进一个脚本,重跑一遍。以后再改命令,要改几处?

常见误区

  1. 三份配置各写一遍完整命令。改一次要改三处,早晚漏一处。
  2. 在 Jenkinsfile 里写复杂逻辑。它是代码,能写就能写出没人看得懂的流水线。
  3. 不声明产物。下一阶段拿不到上一阶段的输出,只能重跑一遍。
  4. 密钥写进配置文件。三个平台都支持密钥库,没有理由不用。
  5. 一上来就支持三个平台。先在本地脚本跑通,再包平台,顺序反了会先陷进差异里。
自测三问(点开对答案) 1. 三个平台真正的差异有哪几处? —— 三处:触发怎么写、密钥怎么传、产物怎么留。命令本身是一样的。 2. 为什么命令只该写一份? —— 写三遍就要改三处,早晚漏一处。放脚本里,三份配置各自调它。 3. 为什么落地顺序是「本地脚本、然后一个平台、最后其他」? —— 因为能本地跑才能快调试。先铺三个平台,会在还没验证流程时就先处理平台差异。

三个平台都能跑了,下一节回头补最重要的一课:这些权限开出去,安全怎么兜。

相关笔记