Docker volume speed up npm install

上一节决定在Jenkins中采用Docker作为构建环境,于是就可以为所欲为的使用各种node版本编译我们的项目。解决了版本切换问题。然而,Docker设计的目的就是纯净的执行环境,因此每次运行docker容器都相当于一个新的系统,所以就不会有缓存。而npm install需要下载大量的依赖,我们总不能每次都去下载吧。而且,node-sass的下载速度总是让人以为卡死了。作为CI,每天即便达不到成千上万次构建也算很频繁了。

经调研google, 复制node_modules可以快速加载依赖,但可操作性太差,需要定制脚本。复用npm cache基本可以解决离线缓存,减少联网下载的次数。

创建volume

通过如下方式可以在docker磁盘上创建一个磁盘卷npm_cache

sudo docker volume create npm_cache
> sudo docker volume ls
DRIVER VOLUME NAME
local 0cf39840bd652ef744137b177537357b1ce18a1b55521e381524501996db2ea2
local npm_cache

初始化是空的,位置在

> sudo docker volume inspect npm_cache
[
{
"CreatedAt": "2019-07-26T14:17:29+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/data/docker/volumes/npm_cache/_data",
"Name": "npm_cache",
"Options": {},
"Scope": "local"
}
]

使用volume, 这里通过-v指令在运行容器时挂载:

sudo docker run -d -v npm_cache:/root/.npm  -v `pwd`:/tmp  node

上述命令的含义是:

运行node容器,挂载磁盘npm_cache到/root/.npm, 挂载当前项目路径到/tmp. 这样就可以在/tmp目录下构建本项目。

测试构建时间

比如如下依赖,分别采用cache和不采用cache的构建时间比较

"dependencies": {
"axios": "^0.19.0",
"element-ui": "^2.11.0",
"vue": "^2.6.10",
"vue-router": "^3.0.7",
"vuex": "^3.1.1"
},
"devDependencies": {
"babel-core": "^6.26.3",
"node-sass": "^4.12.0"
}

不挂载.npm

added 220 packages from 163 contributors and audited 756 packages in 268.127s

found 0 vulnerabilities

挂载.npm并已有cache

added 220 packages from 163 contributors and audited 756 packages in 9.38s

found 0 vulnerabilities

同时,可以在本地磁盘看到缓存的依赖

root@ryan-computer:/data/docker/volumes/npm_cache/_data# tree -L 2
.
├── anonymous-cli-metrics.json
├── _cacache
│   ├── content-v2
│   ├── index-v5
│   └── tmp
├── _locks
└── node-sass
└── 4.12.0

Jenkins中使用

首先,安装Docker Pipeline Plugin.

使用Jenkinsfile构建流水线。

在Jenkinsfile中添加stage

stage('Build') {
echo "2. Build" try {
docker.image('node:12.6.0-buster').inside(" -v npm_cache:/home/node/.npm") {
sh 'npm install --registry=https://registry.npm.taobao.org;'
sh 'npm run test:ci --registry=https://registry.npm.taobao.org'
}
} catch (Exception ex) {
updateGitlabCommitStatus name: 'build', state: 'failed'
throw ex;
} updateGitlabCommitStatus name: 'build', state: 'success'
}

上述脚本将会在node中构建我们的项目并执行test. 本质上,上述命令会转换为

docker run -t -d -u 1000:1000 -v npm_cache:/home/node/.npm -w /data/opt/jenkins/workspace/dbrest-web_master -v /data/opt/jenkins/workspace/dbrest-web_master:/data/opt/jenkins/workspace/dbrest-web_master:rw,z -v /data/opt/jenkins/workspace/dbrest-web_master@tmp:/data/opt/jenkins/workspace/dbrest-web_master@tmp:rw,z -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** node:12.6.0-buster cat

需要注意的地方

我们为什么使用docker run -u 1000:1000?

因为执行命令的用户是Jenkins,Jenkins的id是1000,为了防止容器里构建的dist等asset文件权限变成root,需要使用当前dir拥有者的权限。说的有点绕,换句话说,docker将当前文件作为工作目录,构建会产生dist文件,这个dist文件的权限取决于runner。

cache为什么挂载到/home/node/.npm ?

这里就是docker初学者容易疏漏的地方,当docker -u uid:gid来运行容器的时候, 容器里的执行用户是这个id。有意思的是,node官方docker镜像的Dockerfile也专门创建了一个用户node, 其id也是1000. 所以,我们的容器在宿主机器以1000的Jenkins用户执行,容器内部以1000的node执行。

因此,workspace下node项目就会被编译。如果不喜欢使用Jenkins docker插件,也可以直接使用docker命令。

复用cache前后的对比

使用cache后时间reduce是分钟级别的。


