【问题标题】:Cache NPM dependencies on Jenkins pipeline在 Jenkins 管道上缓存 NPM 依赖项
【发布时间】:2018-04-02 20:38:30
【问题描述】:

我们都知道使用 npm 下载依赖项可能非常耗时,尤其是当我们受限于旧的 npm 版本时。

对我来说,作为一名开发人员,这没什么大不了的,因为我不得不在我的本地开发机器上执行此操作的次数非常少,而且一切都与我项目文件夹中的 node_modules 缓存一起工作。但现在我想通过 Jenkins 将这些应用程序带到 CI 环境中。

我意识到在使用 npm 下载依赖项上花费了大量时间。这是一个问题,因为:

  1. npm 下载项目文件夹中的依赖项,而不是 Maven 的 /home/user/.m2 等全局文件夹

  2. 我必须在每次运行时清理 Jenkins 工作区文件夹以避免 git checkout 出现问题。

我想要一个非常优雅的解决方案来缓存我的 Jenkins slave 上的 npm 依赖项,但到目前为止我只能想到:

  1. 从 Jenkins 工作区中删除除 node_modules 文件夹之外的所有内容。我不喜欢这样,因为如果我继续为我的项目创建分支,我可能会消耗大量的 HDD。每个分支都会创建一个工作区。

  2. 在每次 npm 安装后执行 cp ./node_modules /home/npm_cache 之类的操作,然后在代码签出后执行 cp /home/npm_cache ./node_modules 之类的操作。

我觉得这些解决方案很糟糕。必须有更好的方法来做到这一点。

【问题讨论】:

  • 我不确定在您的情况下下载 NPM 依赖项需要多长时间,但我们通过在中间添加 Nexus 作为代理管理器而不是每次都从 Internet 下载依赖项来最大限度地减少时间。当然,您提到的选项也普遍遵循。
  • 您找到更好的解决方案了吗? npm 下载占用了我们构建时间的一半以上 :(

标签: node.js caching jenkins dependencies


【解决方案1】:

我对 node.js 的了解还不够,不知道如何在那边处理这个问题。但是在 Linux 机器上可以处理的一种简单方法是在您从 git 签出后立即将缓存目录符号链接到外部位置。每台代理机器都将维护自己的缓存,但无论解决方案如何,您都可能必须这样做。

我假设你已经调查了 nodeJS 插件,但它不能做你想做的事。

【讨论】:

  • 是的,但是使用 node 时,软件包会在 package.json 文件旁边下载,该文件定义了依赖关系。所以,我必须将 package.json 从项目复制到缓存文件夹,在那里下载依赖项,然后创建符号链接......我不确定......我正在寻找非常直接的东西。跨度>
  • 啊。我知道了。我告诉过你我对 node.js 知之甚少。 :) 并不是说​​它可以更好地解决问题,但我认为您可以制作符号链接,复制 packages.json,然后下载依赖项。仍然不是很优雅。
【解决方案2】:

NPM 有一个全局缓存存储在~/.npm

【讨论】:

  • 某些依赖项具有下载二进制文件的安装后步骤,并且可能不会按照您的意愿使用缓存(例如 puppeteer,它每次都会下载 chrome)。
【解决方案3】:

我在 Jenkins 管道中为 3 个不同的项目所做的是使用 tar 而不是 cp,然后是 npm install 而不是 npm ci,每个项目:

  1. cd你的项目
  2. npm i
  3. tar cvfz ${HOME}/your_project_node_modules.tar.gz node_modules

然后在管道中:

dir(your_project){
  sh "tar xf ${HOME}/your_project_node_modules.tar.gz"
  sh "npm i"
}

当然,它的缺点是随着时间依赖性的变化,安装会花费更长的时间,但我设法将映像中的磁盘空间使用量减少了大约 0.5GB,tarcp 快得多(cp ~30 秒,tar ~5 秒)

就我而言,总安装时间从大约 3 分钟缩短到几秒钟。

【讨论】:

  • 谢谢!但是我有以下问题:由于某种原因提取存档内容需要很长时间。我看到了我需要使用--occurence=1 标志的答案,但操作系统是Windows。也许你知道如何解决这个问题?
  • AFAIK,Windows 没有tar,你有 Cygwin 什么的吗?这可能是问题所在。
  • 我使用sh.exe,由 git 提供。我试图在其他来源中找到答案,现在我认为这是预期的行为。从.tar.gz 中提取文件夹(提取后的数据大小约为 600 MB)持续 10-15 分钟。但如果我错了,请纠正我
  • 对于 Windows,我更喜欢 7zip,请参阅 here
  • 我的案例提取速度没有提高
【解决方案4】:

我创建了这样的脚本来检查 Jenkins 中 package.json 的 md5sum:

