查看原文
其他

从socket权限问题重新认识docker架构

木讷大叔爱运维 木讷大叔爱运维 2022-07-13


点击上方蓝色字体,关注我们



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 directory14:48:53 [INFO] Retrying request to {}->unix://localhost:8014:48:53 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: No such file or directory14:48:53 [INFO] Retrying request to {}->unix://localhost:8014:48:53 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: No such file or directory14: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 versionClient: 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 socketdockerd -H unix:///var/run/docker.sock#tcp socketdockerd -H tcp://0.0.0.0:2375#fd socketdockerd -H fd://

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

docker -H tcp://0.0.0.0:2375 psexport 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 denied14:58:02 [INFO] Retrying request to {}->unix://localhost:8014:58:02 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied14:58:02 [INFO] Retrying request to {}->unix://localhost:8014:58:02 [INFO] I/O exception (java.io.IOException) caught when processing request to {}->unix://localhost:80: Permission denied14: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 jenkinsroot@test:~# id jenkinsuid=1001(jenkins) gid=1001(jenkins) groups=1001(jenkins),999(docker)#重启dockersystemctl restart docker

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

#登录容器dock exec -it jenkins /bin/bash#查看权限jenkins@3387b9e025bd:/$ cat /etc/group |grep 1000jenkins:x:1000:jenkins@3387b9e025bd:/$ cat /etc/passwd |grep 1000jenkins:x:1000:1000::/var/jenkins_home:/bin/bash#查看容器内的docker.sockjenkins@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






关注我们








您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存