记一次CI构建优化
目前的前端项目是在gitlab CI中进行构建和部署。最近发现整个CI的构建时间比较长,比较影响开发体验,因此需要优化一下。
本文首先介绍gitla CI的一些与构建流程相关的配置项,然后尝试从这些配置项优化构建速度
gitlab CI/CD
在传统流程中,开发需要将更新后的代码手动部署到测试or生产服务器上,整个部署流程涉及到安装依赖、编译、打包等流程,比较繁琐,会占用额外的开发时间。
因此,我们需要使用CI/CD自动化处理这些问题。
gitlab本身提供了CI功能,可以在git项目根目录下配置.gitlab-ci.yml
文件,用来定义了这个项目该如何构建。
首先了解下gitlab ci的配置项,最直接的还是看看官方文档
也可以参考下面的教程
问题定位
在优化之前的CI配置比较简单
image: node:12.14.0
stages:
- build
cache:
paths:
- node_modules/
before_script:
- git remote set-url origin xxx
- npm install --registry=https://registry.npm.taobao.org
after_script:
- node scripts/publish.js --branch ${CI_COMMIT_REF_NAME}
build_test:
stage: build
only:
- test
script:
- npm run build:test
build_prod:
stage: build
only:
- master
script:
- npm run build
主要就是install
和build
两个步骤,且没有拆分成独立的job。为了减少npm i
的耗时,对node_modules
目录进行了缓存处理,整个项目构建需要大概五六分钟(300s)
该项目使用vue-cli
构建,也使用了一些优化构建速度的措施,整个项目本身构建耗时
- 在本地16G、i7的mbp上面大概是50s左右,
- gitlab Runner分配的配置也还可以(4核8G),构建用时大概是90s左右
由于有node_modules缓存的存在,在不添加新依赖的情况下,安装速度也比较快,10s左右。
那么剩下多出来的300 - 90 - 10 耗时到底是花在哪里去了呢?
在观察job的打包日志之后发现了一个问题:在after_script
脚本运行之后,按理说整个构建就已经结束了,但整个job还要运行很长一段时间才会结束
node_modules/: found 396162 matching files
这里是在创建node_modules的缓存,怎么有这么多文件需要缓存?
需要回头来看一看cache
关键字
缓存配置
参考
cache配置项
看一下cache关键字的配置文档
cache用来指定需要在job之间缓存的文件或目录,可以配置全局cache,也可以在某个job下面配置cache,后者会覆盖前者。
cache:paths
配置需要被缓存的文件或目录
cache:key
缓存是在jobs之前进行共享的。如果想在不同的job之间使用不同的cache,则需要配置不同的cache:key
cache:policy
默认缓存是拉取且推送的,可以配置某个job按照指定策略使用缓存
- pull,拉取缓存
- push,推送缓存
- pull-push,拉取且推送缓存(默认)
优化
为什么要做缓存呢? 主要是为了重复运行任务的时候不会重复安装全部node_modules
的包,从而减少整体构建时间。
那为什么每次都要缓存?
这是必须的,项目会添加或移除某些依赖,为了保证后续构建能正常进行,因此需要更新缓存,也就是说每次都要npm i
然后把新的node_modules
缓存起来。
整体流程简化为:拉取缓存—>下载依赖->打包->上传->创建缓存。
回到上面的问题,看起来耗时就出现在了创建缓存这一步。
找到了问题所在,就可以考虑优化思路了:既然创建缓存这一步比较耗时,那么就尽可能不走这一步。
我们先把job拆分成安装和打包两个步骤,这样就可以使用job级别的cache来控制缓存
image: node:12.14.0
stages:
- pre-install
- build
pre_install:
stage: pre-install
script:
- npm install --registry=https://registry.npm.taobao.org
cache:
key: "node_modules"
paths:
- node_modules/
policy: pull-push
build_test:
stage: build
only:
- test
after_script:
- node scripts/publish.js --branch ${CI_COMMIT_REF_NAME}
cache:
key: "node_modules" # 使用与pre_install相同的cache:key
paths:
- node_modules/
policy: pull
script:
- npm run build:test
在pre_install
job中,配置pull-push
策略缓存,同时执行安装依赖的工作
在build_test
job中,只需要pull
缓存,然后执行打包即可。
不同的job职责分清之后,就可以进行优化了。看起来拉取缓存、下载依赖和创建缓存在package.json没有变化时是可以跳过的,只要不执行pre_install
,就不会进行缓存的拉去和推送,也就避开了创建缓存比较耗时的问题。
恰好job的配置项only:changes
可以指定某些文件变化时再触发
pre_install:
stage: pre-install
only:
# 这里貌似不能使用refs 指定分支,否则会忽略changes
changes:
- package.json
- package-lock.json
except:
- /develop|feature.*?/
script:
- npm install --unsafe-perm
cache:
key: "web_node_modules"
paths:
- node_modules/
policy: pull-push
# 定义各种build job
这样,只有在依赖更新的时候,才会执行pre_install
job,常规的业务迭代只会执行build
job
- 第一次
pre_install
+build
,耗时250s - 第二次只有
build
,耗时160s
相比之前每次都需要耗费300s+,非常明显地缩短了打包时间。
这里需要注意的是如果同时配置了only:changes
和only:refs
,就会忽略only:changes
目前推荐使用rules
来控制job的触发时机,但由于公司gitlab使用的runner版本较老,还不支持该规则,因此还是使用only:changes
来实现。
此外,随着打包任务的增加,node_modules/.cache
目录里面的文件也会越来越多(如vue-loader、babel-loader等文件缓存),这会导致在push cache的时候耗时增加,可以定期清理重置cache,减少缓存的文件数量。
小结
本文总结了如何通过拆分job并配置不同的缓存策略来避免前端项目构建耗时较长的问题。目前在线上运行了一段时间,感觉优化效果还是比较明显的。
对于开发工作之外的问题,一劳永逸的自动化确实是个不错的选择。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。