以下来自官方文档:

设计流水线的目的是更方便地使用 Docker镜像作为单个 Stage或整个流水线的执行环境。 这意味着用户可以定义流水线需要的工具,而无需手动配置代理。 实际上,只需对 Jenkinsfile进行少量编辑,任何 packaged in a Docker container的工具, 都可轻松使用。

Jenkinsfile (Declarative Pipeline)
pipeline {
agent {
docker { image 'node:7-alpine' }
}
stages {
stage('Test') {
steps {
sh 'node --version'
}
}
}
}

当流水线执行时, Jenkins 将会自动地启动指定的容器并在其中执行指定的步骤:

[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] sh
[guided-tour] Running shell script
+ node --version
v7.4.0
[Pipeline] }
[Pipeline] // stage
[Pipeline] }

容器的缓存数据

许多构建工具都会下载外部依赖并将它们缓存到本地以便于将来的使用。 由于容器最初是由 "干净的" 文件系统构建的, 这导致流水线速度变慢, 因为它们不会利用后续流水线运行的磁盘缓存。 on-disk caches between subsequent Pipeline runs.

流水线支持 向Docker中添加自定义的参数, 允许用户指定自定义的 Docker Volumes 装在, 这可以用于在流水线运行之间的 agent上缓存数据。下面的示例将会在 流水线运行期间使用 maven container缓存 ~/.m2, 从而避免了在流水线的后续运行中重新下载依赖的需求。

Jenkinsfile (Declarative Pipeline)
pipeline {
agent {
docker {
image 'maven:3-alpine'
args '-v $HOME/.m2:/root/.m2'
}
}
stages {
stage('Build') {
steps {
sh 'mvn -B'
}
}
}
}

使用多个容器

代码库依赖于多种不同的技术变得越来越容易。比如, 一个仓库既有基于Java的后端API 实现 and 有基于JavaScript的前端实现。 Docker和流水线的结合允许 Jenkinsfile 通过将 agent {} 指令和不同的阶段结合使用 multiple 技术类型。

Jenkinsfile (Declarative Pipeline)
pipeline {
agent none
stages {
stage('Back-end') {
agent {
docker { image 'maven:3-alpine' }
}
steps {
sh 'mvn --version'
}
}
stage('Front-end') {
agent {
docker { image 'node:7-alpine' }
}
steps {
sh 'node --version'
}
}
}
}

脚本化流水线的高级用法

运行 "sidecar" 容器

在流水线中使用Docker可能是运行构建或一组测试的所依赖的服务的有效方法。类似于 sidecar 模式, Docker 流水线可以"在后台"运行一个容器 , 而在另外一个容器中工作。 利用这种sidecar 方式, 流水线可以为每个流水线运行 提供一个"干净的" 容器。

考虑一个假设的集成测试套件,它依赖于本地 MySQL 数据库来运行。使用 withRun 方法, 在 Docker Pipeline 插件中实现对脚本化流水线的支持, Jenkinsfile 文件可以运行 MySQL作为sidecar :

node {
checkout scm
/*
* In order to communicate with the MySQL server, this Pipeline explicitly
* maps the port (`3306`) to a known port on the host machine.
*/
docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw" -p 3306:3306') { c ->
/* Wait until mysql service is up */
sh 'while ! mysqladmin ping -h0.0.0.0 --silent; do sleep 1; done'
/* Run some tests which require MySQL */
sh 'make check'
}
}

该示例可以更进一步, 同时使用两个容器。 一个 "sidecar" 运行 MySQL, 另一个提供执行环境, 通过使用Docker 容器链接。

node {
checkout scm
docker.image('mysql:5').withRun('-e "MYSQL_ROOT_PASSWORD=my-secret-pw"') { c ->
docker.image('mysql:5').inside("--link ${c.id}:db") {
/* Wait until mysql service is up */
sh 'while ! mysqladmin ping -hdb --silent; do sleep 1; done'
}
docker.image('centos:7').inside("--link ${c.id}:db") {
/*
* Run some tests which require MySQL, and assume that it is
* available on the host name `db`
*/
sh 'make check'
}
}
}

上面的示例使用 withRun公开的项目, 它通过 id 属性具有可用的运行容器的ID。使用该容器的 ID, 流水线通过自定义 Docker 参数生成一个到inside() 方法的链。

The id property can also be useful for inspecting logs from a running Docker container before the Pipeline exits:

sh "docker logs ${c.id}"

来源参考

