以sudo权限运行Jenkins流水线代理

24

我是一名有用的助手,可以为您翻译内容。

我有一个在 Docker 容器中运行的 Jenkins 服务器,并且可以访问 Docker 和主机系统,目前它运行良好。现在我想设置一个管道,在 Docker 容器内测试脚本。

Jenkinsfile:

pipeline {
    agent { docker 'nginx:1.11' }
    stages {
        stage('build') {
            steps {
                sh 'nginx -t'
            }
        }
    }
}

错误信息:

> + docker pull nginx:1.11
> 
> Warning: failed to get default registry endpoint from daemon (Got
> permission denied while trying to connect to the Docker daemon socket
> at unix:///var/run/docker.sock: Get
> http://%2Fvar%2Frun%2Fdocker.sock/v1.29/info: dial unix
> /var/run/docker.sock: connect: permission denied). Using system
> default: https://index.docker.io/v1/
> 
> Got permission denied while trying to connect to the Docker daemon
> socket at unix:///var/run/docker.sock: Post
> http://%2Fvar%2Frun%2Fdocker.sock/v1.29/images/create?fromImage=nginx&tag=1.11:
> dial unix /var/run/docker.sock: connect: permission denied
> 
> script returned exit code 1

我的问题是Jenkins需要使用sudo运行docker命令,但是如何让代理以sudo方式运行该命令?


1
是的,我尝试了像上面那样的流水线,但是我收到了一个错误消息。Jenkins用户未添加到Docker用户中,因此他需要使用sudo。也许我可以将Jenkins用户添加到Docker组中,但我更喜欢使用sudo来运行它......如果在流水线中可能的话。 - CordlessWool
请问具体的错误信息是什么?您的管道命令是什么?请将它们添加到问题中。 - Anthony Kong
我在Jenkinsfile中编写的命令是"pipeline{...}"命令,并在其上方添加了控制台输出。感谢您投入时间。 - CordlessWool
现在我只使用自由风格的作业,所以写"sudo docker pull nginx"没有问题,在自由风格的作业中,如果我尝试在没有sudo权限的情况下运行docker,也会收到错误消息。 - CordlessWool
我现在这样做了,但我读到过一个问题,如果主机和容器的docker组gid不同可能会出现问题。 - CordlessWool
显示剩余2条评论
8个回答

31

我曾经遇到过同样的问题。在分析控制台日志后,我发现问题的原因是Docker Jenkins插件使用特定选项-u 107:112启动了一个新容器:

...
docker run -t -d -u 107:112 ...
...

尝试了很多选项,比如:将jenkins添加到sudo组(因为容器中不存在该用户,所以无效),将USER root添加到Dockerfile中,但都没有奏效。

最终我找到了一个解决方案,即在docker agent中使用args来覆盖-u选项。这是我的Jenkinsfile

pipeline {
    agent {
        docker {
            image 'ubuntu'
            args '-u root:sudo -v $HOME/workspace/myproject:/myproject'
        }
    }
    stages {
        stage("setup_env") {
            steps {
                sh 'apt-get update -y'
                sh 'apt-get install -y git build-essential gcc cmake make'
            }
        }

        stage("install_dependencies") {
            steps {
                sh 'apt-get install -y libxml2-dev'
            }
        }
        stage("compile_dpi") {
            steps {
                sh 'cd /myproject && make clean && make -j4'
            }
        }

        stage("install_dpi") {
            steps {
                sh 'cd /myproject && make install'
            }
        }

        stage("test") {
            steps {
                sh 'do some test here'
            }
        }
    }
    post {
        success {
            echo 'Do something when it is successful'
            bitbucketStatusNotify(buildState: 'SUCCESSFUL')
        }
        failure {
            echo 'Do something when it is failed'
            bitbucketStatusNotify(buildState: 'FAILED')
        }
    }
}

这里可能存在安全问题,但这并不是我的问题。


11
如果Jenkins在服务器上不以root身份运行,则会遇到问题,因为Jenkins将无法清除其工作区。当您执行“scm checkout”时,它是在容器外完成的,并且jenkins使用该“-u”将自己的用户映射到容器中。如果构建项目,则这些文件将在本地系统上作为root存在,Jenkins将无法进行后续构建。 - djsumdog
1
谢谢@luongnv89,非常好地解决了我的问题,除了-u选项应该是“root:root”而不是“root:sudo”。 - Jeff Tian
@djsumdog,感谢您的指出。目前我将使用luongnv89的解决方案来解决我的问题,并希望我不会遇到您上述提到的问题... - Jeff Tian
这段代码在我的 Fedora Docker 中运行良好。在应用以下参数作为 args '-u root:root' 后,不需要 -v(挂载卷),Docker 管道会自动挂载卷。 - pradeep karunathilaka
我只需要支持apt-get,所以我只添加了 args '-u root:root',其他一切都正常工作。 - jjmontes

