加3行代碼減少80%構(gòu)建時(shí)間
背景
最近接手的BI項(xiàng)目在Jenkins的構(gòu)建機(jī)上構(gòu)建耗時(shí)比較久,日常構(gòu)建耗時(shí)都在 20min 以上,即使改動(dòng)一行代碼也要構(gòu)建這么久。構(gòu)建耗時(shí)截圖如下:
構(gòu)建耗時(shí)較長導(dǎo)致日常測(cè)試和正式發(fā)版都會(huì)浪費(fèi)很多時(shí)間等待,對(duì)研發(fā)流程影響較大(主要是我忍不了)。因此需要對(duì)構(gòu)建速度進(jìn)行優(yōu)化。
優(yōu)化思路分析
要優(yōu)化項(xiàng)目的構(gòu)建速度,得先了解構(gòu)建流程:
開發(fā)人員推送代碼到 Gitlab,觸發(fā) Gitlab 服務(wù)器的 Push Events
Push Events被觸發(fā)后,會(huì)調(diào)用提前配置好的 Jenkins webhooks
Jenkins webhooks被調(diào)用后,會(huì)執(zhí)行對(duì)應(yīng)項(xiàng)目的構(gòu)建任務(wù)
構(gòu)建任務(wù)開始后先拉取項(xiàng)目源碼到構(gòu)建機(jī),再使用docker build構(gòu)建鏡像
docker 構(gòu)建鏡像分為兩個(gè)階段,先使用npm scripts構(gòu)建前端項(xiàng)目,然后把構(gòu)建產(chǎn)物拷貝到nginx基礎(chǔ)鏡像
在這個(gè)流程中,可以優(yōu)化的環(huán)節(jié)只有構(gòu)建docker鏡像這一步,其他環(huán)節(jié)的耗時(shí)基本可以忽略不計(jì)。而在不大改項(xiàng)目的情況下能起到明顯提速效果的方案是:緩存策略。構(gòu)建docker鏡像時(shí)可以用到的緩存包括兩類:docker層緩存和應(yīng)用層緩存。
docker層緩存是指docker build所產(chǎn)生的可重用鏡像層,只要Dockerfile中的命令及相關(guān)的源文件未改變,就能直接使用這些鏡像緩存。這種緩存策略在代碼不改變的情況下效果很好,構(gòu)建耗時(shí)甚至可以控制在 10 秒內(nèi)。而對(duì)于日常開發(fā)情況下,代碼頻繁變化,如果應(yīng)用本身構(gòu)建時(shí)間又很長,則需要使用應(yīng)用層緩存。(上一篇文章《docker build 緩存失效分析》中有 docker 層緩存相關(guān)介紹,也可以看看官方文檔、中文文檔,本文不再贅述)
應(yīng)用層緩存是指應(yīng)用構(gòu)建所產(chǎn)生的中間產(chǎn)物,這些中間產(chǎn)物主要是node_modules目錄中的物理文件,其中包括npm install下載的依賴包和npm run build產(chǎn)生的.cache目錄文件。而docker build每次都會(huì)初始化全新的環(huán)境用于構(gòu)建,新環(huán)境中不存在node_modules目錄,因此每次都是重新寫入而無法復(fù)用,得想辦法復(fù)用該目錄下的文件;另外npm run build需要開啟緩存功能,才會(huì)輸出緩存文件到node_modules/.cache目錄。
綜上,優(yōu)化思路主要是兩點(diǎn):1、開啟應(yīng)用層構(gòu)建緩存(如webpack cache);2、持久化node_modules目錄,確保每次npm install和npm run build都能復(fù)用該目錄下的文件。
開啟應(yīng)用層構(gòu)建緩存
項(xiàng)目使用的技術(shù)是React,構(gòu)建主要依靠react-scripts@4.0.3,底層實(shí)際調(diào)用的是webpack@4.44.2,應(yīng)用構(gòu)建緩存主要來自webpack。webpack需要手工開啟緩存功能(官方文檔傳送門),配置cache屬性為true即可。
實(shí)際操作只有 1 步, 找到webpack.config.js設(shè)置cache:true,代碼如下:
module.exports = {
//...
cache: true
};
復(fù)制代碼
本地首次npm run build構(gòu)建,無緩存的情況下,耗時(shí) 13min 左右。
啟用緩存后在本地進(jìn)行二次構(gòu)建,有緩存的情況下,無論是否修改源碼構(gòu)建耗時(shí)均為 4min 左右,比優(yōu)化前的 13min 有明顯提升。 構(gòu)建耗時(shí)截圖如下:
實(shí)際上,webpack@4的緩存只在watch和development模式下生效,在上述構(gòu)建測(cè)試中其實(shí)不起作用。 實(shí)測(cè)刪除wepack中的cache:true配置,或者配置為cache:false,二次構(gòu)建時(shí)間也是 4min 左右。
之所以構(gòu)建速度提升了那么多,是因?yàn)閞eact-scripts的webpack配置中開啟了babel-loader和eslint-webpack-plugin的緩存功能,另外terser-webpack-plugin配置也默認(rèn)開啟了緩存功能。從緩存目錄node_modules/.cache中也能看到它們的緩存文件。
所以,這一步其實(shí)啥也不用做,如果想進(jìn)一步提速可以升級(jí)到webpack@5。
持久化node_modules目錄
想在docker build環(huán)境中持久化node_modules需要使用到BuildKit的mount功能,該功能有幾個(gè)前置條件:
docker 版本必須高于 18.09
BuildKit需要手工啟用,可在docker build命令前添加環(huán)境變量DOCKER_BUILDKIT=1啟用
如果前兩個(gè)條件不滿足,則需要具備Jenkins和構(gòu)建機(jī)的讀寫權(quán)限,以調(diào)整構(gòu)建環(huán)境參數(shù)
修改Dockerfile,使用RUN --mount=type=cache運(yùn)行npm install和npm run build指令(--mount=type=cache說明文檔傳送門)
開啟BuildKit還有其他特性,比如輸出日志更友好,基本每一步都會(huì)輸出耗時(shí),就這一條,值了!
實(shí)際操作分為 2 步:1、修改Jenkins配置,在docker build命令前加上環(huán)境變量。修改后鏡像構(gòu)建命令長這樣:
DOCKER_BUILDKIT=1 docker build .
復(fù)制代碼
2、修改Dockerfile,將RUN npm install和RUN npm run build指令改為RUN --mount=type=cache npm xxx。修改后Dockerfile長這樣:
FROM node:alpine as builder
WORKDIR /app
COPY package.json /app/
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
--mount=type=cache,target=/root/.npm,id=npm_cache \
npm i --registry=https://registry.npm.taobao.org
COPY src /app/src
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
npm run build
復(fù)制代碼
文檔說由于 BuildKit 為實(shí)驗(yàn)特性,需要在 Dockerfile 文件開頭加上如下代碼:# syntax = docker/dockerfile:experimental。在Docker 20.10環(huán)境下,加了上述代碼反而構(gòu)建報(bào)錯(cuò),原因是加載外網(wǎng)資源失敗,刪除后構(gòu)建成功。這不就是玄學(xué)嗎???
優(yōu)化結(jié)果
在配置好緩存策略后,模擬日常開發(fā)修改項(xiàng)目代碼觸發(fā)自動(dòng)構(gòu)建流程,構(gòu)建耗時(shí)從 20min+下降到 4min+,總體耗時(shí)減少 80%。整個(gè)優(yōu)化過程修改了Jenkins的一行配置,另外在Dockerfile中添加了3行代碼,改動(dòng)很少但效果很不錯(cuò)。
作者:Whilconn
原文:https://juejin.cn/post/7135756687134162980
作者:Whilconn
歡迎關(guān)注微信公眾號(hào) :深圳灣碼農(nóng)