前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker volume speed up npm install

Docker volume speed up npm install

作者头像
Ryan-Miao
发布2019-07-28 15:00:33
1.6K0
发布2019-07-28 15:00:33
举报
文章被收录于专栏:Ryan MiaoRyan Miao

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

代码语言:javascript
复制
sudo docker volume create npm_cache
> sudo docker volume ls
DRIVER              VOLUME NAME
local               0cf39840bd652ef744137b177537357b1ce18a1b55521e381524501996db2ea2
local               npm_cache

初始化是空的,位置在

代码语言:javascript
复制
> 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指令在运行容器时挂载:

代码语言:javascript
复制
sudo docker run -d -v npm_cache:/root/.npm  -v `pwd`:/tmp  node 

上述命令的含义是:

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

测试构建时间

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

代码语言:javascript
复制
"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

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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. 本质上,上述命令会转换为

代码语言:javascript
复制
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的工具, 都可轻松使用。

代码语言:javascript
复制
Jenkinsfile (Declarative Pipeline)
pipeline {
    agent {
        docker { image 'node:7-alpine' }
    }
    stages {
        stage('Test') {
            steps {
                sh 'node --version'
            }
        }
    }
}

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

代码语言:javascript
复制
[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, 从而避免了在流水线的后续运行中重新下载依赖的需求。

代码语言:javascript
复制
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 技术类型。

代码语言:javascript
复制
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 :

代码语言:javascript
复制
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 容器链接。

代码语言:javascript
复制
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}"

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Docker volume speed up npm install
    • 创建volume
      • 测试构建时间
        • Jenkins中使用
          • 需要注意的地方
            • 容器的缓存数据
            • 使用多个容器
            • 脚本化流水线的高级用法
        相关产品与服务
        容器镜像服务
        容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档