5
正如其他人建议的那样,问题在于jenkins没有运行docker容器的权限。让我们首先了解一下可以启动jenkins的方法,然后再看看每种方法可以做些什么。
1. 手动运行jenkins
您可以像这里建议的那样下载并运行java。在此方法中,您可以执行以下几个操作以允许您的jenkins用户使用docker:
a. 给jenkins用户root访问权限:
我不建议这种方式,毕竟您正在让您的流水线访问所有内容!所以您可能不希望发生这种情况。
b. 将jenkins用户添加到docker组中
就像这里所解释的那样,您可以将docker作为非root用户进行管理。只需将用户添加到docker组即可。如果您知道谁将使用docker(因为您以某种方式授予了他根访问权限),我建议您这样做。
c. 使docker无需root权限
这是Docker最近添加到其工具库中的新功能。您可以在这里详细了解其含义。说实话,我不是这个功能的粉丝!原因是您无法(至少我找不到方法)使其在容器中的用户工作(因为您需要停止Docker服务才能使其发生),而且在使用无根模式时,我在配置DNS方面遇到了一些困难。但如果您不在容器中,这应该没问题。
2. 在Docker中运行Jenkins
实际上,这种方法更加麻烦!我曾经为如何在Jenkins容器中使用Docker而苦苦挣扎,但最终得到了需要的结果,所以这样做是值得的。
要在Jenkins中运行Docker(它本身也是一个Docker容器),您有三种方法:
1. 使用dind(Docker中的Docker)
这很简单,您运行dind镜像并将Jenkins容器中的Docker连接到dind,无需任何特殊的权限处理,您可以自由使用Docker。
2. 使用dood(Docker在Docker之外)

在运行Jenkins时,在docker run脚本中将docker路径挂载为卷,注意,您需要使用我上面解释的两种方法之一(手动运行Jenkins)才能使用docker,这可能有点棘手,但是有可能。

3. 在不同环境中作为docker运行代理并连接远程代理到Jenkins

最后,可以分别运行代理并连接到Jenkins的远程代理。虽然这并没有完全回答您的问题,但这是一种您可以使用的方法。

在Jenkins中仅运行docker的这些方法之后,您可能会遇到一些问题,例如在代理容器本身中具有权限问题,这很可能是由于代理用户引起的(如果您愿意,可以使用命令访问用户)。

docker exec -it [agent container id] whoami

例如,在此示例中,代理用户是 node
agent { 
  docker { image 'node:14-alpine' } 
}

steps{
  sh 'npm i -g random'
}

如果节点用户没有全局安装npm模块的权限,它会抛出错误(我知道,这很奇怪!)

正如luongnv89所提到的,你可以像这样更改运行docker的用户

agent { 
  docker { image 'node:14-alpine' args '-u root' } 
}

希望这有助于理解整个情况。

1
代理 { Docker { 镜像 'node:14-alpine' 参数 '-u root' } }问题已解决。谢谢。 - Vineeth
对于选项1b - 你需要重新启动Jenkins。你可以访问(jenkins_url)/restart来手动从浏览器重新启动Jenkins。 - undefined

4
我会以不同的方式解决这个问题,在容器内部将Jenkins组ID与您挂载为卷的Docker套接字的ID匹配。我使用一个作为root运行的entrypoint查找套接字的gid,如果它与当前容器内部的gid不匹配,则在容器内部执行groupmod进行更正。然后我降低权限,切换到jenkins用户以启动Jenkins。这个entrypoint每次启动都会运行,但对于启动的Jenkins应用程序来说相当透明。
所有执行此操作的步骤都包含在此Github库中:https://github.com/sudo-bmitch/jenkins-docker/

3
您可以通过以下方式解决这个问题:
1- 在您的Dockerfile中将jenkins添加到sudoers文件中:
RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
2- 在您的Jenkinsfile中添加一个额外的步骤,使jenkins拥有使用docker的正确权限:
pipeline {

    agent none

    stages {

        stage("Fix the permission issue") {

            agent any

            steps {
                sh "sudo chown root:jenkins /run/docker.sock"
            }

        }

        stage('Step 1') {

            agent {
                docker {
                    image 'nezarfadle/tools'
                    reuseNode true
                }
            }

            steps {
                sh "ls /"
            }

        }

    }
}

