從socket權限問題重新認識docker架構

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 psexport 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:如果你對博文感興趣,請關注我的公衆號“木訥大叔愛運維”,與你分享運維路上的點滴。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章