从socket权限问题重新认识docker架构
点击上方蓝色字体,关注我们
jenkins构建出现socket权限问题
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 processings 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应用,主要由以下组件:
docker daemon,守护进程dockerd;
REST API,client与server进行通信及操作的接口;
docker CLI,命令行界面的客户端;
基于client-server架构,Docker的工作机制如下:
Docker客户端与Docker守护进程进行对话,该守护进程完成了构建,运行和分发Docker容器的繁重工作;
Docker客户端和守护程序可以在同一系统上运行,或者您可以将Docker客户端连接到远程Docker守护程序;
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。
unix:默认情况下,在/var/run/docker.sock上创建unix socket,需要root许可或Docker组成员身份;
tcp:当我们需要远程访问守护进程dockered时,可以使用tcp socket;
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
通过不同的socket,server不仅可以提供给本地client调用,还可以满足远程client的调用。只不过我们日常操作都是基于在/var/run/docker.socket 上创建的unix socket 与本地的server进行交互。
#本地通过http方式访问,还可通过sdk方式访问
curl -s --unix-socket /var/run/docker.sock http:/v1.39/info
解决方案
通过对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
docker-compose up -d
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,但是因为权限问题访问被拒绝。
具体分析如下:
从jenkins的官方镜像得知,jenkins容器内部默认使用jenkins用户且uid、gid均为1000。而为了保证jenkins的备份,我们已经在宿主机新增jenkins用户,并且已经对jenkins_home授权
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的了解,希望通过在docker的应用实践中,不断总结经验。
END