Docker 安装 Jenkins 很简单, 但是安装完的 Jenkins 并不直接支持 maven, docker 等 CI 常用工具. 特别是 docker 涉及到 docker中运行docker
, 也就是 docker in docker
的问题. 本文将示例如何解决.
本示例所主机 ttg12
的环境为:
- IP: 192.168.31.12,
- OS: Ubuntu Server 18.04,
- Docker 版本: 19.03.6.
比较着急的同学可以直接跳到最后一节 四. 方案总结
一. Docker 安装 Jenkins
Jenkins现在也分长期支持版(LTS)和普通支持版, 对于需要线上长期稳定支持的, 最好下载 LTS 版. 当前 (2020.7) jenkins 最新 LTS 版本为 2.235.1-lts
.
# 下载镜像
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 说明如下.
-
-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 个小时.
查看容器日志:
sudo docker logs dao_jenkins_1
显示权限错误:
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
.
简单的解决方案, 就是将 /data2/jenkins/jenkins_home
的所有者 (owner) 修改为 host 上运行 docker 用户 faceless
(uid=1000)
# 修改 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
二. 支持 Docker
我们将 Jenkins 运行在容器中, 而 Jenkins CI 也需要运行 docker 的话, 就会遇到 docker in docker
的问题. 具体请参考: ~jpetazzo/Using Docker-in-Docker for your CI or testing environment? Think twice..
而我们知道, Docker 其实分为两部分: 服务端和客户端. 服务端通过 socket 套接字 或者监听端口接受客户端的命令. 具体可参考官方文档 Configure where the Docker daemon listens for connections.
也就是说如果我们在宿主机上运行了 docker 服务端的话, 我们在容器内可以只安装 docker 客户端, 然后通过 socket套接字
或者 ip+端口
的方式来直接使用宿主的docker 服务. 这样在容器内新建容器, 其实是在宿主机上新建容器.
因为 Docker 服务端和客户端默认使用 socket 套接字进行交互, 所以我们这里也使用 socket套接字
的方案, 即将宿主机的 /var/run/docker.sock
通过映射给 Jenkins
容器.
2.1 让容器使用宿主的docker
按照上面的思路, 我们在创建容器的 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安全访问白名单 等配置.
2.2 解决访问宿主 socket 的权限问题
重启容器后, 我们通过 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 个:
- 在宿主机将 socket 文件的所有者改为 user id = 1000 的用户;
- 在给容器内的用户增加 group id = 128 的权限.
下面分别讲解.
2.2.1 修改宿主机 socket 文件权限设置
将 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
用户, 所以我们又需要再执行上面的命令修改权限.
2.2.2 给予容器 docker 组权限
第二个方案是, 我们给容器内的 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 faceless:
docker:x:128:faceless
可以看到 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
三. 安装其他 CI 工具
3.1 安装和配置 Maven
首先安装 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
, 节约下载时间.
3.2 支持 Kubernetes Helm
对 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
3.3 提交修改到新镜像 Image
待 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 \
--group-add=128 \
--name dao_jenkins_1 \
jenkins_with_dockercli:2.235.1-lts
如果需要, 可以将 image push 到私有的 docker registry. 这里示例 push 到阿里云镜像服务:
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
如果是开发或测试环境, 建议使用 Docker Registry 搭建本地简单镜像服务, 提升效率, 节约时间. 关于如何搭建本地镜像服务, 参考我的另一篇博文 私有化部署极简 Docker Registry.
四. 方案总结
为了方便以后进行扩展, 我把上面的 Jenkins + Maven 的镜像 Dockerfile 上传到了 gitee jenkins-docker-plus. 有兴趣的同学可以直接下载使用, 也欢迎提交新的 Jenkins+XX PR, 一起完善.
最后总结一下前面一步一步得到的方案. Docker 安装 Jenkins 并支持 Maven, Docker, Helm:
- 在宿主机上安装 docker-ce, 并配置好国内镜像;
- 在宿主机上安装 K8S kubectl 和 helm3, 并完成相关配置.
- 从gitee jenkins-docker-plus下载
jenkins-maven/Dockerfile
文件到宿主机~/tmp/jenkins-maven/Dockerfile
; - 进入
`~/tmp/jenkins-maven/
执行sudo docker build -t jenkins-maven:2.235.1-lts .
构建镜像; - 查看本地
docker
用户组 id:cat /etc/group | grep docker
. 以下用 <docker_groupd_id> 表示 docker 用户组 id. -
配置Volumn映射和用户组等参数, 创建并启动 Jenkins Docker:
# 注意替换以下变. 本示例中: # <host_jenkins_home> = $HOME/jenkins_home # <docker_groupd_id> = 128 mkdir -p <host_jenkins_home> sudo docker run -d -p 8081:8080 -p 50001:50000 \ -v <host_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 \ --group-add=<docker_groupd_id> \ --name dao_jenkins_1 \ jenkins-maven:2.235.1-lts
- 访问 http://<host_ip>:8081, 进入 jenkins, 进行初始化配置.
- [可选]参照上面"3.1 安装和配置 Maven"章节, 在宿主机上配置 maven repository 和
.seetings.xml
进行 maven 加速.
五. 附录
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。