Docker volume speed up npm install的更多相关文章

  1. docker volume创建、备份、nfs存储

    docker存储volume #环境 centos7.4 , Docker version 17.12.0-ce docker volume创建.备份.nfs存储 #docker volume 数据存 ...

  2. Docker学习笔记之docker volume 容器卷的那些事(一)

    预览目录 volume 方式 相关用例 使用方式 使用 volume driver bind mount 方式 相关用例 使用方式 配置selinux标签 配置macOS的安装一致性 tmpfs 方式 ...

  3. (转)Docker volume plugin - enabled create local volume on docker host

    原文地址:https://hub.docker.com/r/cwspear/docker-local-persist-volume-plugin/ Short Description Create n ...

  4. 为什么npm install 经常失败

    Hello 您好,我是大粽子.深耕线上商城的攻城狮(程序员)一枚. 前言 这段时间真的是忙,最近能抽时间搞搞大家在自己环境中遇到的各种问题了,我呢就是见不得我的代码在你的电脑运行不起来的.就像姜子牙睡 ...

  5. 转:解决npm install慢的问题

    使用NPM(Node.js包管理工具)安装依赖时速度特别慢,为了安装Express,执行命令后两个多小时都没安装成功,最后只能取消安装,笔者20M带宽,应该不是我网络的原因,后来在网上找了好久才找到一 ...

  6. Npm install failed with “cannot run in wd”

    Linux环境下,root账户,安装某些npm包的时候报下面的错误,例如安装grunt-contrib-imagemin时: Error: EACCES, mkdir '/usr/local/lib/ ...

  7. npm全局安装和本地安装和本地开发安装(npm install --g/--save/--save-dev)

    详细说明参考:http://www.cnblogs.com/PeunZhang/p/5629329.html 我个人理解: 1.全局安装(npm install -g)是为了用命令行,比如在windo ...

  8. npm install报错Error: ENOENT

    E:\projects\ueditor\ueditor1_4_3_3-src>npm installError: ENOENT, stat 'C:\Users\Lucas\AppData\Roa ...

  9. Windows环境下npm install常见错误

    Windows环境下npm install安装包依赖时,常出现一些错误,下面为个人解决办法: 错误一 缺少python环境: G:\nodejs\moviesite\node_modules\bcry ...

随机推荐

  1. SYN5307型数字式电秒表

    SYN5307型数字式电秒表 产品概述 SYN5307型数字式电秒表是由西安同步电子科技有限公司精心设计生产的一款多功能,高精度电秒表.该仪器采用高精度石英晶振作为测量基准,测量准确度高于一般的电秒表 ...

  2. Flutter学习笔记(2)--Dart语言简介

    Dart简介: Dart诞生于2011年10月10日,Dart是一种"结构化的web编程"语言,Dart虽然是谷歌开发的计算机编程语言,但后来被ECMA认定位标准,这门语言用于We ...

  3. 读书笔记——《谁说菜鸟不会数据分析—Python篇》

    最近刚读完一本新书,关注的公众号作者出的“谁说菜鸟不会数据分析—Python篇”,话说现在很多微信公众号大牛都在出书,这貌似是一个趋势.. 说说这本书吧,我之前看过一些网文,对于数据分析这一块也有过一 ...

  4. 浅谈ASP.NET Core中IOC与DI的理解和使用

    说起IOC和DI,使用过ASP.NET Core的人对这两个概念一定不陌生,早前,自己也有尝试过去了解这两个东西,但是一直觉得有点很难去理解,总觉得对其还是模糊不清,所以,趁着今天有空,就去把两个概念 ...

  5. spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...

  6. Nginx查看并发连接数

    Nginx查看并发连接 通过界面查看 通过界面查看通过web界面查看时Nginx需要开启status模块,也就是安装Nginx时加上 --with-http_stub_status_module 然后 ...

  7. asp.net core 2.2 生产环境直接更新View页面并立即生效

    有的时候我们会直接在生产环境上更新页面文件,比如更改了JS代码,CSS代码或页面的文案,布局等.这种没有改到后台代码的情况一般就是直接发布页面文件了,在asp.net core 2.2以前的版本(没有 ...

  8. jmeter分析性能报告时的误区

    概述 我们用jmeter做性能测试,必然需要学会分析测试报告.但是初学者常常因为对概念的不清晰,最后被测试报告带到沟里去. 常见的误区 分析响应时间全用平均值 响应时间不和吞吐量挂钩 响应时间和吞吐量 ...

  9. HBase Region重点剖析

    Region的概念 Region是HBase数据管理的基本单位.数据的move,数据的balance,数据的split,都是按照region来进行操作的. region中存储这用户的真实数据,而为了管 ...

  10. 关于自己"手机瘾"的切身体验

    其实之前也喜欢看手机,只是从去年7月份开始,因为自控能力.新项目等原因,导致"手机瘾"这个病更严重了,控制不住,一看就是几小时.去年过年的时候,自己定的第一个目标就是戒掉手机瘾,到 ...