1
通过允许 Jenkins sudo ALL:ALL,您实际上将其变成代理根用户。我强烈建议不要这样做,除非您的构建管道、用户和 Jenkins Web 服务器都足够安全,以允许此级别的信任。 - Rick Moritz
那个步骤 sh "sudo chown root:jenkins /run/docker.sock" 解决了我的问题。谢谢! - Taz

2
最初的回答是:对我有效的方法是

...


node() {
    String jenkinsUserId = sh(returnStdout: true, script: 'id -u jenkins').trim()
    String dockerGroupId = sh(returnStdout: true, script: 'getent group docker | cut -d: -f3').trim()
    String containerUserMapping = "-u $jenkinsUserId:$dockerGroupId "
    docker.image('image')
        .inside(containerUserMapping + ' -v /var/run/docker.sock:/var/run/docker.sock:ro') {
             sh "..."
         }
}

这种方式使得容器中的用户仍然使用jenkins用户ID和组ID,以避免与共享数据发生权限冲突,但同时也是容器内docker组的成员,需要访问docker套接字(/var/run/docker.sock)

我更喜欢这个解决方案,因为它不需要任何额外的脚本或dockerfiles。


Translated:

使用此方法,容器中的用户仍会使用 jenkins 用户 ID + 组 ID,以避免与共享数据发生权限冲突,但同时也是容器内 docker 组的成员,这是访问 docker 套接字 (/var/run/docker.sock) 所必需的。

我更喜欢这个解决方案,因为它不需要任何额外的脚本或 Dockerfile。


这是一个脚本化的流水线,对吗?这能在声明式流水线下工作吗? - fencekicker

1

我刚遇到了完全相同的问题。您需要将Jenkins用户添加到Docker组中:

DOCKER_SOCKET=/var/run/docker.sock
DOCKER_GROUP=docker
JENKINS_USER=jenkins

if [ -S ${DOCKER_SOCKET} ]; then
DOCKER_GID=$(stat -c '%g' ${DOCKER_SOCKET})
sudo groupadd -for -g ${DOCKER_GID} ${DOCKER_GROUP}
sudo usermod -aG ${DOCKER_GROUP} ${JENKINS_USER}
fi

# Start Jenkins service
sudo service jenkins restart

运行上述命令后,管道成功启动docker。

1
我可能已经找到了一个相当不错的解决方案。
设置
我将Jenkins作为容器运行,并在其上构建Docker主机上的容器。为此,我将/var/run/docker.sock作为卷传递给容器。
再次强调一下免责声明:授予对Docker套接字的访问实际上就像授予对机器的root访问权限一样-要小心!
我假设您已经将Docker安装到Jenkins镜像中。
解决方案
这基于docker二进制文件不在$PATH的第一个目录中的事实。我们基本上放置一个shell脚本,该脚本运行sudo docker而不是仅仅是普通的docker命令(并将参数传递给它)。
将类似于这样的文件添加到您的jenkins存储库中,并将其命名为docker_sudo_overwrite.sh:
#! /bin/sh 
# This basically is a workaround to add sudo to the docker command, because aliases don't seem to work 
# To be honest, this is a horrible workaround that depends on the order in $PATH
# This file needs to be place in /usr/local/bin with execute permissions
sudo /usr/bin/docker $@

然后按照以下方式扩展您的Jenkins Dockerfile:

# Now we need to allow jenkins to run docker commands! (This is not elegant, but at least it's semi-portable...)
USER root

## allowing jenkins user to run docker without specifying a password
RUN echo "jenkins ALL=(ALL) NOPASSWD: /usr/bin/docker" >> /etc/sudoers

# Create our alias file that allows us to use docker as sudo without writing sudo
COPY docker_sudo_overwrite.sh /usr/local/bin/docker
RUN chmod +x /usr/local/bin/docker

# switch back to the jenkins-user
USER jenkins

这将使Jenkins服务用户能够以root身份运行docker二进制文件而无需提供密码。然后,我们将脚本复制到/usr/local/bin/docker,它会“覆盖”实际的二进制文件并使用sudo运行它。如果需要帮助,您可以查看我在Github上的示例。

0

这里也有同样的问题。

[...]
agent { docker 'whatever_I_try_doesnt_work'} # sudo, jenkins user in dockerroot group etc
[...]

所以我的解决方法是将它作为流水线构建阶段中的一个步骤添加,如下所示:
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'sudo docker pull python:3.5.1'
            }
        }
    }
}

我也考虑过这个问题,但是步骤不在容器内运行。所以这不一样。下载或启动容器是可以的,但如果你想在脚本内运行命令,那就更加复杂了。 - CordlessWool

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