前言
互联网时代,软件开发和发布的重要性不言而喻,目前已经形成一套标准的流程,最重要的组成部分就是持续集成(CI)及持续部署、交付(CD)。本文基于 Docker + Jenkins + Git + Registry 实现一套 CI 自动化发布流程。
- 网路结构图
- 开发人员在 GitLab 上打了一个 Tag
- GitLab 把 Tag 事件推送到 Jenkins
- Jenkins 获取 Tag 源码,编译,打包,构建镜像
- Jenkins Push 镜像到阿里云仓库(或本地镜像仓库)
- Jenkins 执行远程脚本
- 远程服务器 Pull 指定镜像
- 停止老版本容器,启动新版本容器
- 通知测试人员部署结果
阅读本文之前,需要安装好 docker、gitlab、registry 环境,本文的重点是如何配置 jenkins 实现自动化部署,其他的请自行 Google 安装配置好环境。
本文使用的版本为 CentOS 7、Jenkins 2.150、GitLab 11.4.5、Docker 17.3,Jenkins 挂载路径为 /home/jenkins
。
环境划分
角色 | IP |
---|---|
Docker/Jenkins/Registry | 47.101.61.194 |
Docker(部署项目) | 115.47.124.247 |
Jenkins
Jenkins 获取 Tag 源码,编译、打包、构建镜像时,需要用到 Docker 命令,Jenkins 容器本身没有安装 Docker ,如何在 Docker 容器内使用 docker 命令? 为了让容器内可以构建镜像,应该使用 Docker Remote API 的客户端来直接调用宿主的 Docker Engine,也就是所谓的 Docker In Docker (DIND)。
为 Jenkins 添加 Docker 命令行
以官方 jenkins/jenkins:lts-alpine
镜像为例,使用 Dockerfile 添加 docker 命令行可执行文件,并调整权限。
# 基础镜像
FROM jenkins/jenkins:lts-alpine
# 作者
MAINTAINER ryan <me@ryana.cn>
# 安装 Docker CLI
USER root
RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz \
&& tar zxvf docker-latest.tgz \
&& cp docker/docker /usr/local/bin/ \
&& rm -rf docker docker-latest.tgz
# 将 `jenkins` 用户的组 ID 改为宿主 `docker` 组的组ID,从而具有执行 `docker` 命令的权限。
ARG DOCKER_GID=994
USER jenkins:${DOCKER_GID}
在上面这个 Dockerfile 例子中,我们下载了静态编译的 docker 可执行文件,并提取命令行安装到系统目录下。然后调整了 jenkins 用户的组 ID,调整为宿主 docker 组 ID,从而使其具有执行 docker 命令的权限。
组 ID 使用了 DOCKER_GID
参数来定义,以方便进一步定制,ARG DOCKER_GID=994 只是个例子,您服务器上的 DOCKER_GID 可能不是 994,可通过下面两种方式改变(二选一):。
- 命令
cat /etc/group|grep docker
查看DOCKER_GID
, 构建镜像时通过--build-arg
来改变DOCKER_GID
的默认值。 - 运行时通过
--user jenkins:994
来改变运行用户的身份(墙裂推荐)。
# 如果需要构建时调整 docker 组 ID,可以使用 --build-arg 来覆盖参数默认值
docker build -t jenkins --build-arg DOCKER_GID=994 .
# 在启动容器的时候,将宿主的 `/var/run/docker.sock` 文件挂载到容器内的同样位置,从而让容器内可以通过 unix socket 调用宿主的 Docker 引擎
docker run --name jenkins \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-d ryaning/jenkins
下载
该镜像按照上方的 Dockerfile 制作好的,直接下载就行了。
docker pull ryaning/jenkins
运行
# 查看 docker 组的 gid
cat /etc/group|grep docker
# 例
[root@test ~]# cat /etc/group|grep docker
docker:x:994:
[root@test ~]#
# 替换 docker 组的 gid,并运行
docker run --name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-e TZ="Asia/Shanghai" \
-v /etc/localtime:/etc/localtime:ro \
-v /home/jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
--user jenkins:994 \
-d ryaning/jenkins
查看日志
查看 jenkins 是否启动成功
docker logs jenkins
注意:在运行 jenkins 时,挂在文件夹的归属用户 id 必须是 1000 ,否则会抛出无操作权限异常。异常如下:
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?
为什么挂载文件夹的归属用户 Id 必须是 1000?在 Jenkins 的 Dockerfile 中可以看到说明要确保使用相同的 uid。
ARG user=jenkins
ARG group=jenkins
ARG uid=1000
ARG gid=1000
ARG http_port=8080
ARG agent_port=50000
ENV JENKINS_HOME /var/jenkins_home
ENV JENKINS_SLAVE_AGENT_PORT ${agent_port}
# Jenkins is run with user `jenkins`, uid = 1000
# If you bind mount a volume from the host or a data container,
# ensure you use the same uid
RUN addgroup -g ${gid} ${group} \
&& adduser -h "$JENKINS_HOME" -u ${uid} -G ${group} -s /bin/bash -D ${user}
# 查看文件夹的归属者
ls -nd /home/jenkins/
# 修改文件夹的归属者和组
chown -R 1000:1000 /home/jenkins/
初始化
# 首次进入后需要输入密码,在命令行使用如下命令获取初始密码
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
之后会进入 jenkins 安装插件页面,选择安装推荐插件就可以了。等待插件安装完成就可以了。
安装支持插件
为了实现自动化部署,需要额外安装一些插件支持,系统管理->管理插件 里进行安装即可。
- Docker:提供 docker 构建和发布(验证)。
- SSH:提供通过 ssh 在远程主机执行命令,用于部署服务。
- Maven Integration:支持 maven。
- Git Parameter:动态获取Git仓库Branch、Tag。
配置全局工具
配置 JDK、Maven
主页面 -> 系统管理 -> 全局工具配置,设置 JDK、Maven。
- JDK
Jenkins容器本身已经安装了Jdk,拿来直接使用即可。
# 进入容器,命令查看 jdk 路径
docker exec -it jenkins bash
echo $JAVA_HOME
# 例:
/usr/lib/jvm/java-1.8-openjdk
- Maven
Maven 选择自动安装就可以了。
配置 SSH
第一步:主页面 -> 凭据 -> 系统 -> 右击全局凭据 -> 添加凭据,创建一个用于连接 Docker 主机的凭据。
第二步:主页面 -> 系统管理 -> 系统设置 -> SSH remote hosts,添加 SSH 远程主机。
配置 Git SSH 凭证
一、在 Jenkins 容器内生成 ssh 的认证,执行 ssh-keygen
,然后将生成的 id_rsa.pub
(cat ~/.ssh/id_rsa.pub) 添加到 GitHub 的 ssh 认证。然后将 id_rsa
(cat ~/.ssh/id_rsa)添加到下图凭证 Private Key
处。
二、主页面 -> 凭据 -> 系统 -> 右击全局凭据 -> 添加凭据,创建一个类型为 SSH Username with private key
用于获取代码的凭据。
创建项目并发布测试
本文以 Hello World demo 项目为例演示。
# 下载 demo
git clone http://gitlab.ryana.cn/ryan/helloworld.git
# 进入项目
cd helloworld
# 创建标签
git tag 1.0.0
# 推送
git push origin 1.0.0
主页面 -> 新建任务 -> 输入任务名称,构建一个 Maven 项目。
配置 Git 参数化构建。
动态获取Git仓库tag,与用户交互选择Tag发布。
指定项目Git仓库地址,并修改 */master
为 $Tag
,Tag 是上面动态获取的变量名,表示根据用户选择打代码版本。
设置maven构建命令选项,利用pom.xml文件构建项目。
clean package -Dmaven.test.skip=true
在Jenkins本机镜像构建与推送到镜像仓库,并SSH远程连接到Docker主机使用推送的镜像创建容器。
在Jenkins主机执行的Shell命令如下:
- war
REPOSITORY=115.47.124.247:5000/helloworld:${Tag}
# 构建镜像
cat > Dockerfile << EOF
FROM ryaning/tomcat:8-jre8
RUN rm -rf /usr/local/tomcat/webapps/ROOT
COPY target/*.war /usr/local/tomcat/webapps/ROOT.war
CMD ["catalina.sh", "run"]
EOF
docker build -t $REPOSITORY .
# 上传镜像
docker push $REPOSITORY
- jar
REPOSITORY=115.47.124.247:5000/helloworld:${Tag}
# 构建镜像
cat > Dockerfile << EOF
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/*.jar helloworld.jar
EXPOSE 8080
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/helloworld.jar"]
EOF
docker build -t $REPOSITORY .
# 上传镜像
docker push $REPOSITORY
SSH远程Docker主机执行的Shell命令如下:
REPOSITORY=115.47.124.247:5000/helloworld:${Tag}
# 部署
docker rm -f helloworld |true
docker image rm $REPOSITORY |true
docker container run -d --name helloworld -p 8080:8080 $REPOSITORY
OK,到这里项目自动化部署已配置完成,开始构建。
选择tag,开始构建。
点击左下角构建历史里,右击第一个查看控制台输出。
访问。
Connecting to 115.47.124.247:54022...
Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.
Last login: Sat Mar 2 15:40:21 2019 from 180.155.249.31
[root@test ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d5c8143ce13 47.101.61.194:5000/helloworld:3.0.0 "catalina.sh run" 5 minutes ago Up 5 minutes 0.0.0.0:8080->8080/tcp helloworld
[root@test ~]# curl -l 127.0.0.1:8080
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
[root@test ~]#