当工具成为生产力瓶颈时,我做了这些事来破局

404收集者
2025-06-29 14:49
阅读 613

我在一家快速发展的互联网公司担任开发工具工程师已有五年。从最初一个人搭建 CI/CD 系统,到如今带着一个小团队维护公司内部的整个 DevOps 生态系统,我对“效率”两个字的理解越来越深。

开发工具的核心使命是服务于人,提高团队整体交付效能。但有时候,我们自己反而是被“低效”困扰最多的人。就在去年底,我们遇到了一个看似简单、实则复杂的痛点:代码提交后等待构建和部署的时间越来越长,严重影响了研发同学的工作节奏。这促使我重新审视我们现有的工具链架构,并做出了一些关键调整。

今天我就想分享一下这段经历,希望能给正在做工具链优化或类似尝试的同学一些启发。


起因:慢得让人焦虑的构建速度

起因:慢得让人焦虑的构建速度

事情要从去年 10 月说起。那段时间,研发团队开始频繁抱怨我们的持续集成(CI)系统——“为什么每次合并 PR 都要等 5 分钟才能跑完测试?”

起初我以为只是个别项目的问题,但随着越来越多团队反馈同样的情况,我知道事情没那么简单。

我们当时的 CI 系统是基于 Jenkins 构建的一套自研体系,核心流程如下:

  • 每次 Git Push 后,触发 Jenkins Pipeline
  • Pipeline 中包括拉取代码、安装依赖、执行 Lint、运行测试、生成报告、上传制品等步骤
  • 构建节点是固定的一组机器资源池
  • 所有服务共享这一套流程配置模板

在业务早期这种模式完全够用,但随着公司规模扩大,问题逐渐显现:

1. 构建资源瓶颈

随着项目数量从几十增长到几百,同时并发构建任务数剧增,Jenkins 的 master-slave 架构开始出现调度延迟。我们每天高峰期会收到十几条 Slack 报警:“当前队列任务积压严重,请稍等”。

2. 流程重复冗余

很多小项目也继承了统一的 Pipeline 模板,包括不必要的编译、打包步骤。例如,部分前端项目的 Markdown 文档修改也强制执行 npm install + lint,导致构建时间动辄两三分钟。

3. 缓存机制缺失

几乎每次构建都要重新下载依赖包,比如 Node Modules、Gradle Wrapper、Maven 依赖等。网络波动时甚至会出现超时失败的情况。

4. 结果反馈慢

Pipeline 运行完需要手动打开页面查看日志才能判断是否通过。对于自动化程度高一点的团队来说,这显然不够“即时”。

这些问题叠加在一起,直接导致平均构建时长超过了 8 分钟(理想情况下应控制在 3 分钟内),严重影响了大家的信心和使用意愿。


探索:不是换平台就能解决问题

探索:不是换平台就能解决问题

面对压力,一开始我也想当然地考虑更换为 GitHub Actions 或者 GitLab CI。毕竟这两个平台在社区中口碑不错,看起来能“一键迁移”,节省不少时间。

但在技术评审会上,我提出了几个关键疑问:

  • 能否保留现有权限控制策略?
  • 如何平滑迁移数百个已经存在的 Pipeline?
  • 如何兼容我们自研的插件与中间件?
  • 是否有足够的技术支持应对突发状况?

最终我们决定,不在平台上“一刀切”,而是在现有框架上做深度优化


我们的解决方案:拆解问题,分段优化

CI/CD流水线-1

我们的解决方案:拆解问题,分段优化

第一阶段:识别浪费点,优化流程结构

我们先做了一轮性能分析,收集了过去一个月的构建日志数据,用 Python 脚本统计出各个步骤耗时分布,发现最耗时的三个环节分别是:

步骤 平均耗时 占比
安装依赖 120s 36%
执行 Lint 90s 27%
执行单元测试 70s 21%

其中,“安装依赖”的耗时波动最大,尤其当网络异常时会暴涨到 5~8 分钟。这说明我们缺乏有效的缓存机制。

于是我们制定了第一轮优化目标:

✅ 引入本地依赖缓存代理

我们在每台 Jenkins Slave 上部署了一个轻量级的本地缓存服务,用于缓存 npm, maven, pip 等公共依赖。具体实现方式是通过中间层代理所有的包请求,并记录已下载内容。第二次相同请求直接走本地。

举个例子,Node.js 项目的 package.json 基本不变,因此 node_modules 几乎可以复用。改造后,依赖安装时间缩短到了 10~20 秒。

✅ 按变更内容做差异化构建

以前每次 PR 提交都会跑全套 pipeline,其实大多数时候只需要运行受影响的部分模块。

我们写了个轻量级的 diff 工具,在 Pipeline 开始前分析 Git Diff 内容,判断是否真的需要执行某些昂贵步骤:

# 伪代码示意
if [[ $(git diff --name-only HEAD^) =~ "README.md" ]]; then
    echo "Only docs changed, skipping test"
    exit 0
fi

这个改动让大约 30% 的文档修改类提交跳过了 Lint 和 Test,节省大量时间。

