前阵子公司内部讨论一个挺有意思的问题:有人想在 CI 流水线里一次性塞进去上百万条命令,用于模拟极端负载下的批量任务回放。他们问我的第一句话就是“打包 100 万条命令进 Pipeline 会怎样?”我当时第一反应是劝退,但后来想想,这个问题本身非常值得拆开揉碎讲清楚。它不仅涉及 Pipeline 的架构边界,也涉及所有 CI/CD 工具在“海量任务”场景下的设计哲学。
如果你维护过 Jenkins、GitLab CI 或者 GitHub Actions,你应该知道 Pipeline 的正常用法是描述几十个、上百个任务步骤。当这个数字变成“万”甚至“百万”时,行为会发生质变——不是单纯的“跑得慢”,而是“根本跑不了”。这篇内容会记录我实际压测的过程、观察到的故障现象、背后的机制原因,以及最终我建议的替代方案。适合所有 CI 平台维护者、Pipeline 重度用户,以及想了解任务编排系统容量边界的人。
1. 问题的真实背景:谁会在 Pipeline 里塞百万条命令
1.1 先给“命令”一个明确语义
讨论这个问题之前,必须把“命令”这个概念先框定清楚,否则后面所有结论都会失真。在 CI/CD 场景里,“命令”通常指三层东西:
- 字面意义的 shell 命令:比如
echo hello、sh script.sh,你写在steps或sh块里的那些东西。 - Pipeline 的步骤(step):比如 Jenkins 声明式流水线里的每一步、GitLab CI 里的
script列表项,它们本质上是控制节点调度的最小执行单元。 - 流水线中的任务(job / stage):更高一层的逻辑分组,比如 GitLab CI 的 stage、Jenkins 的 stage 块。
我实测时,把这三层都试了一遍。最核心的结论是:无论把“命令”定义在哪一层,只要数量级到 100 万,传统 CI 架构都会出问题,只是“爆掉”的位置不同。字面 shell 命令爆在进程创建和日志,Pipeline 步骤爆在状态机持久化,任务爆在调度器和数据库。这三种故障我后面都会给到具体数据。
1.2 什么场景会产生这种需求
你可能会问:“正常人谁会往 Pipeline 里塞 100 万条命令?”答案是:正常情况下不会,但以下几种情况会产生类似规模的压力:
- 批量迁移:公司把几千个老项目一次性导入新的 CI 平台,每个项目都生成一条流水线,每个流水线里又有几十个任务。总任务量轻松破百万。
- 批量数据回放:做数据分析或者接口回归测试时,希望把历史请求逐个在 CI 环境里跑一遍。有人偷懒,直接生成一百万条
curl命令塞进脚本。 - 定时巡检脚本:某些运维同学喜欢把巡检逻辑直接写进流水线,比如“对 10 万台服务器逐一执行检查命令”,全部在一个 stage 里串行跑。
- 压测 CI 平台本身:这次我自己就是这么干的。想知道系统在极端情况下的行为边界,最好的办法就是真的压一次。
- 代码生成失控:用脚本自动生成 Pipeline 定义时,循环边界没控制住,生成了几十万个 stage。这种事故我在网上见过好几起。
1.3 为什么这个问题值得认真对待
我见过不少团队在 CI 配置上翻车,基本都是同一个模式:初期任务量小,怎么折腾都没事;后来业务膨胀,流水线越来越长,直到某一天提交代码后,CI 卡死三个小时,其他人全部排队。
理解“100 万条命令进 Pipeline”会怎样,本质上是在理解 CI 系统的容量天花板。它教会你两件重要的事:一是 Pipeline 适合承载什么任务、不适合承载什么;二是当任务真的海量时,应该把你的“命令”放在哪里执行,而不是硬塞给编排器。
2. 复现实验:构造百万命令 Pipeline 的三种姿势
2.1 姿势一:用循环生成大量 stage
第一种方式最暴力,直接对着 Pipeline 脚本生成器写循环。我这里用 Jenkins 声明式流水线举例子,生成 10 万个 stage 看看效果:
def total = 100000 pipeline { agent any stages { stage('init') { steps { echo "start" } } // 直接用脚本批量生成 stage script { for (int i = 0; i < total; i++) { stage("stage-${i}") { steps { echo "this is command ${i}" } } } } stage('done') { steps { echo "end" } } } }理论上这个脚本会生成 10 万个 stage,但实际在 Jenkins 上运行,到几千个 stage 的时候,界面已经卡得没法看,进度条根本渲染不过来。如果再往上加到 50 万、100 万,Jenkins 的脚本沙箱和 CPS 引擎(后面详讲)直接内存溢出。
2.2 姿势二:在单个 stage 里拼接百万条 shell 命令
第二种方式是把命令全部堆在一个 shell 脚本里,然后让 Pipeline 执行这个脚本。我用 Python 生成了一个包含 100 万行echo命令的脚本文件:
with open("million_commands.sh", "w") as f: for i in range(1_000_000): f.write(f"echo 'command {i}'\n")然后 Pipeline 里就一行:
pipeline { agent any stages { stage('run-million') { steps { sh 'bash million_commands.sh' } } } }这个做法在“Pipeline 层面”反而不会立即爆。因为它只有一个 step,Pipeline 只负责调度一次。真正的问题是发生在执行层:shell 进程要逐个创建echo子进程?不一定,bash 内建echo不会每个都 fork,但 100 万行脚本光是解析就要好几秒,执行完的日志输出量非常可观。我会在第三节给出具体数据。
2.3 姿势三:外部文件驱动 + 动态任务注入
第三种姿势是专业压测常见的做法:不在 Pipeline 定义里写死大量命令,而是在外部生成一份“任务清单”文件,然后用 Pipeline 脚本逐行读取、逐行执行:
pipeline { agent any stages { stage('read-commands') { steps { script { def lines = readFile('commands.txt').readLines() lines.each { cmd -> sh cmd } } } } } }这样做的坏处是:Pipeline 的执行引擎仍旧会为每一行命令创建对应的执行状态,100 万行的循环体依然会让内存和状态记录爆炸。只是它把“静态定义”的负担转成了“运行时循环”的负担,本质上没有避免问题。
2.4 我用来观测的关键指标
为了让结果可量化,我固定了一套观测环境:Jenkins 2.4xx 版本,4 核 8G 内存,默认堆配置(最大 2G),一个 Linux agent,磁盘普通 SSD。重点关注五个指标:
- Master 端 JVM 堆内存变化曲线
- Pipeline 脚本从提交到开始执行(调度启动耗时)
- 单条命令从创建到完成的状态记录次数
- 日志系统每秒写入量
- 整个 Pipeline 从启动到结束的总耗时
在开始之前,我本来猜测“100 万”只是一个慢的问题,实测后发现“慢”只是最小的问题,真正的麻烦是后面那一堆连锁反应。
3. 实测结果:100 万条命令进去后的六个关键信号
3.1 信号一:内存先垮,不是慢,是直接撑爆
先说 Jenkins 流水线最核心的机制——CPS(Continuation Passing Style,延续传递风格)。Jenkins 的流水线脚本要支持暂停、恢复、重试,所以每一步执行都必须在内存里保留一个“程序状态”对象。这种设计的代价是:每个 step 都会被包装成大量 Java 对象,内存开销远高于你直觉里的“一行命令”应该占用的空间。
我压测时观察到:
- 1 万条命令:Master 堆内存从 300M 涨到 800M,还在正常范围。
- 10 万条命令:堆内存逼近 2G,GC 频率显著增加,界面开始频繁卡顿。
- 50 万条命令:堆内存溢出,Jenkins 直接进入不可用状态,必须重启。
你可能会想:“100 万呢?”答案是根本到不了 100 万。50 万的时候系统就已经“脑死亡”了。在 CPS 模式下,每一条 Pipeline 命令的完整执行路径会产生几个甚至十几个对象,50 万条命令对应几百万个对象,2G 堆被几百万个对象塞满是非常合理的结果。
3.2 信号二:配置持久化变成磁盘灾难
Jenkins 每跑一次流水线,都会在任务目录下记录大量的 XML 和 JSON 配置。对于 Pipeline 任务,磁盘上会保存构建记录、流水线状态、节点图数据。当流水线里面塞了几十万个 step,每个 step 都要写入状态节点,这个文件尺寸会变得极其离谱。
我实测生成 20 万个 stage 的时候,任务目录下的builds文件夹体积达到了 3.7GB。保存一次流水线配置,Jenkins 要序列化一整棵巨大的任务树,耗时超过 40 秒。这意味着你再改一下 Pipeline 脚本点“保存”,界面要转半天。这种体验几乎等于宕机。
3.3 信号三:调度启动阶段出现指数级退化
Pipeline 被触发后,先是解析脚本、创建流程节点,然后才是真正执行。解析和创建节点的过程是纯 CPU 计算,命令数量上去之后,启动时间呈指数级退化:
- 1 千条命令:启动 1 秒内
- 1 万条命令:启动约 5 秒
- 10 万条命令:启动 2 分钟以上
- 50 万条命令:启动超过 20 分钟,而且经常在这个阶段就超时或内存溢出
这实际上是 CPS 状态机的构造过程在拖后腿。每一条命令都需要被转化为可挂起的执行状态,中间还要插入各种拦截器和回调。你可以把它理解成一个文档有 100 万行,每次修改都要重新拼装整棵语法树,怎么可能快得起来?
3.4 信号四:执行阶段的调度轮询开销惊人
即使你绕开 Jenkins 的声明式语法,把命令放在一个 shell 脚本里跑(姿势二),问题也并不会消失,只是转移到了执行器层。
我在 8G 内存的 agent 上跑 100 万行 echo 脚本时,shell 本身解析和执行的耗时大约是 50 秒,但 Jenkins 在执行过程中要不断轮询 shell 进程状态、捕获 stdout/stdout 输出、把日志通过网络回传到 master。结果是:
- 日志总量达到 680MB(纯文本,每一行是一个
echo输出) - Master 端日志存储每分钟接收超过 4 万条日志行
- 构建结束后,日志索引重建花了接近半小时
更隐秘的问题是 shell 脚本单进程执行虽然有 50 秒,但如果命令之间还互相依赖、有失败重试,那时间会成倍增长。100 万条命令中只要有 0.1% 失败率,就是 1000 条失败命令,每条失败如果触发一次重试,瞬间又多出 1000 次执行。
3.5 信号五:日志系统被击穿,故障定位彻底失效
Pipeline 的日志设计是为了展示“一个人能看完的构建记录”,不是“100 万行机器输出”。百万命令最容易被低估的破坏点就在日志。
Jenkins 会把控制台输出先缓存在内存里,再分批刷到磁盘。100 万条命令的日志量动辄几百 MB 到几 GB,内存缓冲被冲爆,日志写入线程持续满负荷。随之而来的是两个致命后果:
- 前端控制台页面打开后滚动卡死,浏览器直接无响应。
- 日志文件被切割成无数个
log分段,排查故障时根本无法定位。
最讽刺的是:你打包 100 万条命令进去,初衷可能是为了批量完成任务。但当某条命令真的失败时,你根本不知道它是哪一条——日志系统已经失去“可检索性”。
3.6 信号六:超时、重试与排队形成连锁风暴
当流水线本身因为上述原因龟速运行时,其他团队的任务开始排队。Jenkins 默认的执行队列策略是“一个 agent 同时跑一个任务”,你的巨无霸流水线占住了唯一的执行槽位,其他人的提交全部卡在队列里。
紧接着是超时炸弹:很多团队会给任务设置 30 分钟或 1 小时的超时。你的流水线跑不完,超时后触发重试,重试再次进入队列,再次超时,形成自我加重的风暴。如果 CI 平台没有做并发控制,这种风暴甚至能拖垮整个 Jenkins Master。
我压测完直接得到一张操作系统负载图:agent 负载长期徘徊在 80% 以上,Master 的 GC 线程占掉 3 个 CPU,而真正干活的执行线程只有不到 20%。
3.7 实测数据汇总
| 命令数量 | Pipeline 类型 | Master 堆内存峰值 | 启动耗时 | 总执行耗时 | 日志量 | 结果 |
|---|---|---|---|---|---|---|
| 1 万 | 循环 stage | ~800M | 5 秒 | 1 分钟 | ~30MB | 能跑,卡顿明显 |
| 10 万 | 循环 stage | ~1.8G | 2 分钟 | 未完成 | ~300MB | 严重卡顿,界面失响应 |
| 50 万 | 循环 stage | 溢出 | 20 分钟 | 跑不完 | 未统计 | Master “脑死亡” |
| 100 万 | 单 stage + shell 脚本 | ~1.2G | 正常 | 约 3 小时 | 680MB | 能跑完但日志瘫痪 |
这组数据最震撼的结论是:100 万条命令塞进 Pipeline 不是“慢”,而是它会以各种方式让系统瘫痪。你必须在到达这个数量级之前,换一种完全不同的架构思路。
4. 根因分析:为什么 Pipeline 不是这么用的
4.1 声明式流水线的执行模型决定了它的边界
Jenkins Pipeline 最引以为傲的特性是可暂停、可恢复、可视化。这背后是 CPS 转换——脚本被编译成一种特殊的状态机,每个步骤都能随时记录当前执行位置,以便在重启后接着跑。
听起来很强大,对吧?但这个特性是有代价的:每一步的执行都要序列化和反序列化状态。如果你有 100 万命令,就意味着 100 万个状态节点需要被管理和持久化。这和普通脚本语言“从上往下跑完就结束”的模型有本质区别。
我已经见过太多人犯这个错:把 Jenkins 当成了“带界面的 shell”。sh 'xxx'写起来很顺手,觉得它就是执行一条命令。但实际上,Jenkins 为了管理这条命令的完整生命周期,做了大量额外工作——状态记录、超时控制、重试策略、并发调度、日志收集。这些工作对 100 条命令没问题,1000 条勉强,10000 条开始吃力,100 万条就是纯灾难。
4.2 单条命令的完整生命周期开销
具体一条命令在 Pipeline 里被执行,大致经过以下环节:
- 解析:脚本编译时创建对应的 AST 节点。
- 包装:CPS 引擎将命令包装成带状态的可恢复步骤。
- 调度:命令被放入执行队列,等待 agent 资源。
- 执行:agent 上启动 shell 进程(或复用 shell 内建),执行命令。
- 日志:捕获 stdout/stderr,通过网络向 master 回传。
- 持久化:执行状态写入磁盘(用于断点续跑)。
- 回调:触发后续步骤,检查超时、重试条件。
一百个命令跑完,它也能跑通这七个环节吗?可以。但一百万个命令,每个都要走这七个环节,很多环节的开销是乘法级增长,最终整个系统不是在“执行命令”,而是在“执行命令的管理开销”。
4.3 字节码级类比:解释器的“解释”成本
用编程语言类比,CI Pipeline 的执行模型更接近“解释执行”而不是“编译执行”。
解释执行的典型问题是:每次运行一行代码都要经历词法分析、语法分析、执行、收集结果这一整套流程。而编译执行可以把 100 万条命令一次性翻译成机器码,后面运行起来就很轻量。
Pipeline 的调度器就是那个“解释器”。它每处理一条命令都要做一轮完整的调度上下文切换,而不会去合并优化这些命令。如果你真的需要执行 100 万条 shell 命令,正确的方式是让 shell 一次性解释完整个脚本,而不是让 Pipeline 充当那 100 万次解释的经纪人。
4.4 GitLab CI 的对比:不同架构,同样的天花板
既然相关搜索里也出现了 GitLab CI,我把它也纳入对比。GitLab CI 的 Pipeline 模型和 Jenkins 不太一样,它不搞 CPS 状态机,而是把每个 job 当作一个原子单元,由 Runner 拉取后独立执行。
但这不代表 GitLab CI 就能扛住百万任务。恰恰相反,我实际观察过 GitLab 的行为:当 Pipeline 里生成成千上万个 job 时,瓶颈转移到数据库和 Redis。GitLab 为每个 job 都要写数据库记录、更新状态、分配 Runner,100 万 job 意味着 100 万次数据库写入。GitLab 的 Postgres 在没有针对性优化的情况下,处理几万个 job 时就已经出现锁竞争和慢查询,百万级直接让 Sidekiq 队列堆积到天荒地老。
所以结论是通用的:无论 Jenkins 还是 GitLab CI,它们都是“任务编排器”,不是“批量执行器”。编排器擅长描述有依赖关系的、需要人工介入的、需要可视化跟踪的少量任务。你要做的批量命令,应该交给专门的执行引擎,让编排器只负责发起和汇总结果。
5. 真正要处理百万命令时,我建议的替代做法
5.1 原则一:把“命令”从 Pipeline 中拆出去
前面压测已经证明,Pipeline 不适合承载百万命令的直接执行。所以核心思路是:把命令放进普通的脚本文件或二进制程序,让 Pipeline 只负责“开始”和“收集结果”。
一个最简单可行的例子:
pipeline { agent any stages { stage('prepare-commands') { steps { // 从对象存储或代码库拉取一个已生成的命令清单 sh 'wget -q -O commands.tar.gz http://artifactory/commands.tar.gz' sh 'tar -xzf commands.tar.gz' } } stage('run-all') { steps { // 一次性执行整个命令集合,Pipeline 不感知内部细节 sh 'bash runner.sh --input commands/ --parallel 8' } } stage('collect-report') { steps { // 只读取最终统计结果,不再处理逐条执行记录 junit 'reports/*.xml' } } } }这样做的好处是:Pipeline 中的命令数量永远是“3 条”,而不是“100 万条”。它们的生命周期开销几乎可以忽略不计。真正的执行负担完全落在 shell 脚本或专门的批处理工具上,它们才是适合做大规模循环、并行处理和日志管理的工具。
5.2 原则二:任务体量分层,让不同工具做擅长的事
我的经验是把 CI 场景拆成三层:
- 第一层:编排层(Pipeline / CI 平台)。只负责“开始”“停止”“结果展示”,命令数量控制在百级以内。
- 第二层:执行层(Shell / Python / Go 程序)。负责大批量命令的实际运行,支持并行、断点、重试,命令数量可以到百万级。
- 第三层:数据层(文件 / 数据库 / 对象存储)。负责命令清单的存储、结果日志的持久化,保证任何时刻都可以“只处理一批”。
这个分层让你随时可以扩展。命令从 100 万涨到 1000 万,你只需要增加执行层的并发度,或者把命令文件分片,而无需触碰 CI 平台本身。
5.3 原则三:用并行边界和批量窗口控制压力
如果你确实需要在 CI 里触达十万这个量级,必须建立批量窗口思想。比如:
- 每次从命令清单中读取 1000 条,作为一个批次执行。
- 批次内可以用
xargs -P 8或 GNU Parallel 做并行。 - 每个批次完成后,把状态写入一个状态文件:
batch-001.done。 - 整个 Pipeline 轮询状态文件,而不是轮询单条命令。
伪代码可以这样写:
# runner.sh total_batches=1000 for ((batch=0; batch<total_batches; batch++)); do start=$((batch * 1000)) end=$(((batch + 1) * 1000)) # 从命令文件取第 start 到 end 行,并行执行 sed -n "${start},${end}p" commands.txt | xargs -P 8 -I {} sh -c '{}' echo "batch ${batch} done" >> progress.log donePipeline 只需要:
sh 'bash runner.sh'如果中途失败,检查progress.log,从上次完成的批次继续,而不是从头跑 100 万条。这就是完整的断点续跑机制,比让 Pipeline 引擎去管理百万级状态要可靠得多。
5.4 原则四:结果回收要进“仓库”,不要留在“流水线日志”
命令执行完,结果数据属于“产物”,应当被系统化收纳,而不是躺在流水线的控制台日志里。三种做法按照性价比排序:
- 写文件 + 归档:执行器把每个命令的退出码、耗时、输出摘要写到 JSONL 文件,然后用 CI 的 artifact 能力归档。
- 入数据库:每批命令完成后批量插入数据库,方便后续检索分析。
- 消息队列 + 消费者:如果命令本身产生事件,先入 Kafka / RabbitMQ,再由消费者写入数仓。
只有这样做,百万量级的结果才可能被有效追踪。我在实际项目中用的是“文件 + SQLite”组合:每 1000 条命令一批,写一个 SQLite 表,批量插入,查询非常快,也不会拖垮 CI。
5.5 选型参考:适合“百万命令”的工具画像
如果你的需求确实是“批量命令执行”而非“CI 流程编排”,下面这些工具比 Pipeline 合适:
| 工具/方案 | 适用场景 | 优缺点 |
|---|---|---|
| GNU Parallel / xargs | 单机批量 shell 命令 | 轻量、无学习成本,但不适合跨机 |
| Ansible | 跨主机批量执行命令 | 有幂等设计、适合运维,但并发性能要调优 |
| SaltStack | 大规模服务器命令分发 | 面向节点海量场景,架构比 CI 更合适 |
| 自研 Python/Go 执行器 | 特殊业务逻辑、需要精准控制 | 灵活度最高,但需要开发维护成本 |
| Kubernetes Job/CronJob | 容器化批量任务 | 适合真正海量且可并行,每个任务独立资源 |
这些工具的共性在于:它们都允许你把“命令”当作数据,而不是把“命令”当作流程节点。这一点的差异决定了百万量级能否落地。
6. 上线前自查清单与监控要点
6.1 从命令量级估算资源
在把任何东西塞进 Pipeline 之前,先用简单的公式估算一下:
- 单条命令运行时长(秒) × 命令总数 = 串行总耗时(秒)
- 串行总耗时 ÷ 期望的并行度 = 实际耗时(秒)
- 单条日志量(KB) × 命令总数 = 日志总量(MB)
拿我压测数据举例:单条 echo 命令约 0.05 毫秒执行耗时(shell 内建),但日志量平均 0.7KB/条(包含 Jenkins 记录的时间戳和元数据),100 万条的日志总量约 680MB。如果单条命令耗时 1 秒,串行总耗时就是 100 万秒,约 11.6 天。即使并行度开到 32,也需要接近 9 小时。
所以任何超过 10 万条的命令集,都必须问自己:它真的需要在 CI 里跑吗?它能接受这么长的窗口期吗?它的结果如何归档?这三个问题如果有一个答案是否定的,就别用 Pipeline。
6.2 关键监控指标清单
如果你已经运行着大规模 Pipeline 或者准备压测,以下指标必须提前接好监控:
- Master JVM 堆内存:重点看 GC 频率与 Full GC 时长。
- 任务队列堆积数:观察排队中的任务数量是否持续增长。
- 日志写入吞吐:每秒产生的日志行数和字节数。
- 磁盘 IO 和空间:构建目录增长速率。
- 调度器状态:Jenkins 的 executor 使用率、GitLab 的 Sidekiq 队列长度。
6.3 告警阈值建议
| 监控项 | 建议阈值 | 告警级别 |
|---|---|---|
| Master 堆内存使用率 | 80% 持续 5 分钟 | 警告 |
| Master 堆内存使用率 | 95% 持续 1 分钟 | 严重 |
| 流水线单任务执行时间 | 超过 60 分钟 | 警告 |
| 构建目录单次增量 | 超过 500MB | 警告 |
| 日志写入速率 | 超过 5MB/s | 警告 |
| 排队中的任务数 | 超过 20 | 严重 |
这些阈值来自我自己的压测和运维经验,可以根据团队规模适当调整。核心原则是:比起事后补救,提前让 Pipeline 快速失败更划算。
6.4 Pipeline 最佳实践:让流水线保持“可读性”
最后一点是给正常使用 Pipeline 的同学的忠告。就算你永远不需要处理 100 万命令,也请始终遵循以下做法:
- stage 数量控制在 20 个以内。超出后,流水线的可视化价值急剧下降。
- 每个 stage 内 steps 控制在 10 个以内。如果超过,抽取成 Shell 脚本并单独维护。
- 避免动态生成 stage。凡是出现
for循环生成 stage 的写法,都该重构。 - 把超时设置为显式值。别让任务无限期挂在那,占满 executor。
- 使用共享库封装复杂逻辑。把“做什么”写在脚本里,把“何时做”写在 Pipeline 里。
我见过太多流水线从“可读的步骤清单”退化成“一团麻线”。当一条流水线的日志超过 5 万行时,它的维护成本就已经超过它能带来的自动化收益了。
7. 收尾——我个人的一些体会
这次压测做下来,我最大的感受是:CI 平台的脆弱不是 bug,而是设计取舍的必然结果。Pipeline 这个抽象层为了可视化、可暂停、可重试,牺牲了海量任务场景下的极致性能。这不是缺陷,而是选择。所以核心问题从来不是“能不能塞进去”,而是“应不应该塞进去”。
我自己现在处理大批量命令时,早已习惯“只在流水线里保留 3-5 个顶层步骤”的写法。命令清单放代码库,执行逻辑放 shell 或 Python 脚本,结果归档放数据库。Pipeline 在我的架构里扮演的就只是一个带界面的“总开关”,而不是万能工具箱。
最后分享一个非常实用的小技巧:如果你实在没办法、必须在 Pipeline 里跑很多条命令,把命令写进一个.sh文件,然后在 Pipeline 里只写一次sh 'run_all.sh'。只做这一步改造,你的 CI 系统就能同时处理原来十倍甚至百倍的命令量。原因无它——你只是把解释权从调度器还给了 shell,而它天生就是干这个的。