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. SYN6101型 RS485子钟

    SYN6101型  RS485子钟 产品概述 SYN6101型RS485子钟是由西安同步电子科技有限公司精心设计.自行研发生产的一套以通过RS485总线复接或串行与母钟连接的子钟,接收母钟发送来的时间 ...

  2. 302Java_前定义

    第零章 前定义 1 介绍 1.1 简介 Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. ...

  3. 一线Python运维开发带你秒懂Flask框架

    相信曾经纠结过这个问题:怎样才能彻底掌握flask? Flask是一个使用 Python 编写的轻量级 Web 应用框架.其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 . ...

  4. LVS-DR模式部署流程

    情景一 一.环境介绍 1)RIP.VIP.DIP为同一物理网络 2)LVS Hostname:lvs eth0:DIP-192.168.3.31 eth0:0:VIP-192.168.3.10 3)R ...

  5. 如何用 Flutter 实现混合开发?闲鱼公开源代码实例

    Flutter: 必火,转两篇软文预热哈哈~ 中文网: https://flutterchina.club/get-started/test-drive/ 如何用 Flutter 实现混合开发?闲鱼公 ...

  6. maven的下载与安装,卸载替换eclipse自带的maven

    首先呢,博主在这里给大家一个建议,最好不要用eclipse自带的maven.因为这家伙总会出现一些这样那样的错误,比如常见的jar包下载不全或者是install打包报错等等. 博主用了一段时间,还是觉 ...

  7. 解决IE8placeholder属性问题

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  8. 使用docker运行GitLab

    从docker镜像拉取代码,docker pull gitlab/gitlab-ce:latest. 创建/srv/gitlab目录sudo mkdir /srv/gitlab 启动GitLab CE ...

  9. 使用Appium做手机自动化录制问题

    最近在使用appium做Android手机自动化脚本录制, 发现点击“tap”时,一直没有用,页面还是不能跳转. 咋办?发愁... 于是看到旁边有个“sendkeys”,那是不是能够直接发送参数不就行 ...

  10. kubernetes实战之部署一个接近生产环境的consul集群

    系列目录 前面我们介绍了如何在windows单机以及如何基于docker部署consul集群,看起来也不是很复杂,然而如果想要把consul部署到kubernetes集群中并充分利用kubernete ...