在 CI/CD 的竞技场里,GitHub Actions 凭借「零门槛」与 Marketplace 生态迅速占领 repo。然而当开发者把 YAML 从 30 行写到 800 行后,几乎都会发出同一声吐槽:这大概是体验最差的「包管理器」。没有 lock 文件、没有依赖树、没有语义化版本,却要让全球 560 万工作流每天跑着不可复现的第三方脚本。本文把「最差」拆成三条硬伤疤:设计缺陷、性能黑洞、安全雷区,并给出仍在泥潭中的工程自救方案。
设计缺陷:语法、语义、复现性三重缺失
Actions 的依赖声明只有一行 uses: owner/repo@ref,看似简洁,实则把包管理最基础的三件事全推给用户:
- 无变量插值:
uses字段禁止${{ }},导致无法动态切换源,企业内网隔离只能靠「搜 - 替 - 改」三连。 - 无语义版本:
@v1只是 git 标签的「字符串等于」匹配,不存在^1.2.3的区间语义;一旦上游强制推送,本地 CI 立刻漂移。 - 无 lock 机制:既没有
package-lock.json,也没有go.sum,回滚只能指望人工记 commit hash。GreptimeDB 团队在重构 Release 流水线时吐槽:「YAML 弱 DSL 让 183 行膨胀成 800 行,本质就是把依赖树写死在注释里」。
结果是「可复制构建」成为幻觉:同一套 workflow,在不同时间点跑,拉到的 action 可能已是完全不同的代码。
性能黑洞:缓存失效与重复下载
GitHub 提供的 actions/cache 只解决「文件级」缓存,对 action 本身却毫无记忆:
- 跨 job 不共享:matrix 策略下,每个 slice 都要重新 clone 同一 action,几十秒到数分钟不等。
- 跨分支不共享:PR 触发的新 ref 被视为全新命名空间,旧缓存直接失效。
- 缓存键简陋:默认只哈希 yarn.lock 这类文件,若 action 内部升级了子依赖,缓存不会失效,导致「命中却错误」。
实测一个 20 个 job 的工作流,仅下载 actions 就耗时 6 分 32 秒,占整次 CI 43% 时间。开发者只能把「自建 runner + 本地镜像」当标配,却失去了云原生弹性扩缩的意义。
安全雷区:供应链攻击与权限扩散
Google Project Zero 早在 2020 年就指出,Actions 的工作流命令注入让「打印一条日志就能写环境变量」。更严重的是依赖面:
- 无完整性校验:uses 字段无法附带 SHA256,只能相信 git tag;一旦上游 repo 被劫持,恶意代码立刻获得 GITHUB_TOKEN 写权限。
- 权限过度:默认
permissions: write-all,而第三方 action 仅需一行echo "::set-env"即可把机密带到外部服务器。 - 不可审计:没有依赖树,也就无法生成 SBOM;企业合规团队面对几百个
@main只能人工翻 commit。
2025 年供应链报告显示,16 万个公共工作流直接 git clone 主分支,相当于把生产密钥挂在别人阳台上晾晒。
工程自救:版本锁定、本地缓存、最小权限
在 GitHub 官方给出真正的 lock 文件之前,只能「靠自己」:
- 版本锁定:所有
uses强制写死 commit SHA,例如actions/checkout@b4ffde65f,并在 CODEOWNERS 里要求双审才能升级;配合 renovate-bot 自动提 PR,但合并仍需人工二次确认。 - 私有镜像:把高频 action 同步到企业内网仓库,用
ghcr.io/org/actions/xxx替代官方源;同时在内网 runner 上预 clone,实现「一次下载,全厂共享」。 - 缓存策略:在 job 级别把
/opt/actions-cache打 tar 上传到 S3,用「repo+commit」做 key,让矩阵 job 先尝试拉取缓存,再回退到官方源;实测可把 6 分钟缩短到 45 秒。 - 最小权限:在 workflow 顶部显式声明
permissions: contents:read,按需单独打开id-token、pull-requests;对第三方 action 一律用permissions: {}先锁死,再白名单放行。 - 准入评审:内部维护「允许列表」仓库,任何新增 action 需提交 PR,通过 Trivy + Scorecard 扫描后方可合并;runner 端用 Open Policy Agent 实时校验,拒绝不在列表内的镜像。
结语:把「最差」变成「待改进」
GitHub Actions 的「包管理」仍停留在 2014 年的 git clone 时代:没有版本解析、没有依赖树、没有完整性校验。它把「灵活性」最大化,却把「可维护性」「可复现性」「安全性」全部推给下游。社区已经用脚投票 ——Zig、Dillo 等项目纷纷迁出,GreptimeDB 则花 3 个月把 800 行 YAML 拆成可审计的 Makefile + Dockerfile。
要终结「最差」标签,GitHub 至少需要:
- 引入类似
actions.lock.yml的语义锁文件; - 允许在 uses 字段声明 hash,并提供原生签名验证;
- 把 action 缓存提升到「包」级别,支持跨 job、跨 repo 共享;
- 在 Marketplace 侧显示安全评分与 SBOM 下载。
在那一天到来前,开发者只能把上面五招自救清单贴在 PR 模板里,然后继续对每一行 @main 保持警惕 —— 毕竟,CI 流水线一旦「随机失败」,最先被怀疑的永远是那行没有锁版本的第三方 action。
参考资料
[1] GreptimeDB 团队:《GitHub Actions?! 想说爱你不容易》,InfoQ,2023.
[2] Felix Wilhelm, Google Project Zero:《Workflow commands injection in GitHub Actions》, 2020.