说到CI(持续集成)也许你脑子里马上会蹦出一个著名的CI工具,就是我们本篇实践记录中的jenkins。 没错,jenkins能够帮助我们更容易的做到持续集成,但是也需要你不断的提交代码到主干,从而实现持续集成的目的。我经常在项目团队协作中提醒同事需要频繁的拉取与提交代码,但大部分人总习惯了每日一更,甚至几天一更,这是违背了我们持续集成的目的,提高了代码冲突的风险。
关于jenkins的安装方式可以多样化,常见于宿主机直接war包安装,这是比较简单的做法,但同时依赖于在宿主机先安装好JAVA环境。还有就是通过docker容器化的方式来安装,也是本篇即将采用的方式。
一、 部署Jenkins服务
我们通过 Rancher 部署服务来完成jenkins的安装。首先下载镜像,当然,从rancher的部署页中启动可以自动为我们下载部署的镜像,但我们这里为了更清晰的使用,采用了手动pull镜像的方式,执行如下命令:
$ docker pull jenkins/jenkins:alpine
同样我们可以通过docker inspect 命令查看镜像的详细信息,如下:
$ docker inspect jenkins/jenkins:alpine
当然如果你有镜像提供者的Dockerfile那就更好了,通常公开的常用镜像都可以在GitHub上找到,就比如我们现在使用的jenkins,它的Dockerfile地址是: https://github.com/jenkinsci/docker/blob/master/Dockerfile-alpine
有了Dockerfile,你可以更为清楚的了解这个镜像的制作过程,方便后面的使用。
接下来,在宿主机创建挂载文件夹
$ mkdir -p /docker_volume/jenkins_home
因为 jenkins user - uid 1000,所以这里我们更改目录用户组及用户(更详细的可见:https://github.com/jenkinsci/docker/blob/master/README.md#usage)
$ chown -R 1000:1000 /docker_volume/jenkins_home
在rancher的部署页中做如下操作:
- 输入名称 jenkins。
- 输入镜像名称jenkins/jenkins:alpine。
- 添加端口映射 30000:8080,30001:50000。
- 添加一个路径映射卷,卷名为jenkins-home,主机路径的话就是填入我们先前创建的/docker_volume/jenkins_home目录路径,选择为现有目录并映射到容器路径/var/jenkins_home目录路径。
- 点击启动按钮。
这几个步骤其实反应到我们docker容器中就如同下面的命令:
$ docker run -d --restart unless-stopped --name jenkins \
-p 30000:8080 -p 30001:50000 \
-v /docker_volume/jenkins_home:/var/jenkins_home \
jenkins/jenkins:alpine
点击启动后,会跳转到如下界面:
直到等到服务状态为Active后说明服务部署成功,你可以点击如下图中标红的连接,即:http://192.168.225.129:30000,就是我们先前对8080映射到了主机30000端口。
二、 初始化Jenkins
当jenkns服务部署成功后,我们需要对jenkins进行简单配置后才能使用。点击上面jenkins的访问地址 http://192.168.225.129:30000 后,就来到了jenkins的web页面,如下:
在页面中输入jenkins的初始密码,值得一提的是,页面中提示给我们的密码位置是jenkins容器内部的目录地址,因为我们在上一节中部署jenkins服务时,在数据卷选项栏中采用了主机目录映射到容器内目录,所以其实应该到我们的主机映射目录去查看,如果你是和我先前部署jenkins服务时输入一样的值话,那么执行如下命令得到密码:
$ cat /docker_volume/jenkins_home/secrets/initialAdminPassword
当然你也可以在rancher中工作负载里操作执行命令行
在这个里面操作命令就是基于容器内部文件路径了,你就可以使用/var/jenkins_home目录,使用和上面相同的方式得到jenkins初始密码。 实际上你可以发现,点击[执行命令行]这个操作跟 docker exec命令进入容器内部实现了几乎相同的功能,这也体现出我们在实际使用中更好的去理解rancher作为一款产品其背后的运作原理(当然,不可能这么简单)。
我们把密码填入页面后点击下一步按钮,进入如下页面:
选择按照系统建议的插件,一直等到插件安装完成,这可能需要几分钟时间,这儿比较关注一下这几款插件:Git、Gradle、Ant,非常遗憾默认的插件里没有我们用到的maven,后面需要自己配置插件,从这里我们也要思考一下以后在做项目中,能够考虑用Gradle来替代maven,这也许是一个流行趋势
完成后会跳转到引导页面,输入我们管理员用户名及密码,然后下一步即可:
到此为止,我们已经完成了jenkins的初始化,实际使用中,还需要进行更多的配置,比如管理用户、凭据配置、全局工具配置以及插件管理等,这些都可以在主面板的左侧菜单栏中的系统管理中找到,当然,你也许已经对jenkins的使用非常熟悉了。
三、 安装maven
由于我们的宿主机中是没有直接安装maven软件的,我们可以在全局工具配置中安装maven, 选择一个合适的版本,勾选自动安装,之后直接保存,需注意的是,现在jenkins并不会立即给你安装maven软件
接下来我们在插件管理中查找maven插件,你可以在浏览器中使用ctrl+f快捷键来快速定位插件,如下图所示,我们选择好maven integration插件,然后点击直接安装
稍作等待,到插件安装完成后即可使用插件
四、 创建一个新任务
现在我们来创建一个新任务,输入任务的名称(通常是项目名),因为我们的项目是一个maven项目,所以我们需要选择构建一个maven项目,利用maven来编译打包我们的源代码,如下图所示:
在源码管理中输入项目git仓库地址,这儿使用的我在GitHub上的基于springBoot编写的一个RESTFul API示例公开项目,你可以不添加任何凭证即可使用。
示例项目GitHub地址:https://github.com/wendell-dev/restful-api-demo,欢迎大家star
接着在Build里填写Root POM与Goals and options,你可以点击输入框右边的问号图标来获得输入帮助,这里分别填入pom.xml与clean package -DskipTests=true,点击保存
点击保存后将会跳转到如下界面中,可以选择控制台输出来查看本次构建信息
我们可以详细的分析一下控制台的输出日志,可以看见这里为我们安装了maven软件,还记得前面我们安装maven时所说吗,当时并没有立即为我们安装maven。回到现在控制台中,可以看见在本次构建中为我们安装了maven软件在/var/jenkins_home/tools目录下,你可能从文件夹路径已经看出,这个跟路径在我们部署jenkins时已经进行了主机路径映射,相当于已经进行了持久化,好处不言而喻! 然后执行了代码从Git仓库克隆到jenkins工作空间中,并且执行了我们先前配置的clean package -DskipTests=true命令:
最后,很遗憾的告诉你,本次构建失败了,因为网络或其它原因造成最终未能成功构建项目,我们将通过配置maven来解决这个问题
五、 配置maven
在宿主机执行如下命令进入jenkins-in-maven目录(先前安装的默认目录) 并通过ls查看目录下清单列表
$ cd /docker_volume/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation/jenkins-in-maven
熟悉maven软件目录结构的应该可以看出来,这其实就是一个maven软件的安装后目录,那么接下来我们肯定就是对maven进行配置,如指定Repository目录和mirror等配置。
编辑配置文件
$ sudo vi conf/settings.xml
在相应位置加入如下配置,并保存退出vi模式
<localRepository>
/var/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation/jenkins-in-maven/repository
</localRepository>
与
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
接着重新构建一下我们上一节中创建的项目,可以看见本次构建成功了,从日志中很明显看出我们的jar包都是从http://maven.aliyun.com仓库中拉取了,说明我们的配置生效,而且先前构建不成功很大原因就是因为没有配置镜像仓库导致的网络问题
六、 运行maven项目
6.1 传统方式运行
如果按照传统做法的话,可能会直接在构建成功后执行 java –jar命令启动程序,如下面这样的写法:
#!/bin/bash
pid=`ps -ef | grep restful-api-demo-0.0.1-SNAPSHOT.jar | grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]
then
kill -9 $pid
fi
cp $WORKSPACE/target/restful-api-demo-0.0.1-SNAPSHOT.jar /home/restful-api-demo-0.0.1-SNAPSHOT.jar
BUILD_ID=dontKillMe nohup java -jar /home/restful-api-demo-0.0.1-SNAPSHOT.jar &
但是不要忘了,在这里我们的jenkins是通过rancher运行在容器里面的,按照上面的方式你是很难访问到你的服务的,而且不能通过容器化工具对服务进行管理。那么,我们就需要maven编译打包完成后,把可交付的jar包制作成镜像上传到镜像仓库,然后通过类似Webhooks的东西让rancher部署该服务,后续对服务的管理工作就直接交由rancher来负责,比如服务升级、Pod伸缩等,我们开发人员只需要把代码提交到Git仓库,后续的所有功能都可以自动化完成,这看起来可能有点惊讶,但是容器化就是给我们带来了这样的好处。
6.2 容器中运行
6.2.1 制作镜像
要让服务在容器中运行,就像我们先前说的那样,首先你得把服务制作成镜像,这跟标准的docker镜像制作流程并没有什么出入,首先编写我们应用的Dockerfile,还是拿我们先前的示例项目来说,我已经参考springBoot官网文档编写好了一份Dockerfile文件,地址如下:
https://github.com/wendell-dev/restful-api-demo/blob/master/Dockerfile
然后需要通过 docker daemon 制作镜像,这里我们将采用在rancher中修改jenkins部署配置,然后在jenkins中安装docker插件来进行镜像的制作。
在rancher中对jenkins服务进行升级,在数据卷中加入如下图所示配置,就是把宿主机的docker.sock文件共享给jenkins容器内部使用,这样jenkins容器内部相当于作为docker客户端在与宿主机的docker服务端进行通信,同时在高级选项里命令中的用户UID设置为0,就是root权限运行,不然使用docker时会提示没有权限
在jenkins中安装docker插件,在插件管理里找到并安装如下插件(Docker plugin):
如果成功安装插件,你会在你的系统管理菜单里看见Docker菜单:
然后在系统管理-系统设置中的最下面你会看到新增一个Docker云:
把前面挂载的docker.sock 文件写入Docker Host URI 中:unix:///var/run/docker.sock,可以点击 右中位置的TestConnection按钮进行测试是否连接成功
连接成功后,可以在系统管理菜单里进入Docker菜单,看到如下界面,点击docker进入后,里面会向你展示你宿主机上的所有镜像以及容器
接下来配置我们创建的restful-api-demo服务,在Post Steps里添加一个Build/Publish Docker Image选项,Directory for Dockerfile填写. (就一个点),Cloud选择先前添加docker云配置的名称,然后输入镜像名wendell/restful-api-demo,这里我们暂时不推送镜像到镜像仓库中,因为目前就一个docker daemon,推送与拉取镜像都会造成网络开销,我们这里相当于镜像构建成功后,直接会出现在我们宿主机的docker环境中
这里我们配置好后直接保存,然后对服务进行构建,来看看构建日志:
从日志中可以看出,我们maven编译打包成功后,直接进行的Docker Build,而且都成功了,其实就相当于在宿主机中的项目根目录里执行了如下构建镜像命令:
$ docker build -t wendell/restful-api-demo .
我们可以在宿主机执行docker image ls命令查看镜像是否正在存在,显然,我们已经成功制作了镜像
6.2.2 运行容器
你可以在宿主机中通过docker run 命令来启动容器
$ docker run -p 8081:8081 -d --name restful-api-demo wendell/restful-api-demo
然后在浏览器中输入http://192.168.225.129:8081/swagger-ui.html来试一试我们的示例项目是否启动成功,不出意外的话,你将会看到如下界面:
通过 docker container ls 来查看我们刚才启动的容器
现在我们来删除容器,使用 docker container rm 3e4d6bf8d213 -f 命令,3e4d6是容器ID,-f代表强制删除,接下来我们将在rancher管理界面中部署服务。
和先前部署jenkins服务一样,我们在部署页中填入服务名称与刚才构建的镜像wendell/restful-api-demo,然后映射8081端口到主机30002端口,在高级选项中的安全主机设置中把拉取镜像设为从不(镜像在宿主机):
直接点击启动按钮,进入如下界面:
一直等到状态为Active后,访问http://192.168.225.129:30002/swagger-ui.html,出现如下界面说明我们服务已经通过rancher部署成功:
七、 使用小结
经过上面的一些工具安装及使用流程的演示,相信你已经能够脑补出一个服务从开发到部署的流程图。作为一个开发人员,也许你最关心的是提交代码之前的工作,但是,多了解一些背后支持服务运转的整个流程,各个组件是怎么支撑我们高效工作的,这对我们来说无疑是很大的一个加分项,而且也能为我们以后在软件开发设计上能够提供更多的思路。
接下来我可能会对一些更加自动化的东西进行实战记录,加深容器化组件的使用,同时更好的为我们的DevOps建设快速落地。
好了,本篇实践记录差不多就这么多了,文中所有的操作都是基于前面几篇创建的环境上下文而实现的,如果你有兴趣可以了解了解:
Docker决战到底(一) 虚拟机中安装ubuntu系统
Docker决战到底(二) Docker环境搭建
Docker决战到底(三) Rancher2.x的安装与使用