Docker 安裝 Jenkins 很簡單, 可是安裝完的 Jenkins 並不直接支持 maven, docker 等 CI 經常使用工具. 特別是 docker 涉及到 docker中運行docker
, 也就是 docker in docker
的問題. 本文將示例如何解決.html
本示例所用主機 ttg12
的環境爲:git
Jenkins如今也分長期支持版(LTS)和普通支持版, 對於須要線上長期穩定支持的, 最好下載 LTS 版. 當前 (2020.7) jenkins 最新 LTS 版本爲 2.235.1-lts
.github
# 下載鏡像 sudo docker pull jenkins/jenkins:2.235.1-lts # 新建容器並啓動 sudo docker run -d -p 8081:8080 -p 50001:50000 \ -v /data2/jenkins/jenkins_home:/var/jenkins_home \ -v /etc/localtime:/etc/localtime:ro \ --restart=always \ --name dao_jenkins_1 \ jenkins/jenkins:2.235.1-lts
其中 2 個 Volumn Mapping 說明以下.spring
-v /data2/jenkins/jenkins_home:/var/jenkins_home
, docker 中 /var/jenkins_home
是 jenkins 的 $HOME 以及全部配置, 數據存儲的地方, 因此必須持久化到本地.-v /etc/localtime:/etc/localtime:ro
, 保持 docker 中的時區跟 host 保持一致, 不然日誌等時間都使用 UTC+0 時區, 跟中國時間差 8 個小時.查看容器日誌:docker
sudo docker logs dao_jenkins_1
顯示權限錯誤:apache
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
這是由於 jenkins 在 host 上用戶爲當前用戶 faceless
, 而 host 本地目錄 /data2/jenkins/jenkins_home
屬於 root
. json
簡單的解決方案, 就是將 /data2/jenkins/jenkins_home
的全部者 (owner) 修改成 host 上運行 docker 用戶 faceless
(uid=1000)ubuntu
# 修改 faceless 爲 host 上 運行 docker 的用戶 sudo chown -R faceless /data/jenkins/jenkins_home # sudo chown -R faceless /var/run/docker.sock sudo docker start dao_jenkins_1
而後再重啓 jenkins 容器 dao_jenkins_1
安全
sudo docker restart dao_jenkins_1
咱們將 Jenkins 運行在容器中, 而 Jenkins CI 也須要運行 docker 的話, 就會遇到 docker in docker
的問題. 具體請參考: ~jpetazzo/Using Docker-in-Docker for your CI or testing environment? Think twice.. bash
而咱們知道, Docker 其實分爲兩部分: 服務端和客戶端. 服務端經過 socket 套接字 或者監聽端口接受客戶端的命令. 具體可參考官方文檔 Configure where the Docker daemon listens for connections.
也就是說若是咱們在宿主機上運行了 docker 服務端的話, 咱們在容器內能夠只安裝 docker 客戶端, 而後經過 socket套接字
或者 ip+端口
的方式來直接使用宿主的docker 服務. 這樣在容器內新建容器, 實際上是在宿主機上新建容器.
由於 Docker 服務端和客戶端默認使用 socket 套接字進行交互, 因此咱們這裏也使用 socket套接字
的方案, 即將宿主機的 /var/run/docker.sock
經過映射給 Jenkins
容器.
按照上面的思路, 咱們在建立容器的 docker run
命令中增長以下 3 個參數:
-v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker -v /etc/docker:/etc/docker \
有關三者的說明
-v /var/run/docker.sock:/var/run/docker.sock
, 經過映射主機的套接字文件到容器, 讓容器內啓動 docker 的時候並非啓動容器內的容器(子容器), 而是啓動主機上的容器(兄弟容器).-v /usr/bin/docker:/usr/bin/docker
, 讓容器中直接使用宿主機的 docker 客戶端.-v /etc/docker:/etc/docker
, 讓容器中的 docker 客戶端使用宿主機的 docker 配置文件, 包括國內鏡像 (mirrors) 和 非ssl安全訪問白名單 等配置.重啓容器後, 咱們經過 docker exec -it dao_jenkins_1 /bin/bash
命令進入容器, 執行 docker ps
驗證 docker 命令是否可正常使用, 結果發現會遇到以下權限問題:
jenkins@f9fd87225ddb:/$ docker ps 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.40/containers/json: dial unix /var/run/docker.sock: connect: permission denied
咱們先看下 host 上 /var/run/docker.sock
的權限:
faceless@ttg12:~$ ll /var/run/docker.sock srw-rw---- 1 root docker 0 Jul 16 10:17 /var/run/docker.sock=
能夠看到 docker.sock
屬於 root
用戶 和 docker
組. 映射到容器內的權限爲:
jenkins@5affdae1637b:/$ ls -al /var/run/docker.sock srw-rw---- 1 root 128 0 Jul 16 10:17 /var/run/docker.sock
咱們再在容器查看下 jenkins 用戶的 user id 和 groupd id:
jenkins@5affdae1637b:/$ id uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)
咱們能夠看到 jenkins 的用戶id 爲 1000, group id 也爲 1000. 所以咱們的解決方案也有 2 個:
下面分別講解.
將 host 上 docker socket 的擁有者修改成運行 uid=1000 的用戶, 或者直接將權限修改成其餘人可讀寫666
:
# 修改宿主機上 socket 的 owner 爲 id=1000 的用戶 sudo chown 1000 /var/run/docker.sock # 或修改 sock 的權限爲 666 sudo chmod 666 /var/run/docker.sock
這個方案無需重啓容器, 直接在容器內運行 docker ps
能夠看到能輸出正常結果.
這個方案是網上大多數文章給出的方案. 可是該方案有一個比較的缺陷, 那就是若是宿主機或者 docker 重啓, 會從新建立 docker.sock
文件, 其全部者會被重置爲 root
用戶, 因此咱們又須要再執行上面的命令修改權限.
第二個方案是, 咱們給容器內的 jenkins 用戶增長 id=128 的組權限. 而正好 docker run
很友好地提供 groupd-add
參數支持該操做.
官方文檔 Additional groups🔗
--group-add: Add additional groups to run as
By default, the docker container process runs with the supplementary groups looked up for the specified user. If one wants to add more to that list of groups, then one can use this flag:$ docker run --rm --group-add audio --group-add nogroup --group-add 777 busybox id
uid=0(root) gid=0(root) groups=10(wheel),29(audio),99(nogroup),777
也就是說咱們可一個經過 group-add
參數給容器中的用戶經過 group name 或者 group id 添加多個額外的用戶組權限, 可是注意: 這個用戶組是指容器內的用戶組, 其 id 可能跟宿主機上的 id 不一致. 而咱們要讓容器內的用戶擁有 host 的某個 group 權限, 須要經過 id 來賦權.
所以這裏咱們先看 host 上 docker 組的 id.
faceless@ttg12:~$ cat /etc/group | grep docker [sudo] password for weiping: docker:x:128:weiping
能夠看到 docker 用戶組的 id 爲 128. 所以咱們在建立容器的時候加上 --group-add=128
便可讓容器內的 jenkins
用戶擁有 /var/run/docker.sock
文件的讀寫權限:
# 先移除舊容器 sudo docker rm -f dao_jenkins_1 # 從新建立容器 sudo docker run -d -p 8081:8080 -p 50001:50000 \ -v /data2/jenkins/jenkins_home:/var/jenkins_home \ -v /etc/localtime:/etc/localtime:ro \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /etc/docker:/etc/docker \ -v /usr/bin/docker:/usr/bin/docker \ --restart=always \ --group-add=128 \ --name dao_jenkins_1 \ jenkins/jenkins:2.235.1-lts
其餘可能的解決方案 (還未驗證), 自定義 Dockerfile (參考自 Use docker inside docker with jenkins user:
FROM jenkins:2.32.3 USER root RUN apt-get -qq update \ && apt-get -qq -y install \ curl RUN curl -sSL https://get.docker.com/ | sh RUN usermod -a -G staff jenkins USER jenkins
首先安裝 maven:
mkdir -p /opt/ && \ cd /opt/ && \ curl -fsSL https://mirror.bit.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz > /tmp/apache-maven-3.6.3-bin.tar.gz && \ tar xzf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/ && \ rm /tmp/apache-maven-3.6.3-bin.tar.gz && \ ln -s /opt/apache-maven-3.6.3/bin/mvn /bin/mvn && \ ln -s /opt/apache-maven-3.6.3/bin/mvnyjp /bin/mvnyjp && \ export PATH=/opt/apache-maven-3.6.3/bin:$PATH
而後配置和加速 maven:
1) 在 host 目錄 /data2/jenkins/jenkins_home/
(對應 docker 的 /var/jenkins_home/
) 中打開或新建 .m2/settings.xml
文件, 添加阿里雲鏡像:
<mirrors> <mirror> <id>ali-public</id> <mirrorOf>public</mirrorOf> <name>aliyun maven public</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> <mirror> <id>ali-central</id> <mirrorOf>central</mirrorOf> <name>aliyun maven central</name> <url>https://maven.aliyun.com/repository/central</url> </mirror> <mirror> <id>aligoogle</id> <mirrorOf>google</mirrorOf> <name>aliyun google</name> <url>https://maven.aliyun.com/repository/google</url> </mirror> <mirror> <id>alisping</id> <mirrorOf>spring</mirrorOf> <name>aliyun spring</name> <url>https://maven.aliyun.com/repository/spring</url> </mirror> <mirror> <id>alispringplugin</id> <mirrorOf>spring-plugin</mirrorOf> <name>aliyun spring-plugin</name> <url>https://maven.aliyun.com/repository/spring-plugin</url> </mirror> </mirrors>
2) 甚至能夠把本地 repository 裏面已經下載好的三方庫都 copy 到 .m2/repository
, 節約下載時間.
對 Kubernetes Helm3 的支持比較簡單, 直接將宿主機上的 helm 映射到容器中便可. 注意這裏針對 helm3, 由於 helm3 中已經移除 tiller, 只須要客戶端便可.
先將 k8s master 上的 .kube/config 文件複製到 docker 宿主機 (這裏是ttg12
) 的 /data2/jenkins/jenkins_home/.kube/
目錄下.
而後在 docker run
命令中增長以下映射, 並重建容器.
-v /usr/local/bin/helm:/bin/helm
待 docker-ce 安裝完成後, 提交本次更新爲新的 image, 後面從這個 image 啓動容器:
# 提交修改 sudo docker commit -a "thefacelessman@126.com" -m "jenkins v2.235.1-lts with support for maven, docker & k8s" dao_jenkins_1 jenkins_with_dockercli:2.235.1-lts # 中止並移除舊容器 sudo docker rm -f dao_jenkins_1 # 以新image啓動新容器 sudo docker run -d -p 8081:8080 -p 50001:50000 \ -v /data2/jenkins/jenkins_home:/var/jenkins_home \ -v /etc/localtime:/etc/localtime:ro \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /etc/docker:/etc/docker \ -v /usr/bin/docker:/usr/bin/docker \ -v /usr/local/bin/helm:/bin/helm \ --restart=always \ --name dao_jenkins_1 \ jenkins_with_dockercli:2.235.1-lts
若是須要, 能夠將 image push 到私有的 docker registry:
docker login --username=<your-useranme> registry.cn-hangzhou.aliyuncs.com docker tag jenkins_with_dockercli:2.235.1-lts registry.cn-hangzhou.aliyuncs.com/faceless/jenkins_with_dockercli:2.235.1-lts docker push registry.cn-hangzhou.aliyuncs.com/faceless/jenkins_with_dockercli:2.235.1-lts
參考資料: