Jenkins管道中Docker环境下npm安装失败

68

我正在跟随有关Jenkins管道的教程,我可以在Node 6.10 Docker容器下运行“hello world”。

但是,当我将默认的EmberJS应用程序(使用ember init)添加到存储库并尝试在管道中构建它时,在运行npm install时会失败(因为目录访问问题)。 Jenkinsfile可以在此处查看:https://github.com/CloudTrap/pipeline-tutorial/blob/fix-build/Jenkinsfile

构建打印的错误消息是(本地安装并在Macbook上使用java -jar jenkins.war运行,与问题无关但包含在内):

npm ERR! Linux 4.9.12-moby
npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install"
npm ERR! node v6.10.0
npm ERR! npm  v3.10.10
npm ERR! path /.npm
npm ERR! code EACCES
npm ERR! errno -13
npm ERR! syscall mkdir

npm ERR! Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!  { Error: EACCES: permission denied, mkdir '/.npm'
npm ERR!     at Error (native)
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/.npm',
npm ERR!   parent: 'pipeline-tutorial' }
npm ERR! 
npm ERR! Please try running this command again as root/Administrator.

注意:我希望不以root / sudo身份运行npm install。
更新:我已经取得了一些进展,如下所示:
我在日志中找到了Jenkins使用容器构建的命令:
[Pipeline] withDockerContainer
$ docker run -t -d -u 501:20 -w /long-workspace-directory -v /long-workspace-directory:/long-workspace-directory:rw -v /long-workspace-directory@tmp:/long-workspace-directory@tmp:rw -e

当docker镜像运行时,它的工作目录是一个名为/long-workspace-directory的路径(实际上是一个看起来很神秘的jenkins工作空间路径),用户ID为501(组ID为20),等等。该用户没有名称(显然这破坏了与此问题无关的其他内容)。
  1. Changed agent to use a Dockefile:

    agent {
      dockerfile {
        filename 'Dockerfile'
        args '-v /.cache/ -v /.bower/  -v /.config/configstore/'
      }
    }
    
  2. Specify args '-v ...' for creating volumes for the directories npm install / bower needs.


5
通常情况下,不建议以 root 身份运行 npm install,这被认为是非常不好的做法。我希望避免这种情况。 - les2
请查看nvm - Ebrahim Pasbani
1
我不会安装任何全局依赖项。在工作中和现有的 CI 设置中,我广泛使用 nvm。 - les2
我认为我在错误地使用这个图像。节点图像用于运行节点服务器应用程序,而不是运行一系列构建命令。 - les2
或者您可以像这里一样在Dockerfile内部更改文件夹权限。 - Claudiu Hojda
显示剩余4条评论
13个回答

81

添加环境并将主目录设置为'.',解决方法如下。

pipeline {
    agent { docker { image 'node:8.12.0' } }
    environment {
        HOME = '.'
    }
    stages {
        stage('Clone') {
            steps {
                git branch: 'master',
                    credentialsId: '121231k3jkj2kjkjk',
                    url: 'https://myserver.com/my-repo.git'
            }
        }
        stage('Build') {
            steps {
                sh "npm install"
            }
        }
    }
}

8
哇,太神奇了!这个方法也解决了我的问题,谢谢。我想 npm 依赖于 HOME 变量,而 Jenkins 没有设置它。 - Lloyd
环境也可以在单个阶段中设置。谢谢! - Matthias Gwiozda

37
https://github.com/jenkins-infra/jenkins.io/blob/master/Jenkinsfile
docker.image('openjdk:8').inside {
    /* One Weird Trick(tm) to allow git(1) to clone inside of a
    * container
    */
    withEnv([
        /* Override the npm cache directory to avoid: EACCES: permission denied, mkdir '/.npm' */
        'npm_config_cache=npm-cache',
        /* set home to our current directory because other bower
        * nonsense breaks with HOME=/, e.g.:
        * EACCES: permission denied, mkdir '/.config'
        */
        'HOME=.',
    ]) {
            // your code
    }
}

12
只需设置HOME=.即可,因为npm_config_cache$HOME/.npm,所以在这种情况下它将是./.npm而不是/.npm - Ido Sorozon
1
我可以确认,只需设置“HOME =。”即可。 - amarillion
12
为了完整性,environment { HOME="." } 可以解决 Jenkins 声明式语法中的问题。 - bordeltabernacle
2
我可以确认,仅设置“npm_config_cache = npm-cache”就足够了。 - Ricardo Vigatti

19

在这个问题上浪费了一整天之后,我发现只需在管道编辑器中作为环境变量添加以下内容于代理阶段,即可解决这个问题。

'npm_config_cache=npm-cache'

1
我正在使用yarn,也遇到了同样的问题。你知道如何解决yarn的这个问题吗? - Joey Yi Zhao
2
如何使用此配置? - Xin Meng

14
当Jenkins在Docker代理中运行阶段时,通常会将HOME设置为镜像的WORKDIR值(通常为根目录/),但是Jenkins用户在此目录中没有写权限,这就是为什么npm无法创建其缓存目录~/.npm。要解决这个问题,您需要通过设置NPM_CONFIG_CACHE变量来重写HOMEnpm的缓存目录的位置:
pipeline {
    agent {
        docker {
            reuseNode true
            image 'node:16.15'
        }
    }

    environment {
        // Override HOME to WORKSPACE value
        HOME = "${WORKSPACE}"
        // or override npm's cache directory (~/.npm)
        NPM_CONFIG_CACHE = "${WORKSPACE}/.npm"
    }

    stages {
        stage('Build') {
            steps {
                sh 'npm install'
                sh 'npm run build'
            }
        }
    }
}

NPM_CONFIG_CACHE 传递给 Docker 镜像最终解决了这个令人发狂的问题。谢谢。 - Alex D

10

我想给出更多细节,简而言之,接受的答案有效,但我是 Docker 的新手,想要更好地理解,并想分享我所发现的。

对于我们的 Jenkins 设置,它通过以下方式启动容器:

docker run -t -d -u 995:315 -w /folder/forProject -v /folder/forProject:/folder/forProject:rw,z ...

作为结果,此容器正在以用户uid=995 gid=315 groups=315运行。
由于我使用的镜像(circleci/node:latest)没有具有此UID/GID的用户,因此该用户将没有“主目录”,并且只能在挂载的卷上拥有权限。
当调用NPM命令时,它将尝试使用该用户的主目录(用于缓存),由于该用户未在镜像上创建,主目录被设置为/(Linux的默认值?)。因此,为了使NPM正常工作,我们只需通过Jenkins文件将容器的HOME环境变量指向当前文件夹即可。
pipeline {
  agent none
  stages {
    stage('NPM Installs') {
      agent {
        docker {
            image 'circleci/node:latest'
        }
      }
      environment { HOME="." }
      ...
    }
  }
}

因此,用户可以在“/folder/forProject/.npm”目录下创建所需的“.npm”文件夹。
希望这对某人有所帮助,如果您发现我有错误,请告诉我:D

2

您可以覆盖Jenkins运行Docker容器的用户。例如,这里我使用root进行了覆盖(userid:groupid是0:0):

docker { 
    image 'node:8'
    args '-u 0:0'
}

您可以在控制台输出中的docker run参数中找到当前用户。


2

我也遇到了同样的问题。我使用 root 用户来运行 Docker 镜像解决了这个问题:

node {
    stage("Prepare environment") {
        checkout scm
        // Build the Docker image from the Dockerfile located at the root of the project
        docker.build("${JOB_NAME}")
    }

    stage("Install dependencies") {
        // Run the container as `root` user
        // Note: you can run any official Docker image here
        withDockerContainer(args: "-u root", image: "${JOB_NAME}") {
            sh "npm install"
        }
    }
}

1
在此之后,您必须删除由root用户创建的所有文件,否则Jenkins下一次作业将因工作区目录中的权限冲突而失败。 - kivagant
1
在容器中使用root用户是一种不良的安全实践,应避免使用。 - Madox

1
我使用maven来构建项目,实际上在运行frontend-maven-plugin时会调用npm install。但是npm install会出现错误: 路径为/.npm npm ERR! code EACCES npm ERR! errno -13 npm ERR! syscall mkdir 我将其更改为 npm install --cache /tmp/empty-cache 然后它就可以工作了。

1
我们遇到了同样的问题,问题的核心是容器中的用户和运行Jenkins节点的用户具有不同的UID。 在将容器中用户的UID + GID更改为与运行构建节点的用户匹配(并更改用户主目录的所有权)后,npm将表现正常。
如果容器用户的主目录不可写,也可能发生这种情况。
Dockerfile中的代码:
RUN usermod -u <uid of buildnode> <container user> && \
    groupmod -g <gid of buildnode> <container user group> && \
    chown -R <container user>:<container user group> /home/<container user>

作为工作区被挂载到容器中,它已经属于UID。当通过Jenkinsfile运行容器时,容器用户的UID和GID会自动设置为与构建节点匹配。但是,主目录仍将保留其原始所有者。
现在,node_modules将放置在当前目录中。

1
这个配置对我很有效。

pipeline {
    agent {
        docker {
            image 'node:6-alpine'
            args '-p 3000:3000 -p 5000:5000'
            args '-u 0:0'

        }
    }
    environment {
        CI = 'true'
    }
    stages {
        stage('Build') {
            steps {
                sh 'npm install --unsafe-perm'
            }
        }
        stage('Test') {
            steps {
                sh './jenkins/scripts/test.sh'
            }
        }
        stage('Deliver for development') {
            when {
                branch 'development' 
            }
            steps {
                sh './jenkins/scripts/deliver-for-development.sh'
                input message: 'Finished using the web site? (Click "Proceed" to continue)'
                sh './jenkins/scripts/kill.sh'
            }
        }
        stage('Deploy for production') {
            when {
                branch 'production'  
            }
            steps {
                sh './jenkins/scripts/deploy-for-production.sh'
                input message: 'Finished using the web site? (Click "Proceed" to continue)'
                sh './jenkins/scripts/kill.sh'
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接