「SRE 熟悉 Jenkins」到底是什么能力
英文版:English
Why
「熟悉 Jenkins」写在无数 SRE 的 JD 里,通常被读成一个工具声明:会用 UI,会写 Jenkinsfile,会接 webhook。这个读法只是入场券,不是这行字真正想考察的东西。SRE 的读法从另一个问题出发:这条流水线是所有变更进入生产环境的必经之路,那么它凌晨两点卡死了怎么办,部署到一半挂了怎么办,最糟的情况,它静默地做错了事还报绿,怎么办?
我有机会用一次真实事件检验自己的答案:一次 Jenkins 迁移。把一套安装在环境之间搬家,等于对它体内每一条隐含假设做全量审计。迁移中浮出水面的故障,EOL 的基础系统、失联的上游制品仓库、只活在运行中 pod 里的状态,正是这篇文章判据的全部素材来源。
CI/CD 是生产系统
先说方法论主张,因为其余一切由它推出:绝大多数线上事故由变更引起,所以交付流水线是 SRE 手里可靠性杠杆最高的一个面,也是头号事故源。这一条我持有的是立场,不是中立观察。
三个推论随之落地。流水线值得拥有自己的 SLI,成功率、时长、排队时间,和任何面向用户的服务一样;不测量,流水线的退化就不可见,直到它挡住一次发布。它值得做故障模式分析:每个 stage 挂在一半会发生什么,操作者能不能看出来。它还值得一个容量视角:build agent、依赖缓存、制品仓库都是有自己故障模式的生产依赖,不是背景板。
对 Jenkins 还有一个结构性事实。声明式的 reconcile 天生 idempotent,命令式的 Jenkins pipeline 天生不是:模型里没有任何东西保证一个 job 跑两遍收敛到同一状态。这不是回避 Jenkins 的理由,恰恰是「熟悉 Jenkins」成为一项可靠性工程能力的原因:工具不给你的幂等性,要靠你自己构造出来。
三个判据:工具使用者和系统运维者的分界
流程幂等、可重入
迁移期间,agent 镜像构建在两条独立战线上同时失败:Debian Buster 基础镜像 EOL,apt 仓库整体搬进了 archive;与此同时,若干传递依赖所在的上游 Maven 镜像仓库直接失联。Maven 解析失败,编译失败,Docker 构建跟着失败,一个烂根引发三级连锁。
我为此写的修复脚本遵循同一个形状:先设卫(guard),再备份,然后收敛,最后验证。apt 修复脚本先确认自己确实运行在 Buster 系统上,否则直接空操作退出;对现有配置做带时间戳的备份;然后整体覆写配置,而不是逐行打补丁。覆写到已知良好状态是收敛的:第二次运行产生和第一次相同的终态,跑两遍是安全的,断在中间也没有代价,从头重跑即可。依赖修复脚本的结尾是一个显式的 verify 函数,断言制品此刻确实存在,而不是假设成功。反例就是那种追加行、或假设初始状态干净的修复脚本:跑两遍,一个事故变成两个。
状态可观测、可诊断
同一个 repo 里,每个 fix 脚本配对一个 diagnose 脚本:只读,采集事实(系统版本、源清单、一次 dry-run 更新、对已知 EOL 代号的告警),不改变任何东西。诊断作为独立于修复的 artifact 存在,本身就是要点:它把「先确认故障模式,再动手改变系统」固化成了结构,而不是依赖纪律。
第二个案例是我做的一个小工具,起因是 Jenkins 藏起了自己的状态:UI 一次只给你看一个 job、一个 build、一组参数。工具通过 JSON API 把 job 状态、最近五次 build、参数和 commit ID 聚合到一页,同时覆盖迁移前后两套 Jenkins 环境,并支持用上一次成功 build 的参数一键 replay。它不大,但体现了判据本身:job 状态应该是可提取的数据,而不是靠翻页面拼出来的印象。至于完整的 pipeline SLI 上 dashboard,那是我衡量一套成熟设施的标准,在这里作为判据陈述,不声称这个 repo 已经做到。
配置即代码
这个 repo 里的交付逻辑,约 275 个 pipeline 定义文件加一套可复用步骤的 shared library,全部活在 git 里:可 review,可 diff,以及决定性的一点,可迁移。这次迁移本身就是对这个属性的审计。在 repo 里的东西全部干净地搬了过去;只活在运行中系统里的东西,上游早已死掉的缓存制品、agent pod 里手工调过的 Maven 配置,全部在迁移时刻以故障的形式浮出水面。这就是 pipeline-as-code 和「点 UI 攒出来的雪花 Jenkins」在运维意义上的差别:雪花在定义上不可恢复。把同一判据延伸到 Jenkins 本体,也就是用 JCasC 管理 controller 配置,是我对任何一套自己拥有的 Jenkins 会采用的标准。
自动化本身成为熵的时刻
论点自身要求的反面:自动化不是目的,程度更高也不是单调更好。
迁移留下了一个干净的标本。上游仓库陆续失联的那段时间,构建用的 Jenkinsfile 长出了一批 workaround stage:元数据修复、依赖调试、直接下载兜底。等制品被正式迁移到位之后,这些 stage 全部变成死代码:遮蔽信号,拖长构建,并且保证会迷惑下一个读 pipeline 的人。文档化的修复动作里有一条就是删掉它们。自动化的老化方式和告警规则一模一样:每一段都编码着会静默过期的假设,而一条没人看的自动化路径会在无人察觉中腐化;人工闸门里至少有一个会察觉异常的人。
自动化还会打开新的故障面:凭据集中在流水线够得着的地方,插件供应链,以及脚本本身的腐化。所以我把生产 release 流程里那个人工 review 与 approval 环节读作设计决策而非缺陷:对低频且爆炸半径大的操作,一道有意保留的人工闸门往往是更可靠的那个组件。我为之辩护的排序是:一个逻辑上站得住的流程,可证明能跑、可被观测、可以恢复,胜过一个自动化程度拉满的流程。高频且可逆的,自动化;低频且不可逆的,设闸。
Takeaways
- 「熟悉 Jenkins」应该读作「能把一条交付流水线当生产系统来拥有」:它的故障模式、它的状态、它的恢复路径,而不是它的语法。
- 幂等是其余一切的前提。一个不能安全重跑的流程,就是一个不能自动化、不能重试、不能交接给下一个 oncall 的流程。
- 自动化程度不是指标,它是一笔带着自身熵的交易。可运行、可观测、可恢复在前,配得上的部分再自动化。