如何在ECS的Jenkins Docker流水线中加速NPM构建?
嘿,我之前在ECS上跑Jenkins流水线的时候也碰到过npm install慢到崩溃的问题,给你几个亲测有效的简单方案,都不用大改现有流程:
方案1:用Jenkins流水线缓存插件缓存依赖
直接用Jenkins的Pipeline Cache Plugin,这个插件能帮你把项目的node_modules或者npm的全局缓存目录(一般是~/.npm)缓存到Jenkins的共享存储里,下次构建时直接复用,不用重新下载所有依赖。
配置起来也简单,在你的流水线脚本里加这么一段:
pipeline { agent { docker { image '你的测试依赖镜像' } } stages { stage('安装依赖') { steps { cache(path: 'node_modules', key: "npm-{{ checksum 'package-lock.json' }}") { sh 'npm install' } } } // 后面的构建、测试步骤照旧 } }
它的原理是通过package-lock.json的哈希值作为缓存键,只有当依赖版本发生变化时,才会重新执行完整的npm install;如果依赖没动,直接把缓存的node_modules拉过来用,能省超多时间。
方案2:挂载共享存储的npm缓存目录
既然每次容器销毁后缓存就没了,那我们可以把npm的全局缓存目录挂载到ECS的共享存储上(比如EFS,或者实例的本地磁盘),让缓存能跨构建保留下来。
在Jenkins的Docker agent配置里加个挂载参数就行:
agent { docker { image '你的测试依赖镜像' args '-v /ecs-shared-npm-cache:/root/.npm' } }
这样每次构建时,npm会把下载的包存到宿主机的/ecs-shared-npm-cache里,下次构建直接复用缓存,不用重复下载。如果是ECS集群,用EFS的话所有实例都能共用同一个缓存池,效果更好。
方案3:换成pnpm代替npm
这个是最简单的操作,几乎不用改现有流程,只要把npm install换成pnpm install就行。
pnpm的缓存机制比npm高效太多,它会把所有下载的包存在全局缓存里,然后用硬链接的方式关联到项目的node_modules,不仅安装速度能快3-5倍,还能省不少磁盘空间。
只要你的测试镜像里装了pnpm就行,如果没装,在构建前加一步npm install -g pnpm(或者直接用带pnpm的基础镜像,比如pnpm/node:alpine)。
方案4:用npm的半离线模式
先找个地方(比如本地或者专门的预构建任务)把依赖下载好,生成对应package-lock.json的缓存包,然后把这些缓存上传到ECS能访问的共享存储(比如挂载的S3目录)。
每次构建时,先把缓存目录复制到容器的~/.npm下,再执行半离线安装:
# 复制缓存到容器的npm目录 sh 'cp -r /shared-npm-cache/* ~/.npm/' # 优先用本地缓存安装,缓存没有的再去下载 sh 'npm install --prefer-offline'
这样大部分依赖都能从缓存取,只有新增的依赖才会去下载,速度提升很明显。
方案5:用多阶段构建优化镜像(兼顾大小和缓存)
你说预装依赖会让镜像大三倍,那可以试试Docker的多阶段构建,把依赖安装和运行环境分开,既能保留依赖缓存,又不会让镜像变太大:
# 第一阶段:用完整的Node镜像安装依赖(只在package.json变化时重新构建) FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm install # 第二阶段:用轻量的Alpine镜像,只复制必要的依赖和代码 FROM node:18-alpine WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY . . # 这里加你的启动/测试命令
这样最终的镜像还是很小,而且只要package*.json没变化,Docker会自动缓存第一阶段的依赖安装层,下次构建镜像时直接复用,Jenkins拉这个镜像的时候,依赖已经在里面了,就不用每次都跑npm install了。
内容的提问来源于stack exchange,提问作者RB26