stage('NPM Build') {
  steps {
    sh '''
    node -v && npm -v
    '''
    // rm -rf node_modules
    sh '''
    CACHE_FOLDER=${HOME}/.cache/md5
    echo "EXECUTOR_NUMBER: ${EXECUTOR_NUMBER}"
    MD5_FILE_NAME=package-json_${EXECUTOR_NUMBER}.md5sum

    [ -d ${CACHE_FOLDER} ] || mkdir -p ${CACHE_FOLDER}
    ls ${CACHE_FOLDER}

    if [ -f ${CACHE_FOLDER}/${MD5_FILE_NAME} ];then
      cp ${CACHE_FOLDER}/${MD5_FILE_NAME} ${MD5_FILE_NAME}
      md5sum package.json
      cat ${MD5_FILE_NAME}
      md5sum -c ${MD5_FILE_NAME} || npm ci
    else
      echo "No md5sum backup"
      npm ci
    fi

    echo "create new md5sum backup"
    md5sum package.json
    md5sum package.json > ${MD5_FILE_NAME}
    cp ${MD5_FILE_NAME} ${CACHE_FOLDER}
    '''
    sh '''
    npm run ngcc
    '''
    sh '''
    npm run build
    '''
  }
}

【讨论】:

    【解决方案5】:

    Jenkinsfile 的这些部分将执行以下操作:

    在分支 master 和 develop 上,总是执行全新的 npm install。

    在所有其他分支上,package.json 将被 md5 散列并且在 npm 安装之后 node_modules 文件夹将被放置在定义的缓存文件夹中,如: //node_modules。

    下一次构建可以重用node_modules,不必重新下载所有node_modules。

    parameters {
        booleanParam(name: "CACHED_NODE_MODULES",
                description: "Should node_modules be taken from cache?",
                defaultValue: !'master'.equals(env.BRANCH_NAME) && !'develop'.equals(env.BRANCH_NAME))
    }
    

    ...

    stage('Build') {
       steps {
          cacheOrRestoreNodeModules()
          echo "Performing npm build..."
          sh 'npm install'
    
       }
    }
    

    ...

    def cacheOrRestoreNodeModules() {
    if (params.CACHED_NODE_MODULES) {
        sh '''
        MD5_SUM_PACKAGE_JSON=($(md5sum package.json))
        CACHE_FOLDER=/home/jenkins/.cache/npm/${MD5_SUM_PACKAGE_JSON}
        
        # check if folder exists and copy node_modules to current directory
        if [ -d ${CACHE_FOLDER} ]; then
          cp -r ${CACHE_FOLDER}/node_modules .
        fi
        
        npm install --no-audit
        
        # if folder does not exists, create it and cache node_modules folder
        if ! [ -d ${CACHE_FOLDER} ]; then
          mkdir -p ${CACHE_FOLDER}
          cp -r node_modules ${CACHE_FOLDER}/node_modules
        fi
        '''
    }
    

    }

    【讨论】:

    • 或者您可以使用pnpm.io,这将使您的构建速度显着加快(也在本地)。它使用与 npm 相同的 API...
    【解决方案6】:

    我选择在新的 docker 容器中运行每个构建,但仍然可以完成依赖项缓存。这就是我所做的:

    • 每个项目都有一个用于 npm 包的缓存,这些包被压缩到包含 node_modules 文件夹的文件中。这些 zip 都存储在主机(运行构建的节点)内的 /home/.cache/node_modules 文件夹中。因此,在启动 docker 容器时,它必须具有类似的绑定挂载
    docker { 
        image dockerImage
        args "... -v \"/home/.cache/node_modules:/home/.cache/node_modules\""
    }
    
    • 我正在使用带有自定义构建步骤的共享库,它的实现或多或少是这个:
    sh """#!/bin/bash -xe
        function getNodeModulesListHash {
            npm ls 2> /dev/null | md5sum | cut -d ' ' -f 1
        }
        
        frontendProjectHashZip="\$(echo "${project}" | md5sum | cut -d ' '  -f 1).tar"
        [[ -f "/home/.cache/node_modules/\$frontendProjectHashZip" ]] && tar -xf "/home/.cache/node_modules/\$frontendProjectHashZip"
    
        hashBeforeInstall="\$(getNodeModulesListHash)"
        npm install
        hashAfterInstall="\$(getNodeModulesListHash)"
    
        if [[ \$hashBeforeInstall != \$hashAfterInstall ]]
        then 
            tar -cf \$frontendProjectHashZip node_modules
            rm -f "/home/.cache/node_modules/\$frontendProjectHashZip"
            mv \$frontendProjectHashZip "/home/.cache/node_modules/\$frontendProjectHashZip"
        fi
    """
    

    getNodeModulesListHash 用于获取当前安装包的哈希值。这个哈希值是在 npm install 之前和之后计算的,因此如果它们的值相同,那么我不需要使用 node_modules 重新创建 zip 文件,但我可以保留最初提取的那个。其余的都很简单,逻辑与其他用户提出的非常相似。

    【讨论】:

      猜你喜欢
      • 2021-02-26
      • 2021-03-26
      • 2020-04-16
      • 1970-01-01
      • 2021-01-21
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 2017-06-10
      相关资源
      最近更新 更多