jenkins构建出现的权限问题

docker化运行的jenkins实现对spring boot的docker部署,使用docker-maven-plugin插件。
默认情况下,此插件在docker内部的通过localhost:2375访问docker守护进程。

#jenkins构建过程中 mvn clean package docker:build 报错
14:48:53 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: No such file or directory
14:48:53 [INFO] Retrying request to {}->unix://localhost:80
14:48:53 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: No such file or directory
14:48:53 [INFO] Retrying request to {}->unix://localhost:80
14:48:53 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: No such file or directory
14:48:53 [INFO] Retrying request to {}->unix://localhost:80

原因:
docker-maven-plugin在jenkis 容器内中可以理解为一个docker client,但是容器内无法通过localhost:2375访问docker守护进程,因此会报此错误。

思考:
我们是否需要在jenkins容器内部安装docker,以便docker client能够访问docker守护进程。

对于这个疑问,我们先从docker架构入手找下答案。

Docker Engine

Docker Engine是一个client-server架构,主要由以下组件:

  1. docker daemon,守护进程dockerd;
  2. REST API,client与server进行通信及操作的接口;
  3. docker CLI,命令行界面的客户端;
    在这里插入图片描述基于client-server架构,Docker的工作机制如下:
    1.Docker客户端与Docker守护进程进行对话,该守护进程完成了构建,运行和分发Docker容器的繁重工作。
    2.Docker客户端和守护程序可以在同一系统上运行,或者您可以将Docker客户端连接到远程Docker守护程序。
    3.Docker客户端和守护程序在UNIX套接字或网络接口上使用REST API进行通信。

因此我们在服务器上安装的docker其实是包含client-server的,命令如下:

root@test:~# docker version
Client:
 Version:           18.09.1
 API version:       1.39
 Go version:        go1.10.6
 Git commit:        4c52b90
 Built:             Wed Jan  9 19:35:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false
Server: Docker Engine - Community
 Engine:
  Version:          18.09.1
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       4c52b90
  Built:            Wed Jan  9 19:02:44 2019
  OS/Arch:          linux/amd64
  Experimental:     false

既然client 和 server工作在同一系统上,那他们是如何通信的呢?

Daemon socket

Docker守护程序可以通过三种不同类型的Socket供Docker Engine API请求:unix,tcp和fd。

  1. unix:默认情况下,在/var/run/docker.sock上创建unix socket,需要root许可或Docker组成员身份;
  2. tcp:当我们需要远程访问守护进程dockered时,可以使用tcp socket;
  3. fd:在基于Systemd的系统上,您可以通过Systemd套接字激活与守护程序通信;
#unix socket
dockerd -H unix:///var/run/docker.sock
#tcp socket
dockerd -H tcp://0.0.0.0:2375
#fd socket
dockerd -H fd://

Docker客户端将使用DOCKER_HOST环境变量来为客户端设置-H标志:

docker -H tcp://0.0.0.0:2375 ps
export DOCKER_HOST="tcp://0.0.0.0:2375"
docker ps

通过不同的socker,server不仅可以提供给本地client调用,还可以满足远程client的调用。只不过我们日常操作都是基于在/var/run/docker.socket 上创建的unix.socket 与本地的server进行交互。

通过对docker engine 和 daemon socket的了解,jenkins容器内的docker client是否可以远程访问宿主机的docker守护进程, 这样可以在不增加镜像的大小的情况下解决问题。

联想到本地的client、server交互,我们通过映射宿主机的unix socket到容器内,使其像在本地一样和docker守护进程进行交互。

vim docker-compose.yml
#将本地socket映射到容器内
version: '3.7'
services:
  jenkins:
    image: jenkins/jenkins:lts
    container_name: jenkins
    restart: always
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - "/media/yanggd/work/jenkins:/var/jenkins_home"
      - "/App/maven:/usr/local/maven"
      # 将本地socket映射到容器内
      - "/var/run/docker.sock:/var/run/docker.sock"

docker-compose重新启动后,jenkins再次构建报错:

14:58:02 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied
14:58:02 [INFO] Retrying request to {}->unix://localhost:80
14:58:02 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied
14:58:02 [INFO] Retrying request to {}->unix://localhost:80
14:58:02 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied
14:58:02 [INFO] Retrying request to {}->unix://localhost:80

看来jenkins容器内的docker client能访问宿主的unix socket,但是因为权限问题访问被拒绝。
权限分析:

  1. 从jenkins的官方镜像得知,jenkins容器内部默认使用jenkins用户且uid、gid均为1000。而为了保证jenkins的备份,我们已经在宿主机新增jenkins用户,并且已经对jenkins_home授权
  2. uninx socket需要root许可或Docker组成员身份,我们在宿主机将jenkins用户添加到docker组中,以实现普通用户对/var/run/docker.sock 的访问。