✅ 使用并行化执行非依赖步骤

Jenkins 支持 Parallel Step,我们可以将不需要顺序执行的任务并行处理:

stage('Build') {
    steps {
        script {
            def parallelTasks = [:]
            parallelTasks["Frontend Build"] = {
                sh 'cd frontend && npm run build'
            }
            parallelTasks["Backend Build"] = {
                sh 'cd backend && ./gradlew build'
            }

            parallel parallelTasks
        }
    }
}

这样 CPU 利用率提高了,构建时间又下降了约 15%。


第二阶段:基础设施升级,引入弹性伸缩能力

虽然流程层面做了很多优化,但遇到高峰期依然会出现排队现象。于是我们开始考虑引入 Kubernetes 来管理构建节点。

我们之前使用的 Jenkins slave 是固定虚拟机,扩展性差、运维成本高。迁移到 Kubernetes 上后,好处显而易见:

  • 可以根据负载自动扩缩容
  • 更好的资源利用率(按需分配)
  • 更容易隔离故障节点

我们选择使用 Kubernetes plugin for Jenkins,结合我们自有的 K8s 集群进行集成。

配置示例(简化版):

podTemplates:
  - name: jenkins-agent
    label: k8s-agent
    containers:
      - name: jnlp
        image: jenkins/jnlp4-agent:latest
        args: ${computer.jnlpmac} ${computer.name}
        resourceLimitCpu: "2"
        resourceLimitMemory: "4Gi"

迁移完成后,我们可以在负载激增时自动扩容上百个 Pod,构建响应速度快了将近一倍。


第三阶段:提升反馈效率,构建闭环

即使构建快了,结果反馈还是靠人工去 Jenkins 页面看。于是我们接入了 Slack Webhook 和企业微信机器人,每次构建结果会自动通知到相关负责人。

并且我们做了两个小优化:

  • 在代码审查界面嵌入构建状态徽章(Badge)
  • 自动将构建日志压缩归档并提供直链下载

这些细节极大提升了用户的感知度和满意度。


实际效果:不止于效率提升

实际效果:不止于效率提升

经过三个月的努力,我们的优化成果如下:

指标 优化前 优化后 提升幅度
平均构建耗时 8 分钟 2.5 分钟 70%
队列任务积压率 20% <2% 大幅下降
构建失败率 8% 1.5% 下降明显
用户满意度评分(满分10) 6.2 8.9 显著上升

更重要的是,这种改进带来了组织层面的变化

  • 研发人员更愿意使用 CI 流程
  • 自动化覆盖率大幅提升
  • 新员工入职更快上手 CI 用法

经验与教训

回顾这次优化过程,有几个关键点特别值得总结:

🧱 不要迷信所谓“新平台万能论”

有时候换个平台解决不了根本问题,反而带来新的复杂度。我们当时差点就因为“听说 GitHub Actions 很快”而盲目迁移,后来发现真正慢的是依赖安装和流程不合理。

⚙️ 先从流程入手,再优化基础架构

很多时候,瓶颈不在底层设备,而在于设计不合理。比如我们一开始就解决了依赖缓存问题,后面才引入 K8s,顺序非常关键。

🔍 数据驱动决策是关键

没有日志分析和性能统计数据,很难找到真正的症结。我们当时还做了一个可视化面板,方便实时监控构建流水线各环节耗时。

👩‍💻 让用户感知到改进

工具好用了,不代表大家知道。我们配合市场部门做了一个简短视频教程,展示“现在你的 PR 会在一分钟内跑完测试”,帮助用户建立起信心。


小插曲:一次失败的尝试

过程中我们也踩过坑。比如有一阵我们想用 D2C(Docker in Docker)的方式把整个构建封装进容器镜像,这样每次启动都是干净环境。

想法很好,但实践发现两件事拖累了整体效率:

  1. 每次都需要 pull 一个 1GB+ 的镜像
  2. 文件挂载 IO 成本很高,影响速度

最后我们改为“按需定制镜像 + 共享缓存目录”,兼顾了可维护性和效率。


最后的话:效率优化是一场持久战

这篇文章讲的是一个特定的 CI 构建优化案例,但它背后体现的思维方式和原则适用于各种效率优化工作。

无论你是做内部工具,还是参与产品开发,都要记住一句话:

“你花在改进工具上的每一分钟,都在帮助成百上千个开发者省下时间。”

所以,不要低估每一次微小的改进。正是一个个“少等 10 秒”的积累,构成了我们真正意义上的生产力跃迁。

如果你也在做类似的优化工作,不妨从以下几个方向出发:

  1. 从用户视角出发,找出真正痛的点
  2. 用数据说话,避免拍脑袋决策
  3. 小步快跑,持续迭代
  4. 注意用户体验的每一个细节

希望这篇文章对你有所启发。欢迎留言交流,我们一起探索更高效率的可能性。


作者简介:目前在某一线互联网公司负责 DevOps 工具链建设,热爱开源、追求极致体验的技术人。欢迎关注我的 GitHub 或公众号,一起探讨工程效率与开发者生态。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