root@test:~# usermod -G docker jenkins
root@test:~# id jenkins
uid=1001(jenkins) gid=1001(jenkins) groups=1001(jenkins),999(docker)
#重启docker
systemctl restart docker

经过修改后,jenkins构建仍然报错,这是为什么呢?难道这种方案不行吗?

#登录容器
dock exec -it jenkins /bin/bash
#查看权限
jenkins@3387b9e025bd:/$ cat /etc/group |grep 1000
jenkins:x:1000:
jenkins@3387b9e025bd:/$ cat /etc/passwd |grep 1000
jenkins:x:1000:1000::/var/jenkins_home:/bin/bash
#查看容器内的docker.sock
jenkins@3387b9e025bd:~/workspace/helloworld$ ls -l /var/run/docker.sock 
srw-rw---- 1 root 999 0 Mar 25 07:42 /var/run/docker.sock

通过登录容器查看权限发现,我们虽然在宿主机上将jenkins用户加入到docker组中,但是在容器内部docker.sock 的属组为999,而jenkins的uid及gid都为1000,因此由于gid的不同,在宿主机上授权并不代表容器内也授权成功。我们需要保证宿主机和jenkins容器内部的uid、gid保持一致。

由于宿主机在本地更改uid、gid会影响使用,在此我偷懒使用chmod临时解决。

chmod 666 /var/run/docker.sock

此问题排查过程中,参考了大量的文档都不得解,静下心来通过现象看本质,不仅解决了问题还对docker加深了理解。

docker client 与 守护进程的交互可以通过sdk 或 docker api,如下:

#list containers
#http
curl -s --unix-socket /var/run/docker.sock http:/v1.39/info

使用sdk 或 docker api 与守护进程交互,可以我们在docker应用中提供更多的解决方案。

PS:如果你对博文感兴趣,请关注我的公众号“木讷大叔爱运维”,与你分享运维路上的点滴。

问题引入组件备注jenkinsdocker运行docker-maven-pluginmaven插件jenkins 实现java项目的docker运行,15:01:22 [INFO] Copying src/main/docker/Dockerfile -> /var/jenkins_home/workspace/helloworld/target/do...
通过TCP公开Docker套接字 有时,运行受TLS支持的Docker远程API查询会很复杂。 为了在无需更改Docker守护程序的设置的情况下实现此目的,请使用此映像通过TCP公开Docker套接字。 另外,当前的Docker版本中似乎存在一些错误。 Docker套接字的映像的内部端口是12375 。 例如,您可以通过-p 1234:12375重新映射端口。 这将在Docker主机上的端口1234上暴露Docker套接字。 运行docker run -d -v /var/run/docker.sock:/var/run/docker.sock -p 1234:12375 --name expose -t tobilg/expose-socketDocker / Boot2Docker主机上docker run -d -v /var/run/docker.sock:/va
vim /etc/sysconfig/docker 加入 other_args="-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock 重启服务: service docker restart
 I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Broken pipe 导致这个错误的原因是 project.artifactId 可能包含了大写。 改正后:...
[INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied
1. 打开docker的远程api访问(参考:https://blog.csdn.net/csde12/article/details/70240721)     sudo vi /etc/systemd/system/docker.service      ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/...
jenkins使用docker-maven-plugin进行编译时发现没有权限,具体错误内容如下: INFO: I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied Jun 19, 2017 4:48:41 PM org
在CI上调用此命令遇到问题: test: [exec] Couldn’t connect to Docker daemon at http+unix://var/run/docker.sock - is it running? [exec] [exec] If it’s at a no
场景:使用maven的dockerfile插件dockerfile-maven-plugin,在jenkins那台机器上,手动mvn install 不会出错,但是在jenkins图形化界面就报错了。 一开始考虑是因为maven版本,后来考虑是因为本地没有装docker[jenkins用的docker镜像],因为我是远程打包的。但是在机器上是配置了DOKCER_HOST 为远程 可连接上的机器。 真实原因: jenkins里面环境变量缺失,没有用上系统的环境变量 解决办法: 原来是maven构建,现在改成自
当在 Docker 容器中运行 Nginx 时,可能会遇到权限问题。这是因为 Docker 默认以非特权用户运行容器,而 Nginx 需要特权用户才能正常运行。 为了解决这个问题,可以使用以下方法: 1. 在 Dockerfile 中添加以下命令,以切换到特权用户: USER root 2. 在 Docker Compose 文件中,可以使用以下命令,将容器用户设置为特权用户: user: root 3. 在运行 Docker 容器时,可以使用以下命令,以特权模式运行容器: docker run --privileged -d nginx 使用以上方法之一,即可解决 Docker 容器没有权限运行 Nginx 的问题