解决痛点
-
代码上线故障多
-
不知如何用docker搭建Jenkins操练环境
-
不知如何开始为Java代码编写自动化单元测试
-
不知如何将单元测试运行在Jenkins流水线上
-
不知如何将繁琐的手工Jenkins流水线配置,简化为编写一个Jenkinsfile脚本,并进行版本控制
-
当流水线出现故障后,不知如何revert导致故障的代码提交,来解决故障
使用docker搭建Jenkins操练环境
当然也可以不用docker,直接在本机安装Jenkins。但对于操练DevOps技能来说,Docker是一个必修项目。所以本操练使用docker来搭建操练环境
本操练是 从“CI搭建兽”到“流水线即代码” 的升级版,除了使用docker来运行Jenkins之外,还将
Jenkinsfile
的写法,从原来的脚本式(以
node
开头),升级为声明式(以
pipeline
开头)
安装docker
参见 Install Docker Engine 安装Docker
下面以Ubuntu 20.04为例进行操练,其他操作系统操练步骤类同
安装Kitematic
Kitematic是一个为了方便使用docker而精心设计的图形化工具。参见 Kitematic发布页面 安装Kitematic
安装Jenkins
在Kitematic里下载jenkins/jenkins的image,启动容器并安装Jenkins
打开Kitematic,在搜索框中输入
jenkins
来搜索所有Jenkins的镜像。选择镜像名字第一行和第二行都是jenkins的那个镜像。点击
CREATE
按钮下载镜像,并启动容器。参见下图
Figure 1. 点击
CREATE
按钮下载镜像,并启动容器
点击左上角
jenkins
容器,然后点击右上角
Settings
页签,将容器改名为
jenkins-kata
,参见下图
Figure 2. 将容器改名为
jenkins-kata
点击右上角
Home
页签,浏览容器的log,等待jenkins重启
在本机创建文件夹
~/OOR/docker-volumes/jenkins-kata
,并将其配置为docker的volume,以便保存Jenkins运行后的输出文件,且能同时被docker和本机访问。参见下图
Figure 3. 设置docker的volume
点击右上角
Home
页签,浏览容器的log,等待jenkins重启
点击右上角
Settings
页签,再点击下面左侧的
Hostname/Ports
页签,记下页面左侧中间第一个带有
localhost
的端口号,如下图所示的
localhost:32769
,然后打开浏览器,在地址栏中访问这个地址和端口号,就能进入Jenkins安装页面,安装Jenkins。安装第一步所需要的admin管理员密码,能在
Home
页签中的log内容中找到。安装Jenkins插件时,选择默认的即可。参见下图
Figure 4. 查看Jenkins运行的端口号
用spring boot编写一个web应用程序并手工测试
本操练的代码和文档参见 devops-katas-jenkins-pipeline-as-code-kata
从 start.spring.io 下载web空白应用
下载前的选项,参见下面的列表。其中Dependencies添加Web
-
Group: devops.katas
-
Artifact: adminprovider
-
Name: adminprovider
-
Description: Demo project for Jenkins pipeline as code
-
Dependencies: Web
编写adminprovider的Web应用,可以按id号一次返回一位管理员
将刚才下载的adminprovider.zip解压,用IntelliJ IDEA打开该Maven项目,开始编写一个Web应用
为方便起见,本操练所创建的类,都写在AdminproviderAppication类中
首先创建
AdminController
类
AdminproviderApplication.java
@RestControllerclass AdminController { @GetMapping("/admin/{id}") Admin admin(@PathVariable int id) { return new Admin("firstName [" + id + "]", "lastName [" + id + "]"); }}
然后创建
Admin
类。其中的两个getter是必须的,否则在运行时会报
HttpMessageNotWritableException
AdminproviderApplication.java
class Admin { private final String firstName; private final String lastName; public Admin(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } // The public getters are mandatory to fix the issue "org.springframework.http.converter. // HttpMessageNotWritableException: No converter found for return value of type: // class devops.katas.adminprovider.Admin" public String getFirstName() { return firstName; } public String getLastName() { return lastName; }}
最后在
application.properties
文件中,添加该Web应用启动的端口号
8765
application.properties
server.port=8765
此时,在Intellij IDEA中运行
AdminproviderApplication
类。然后用浏览器或 HTTPie工具来访问地址
localhost:8765/admin/1
。应该能得到1号管理员的姓和名,参见下图
Figure 5. 用HTTPie工具访问
编写AdminService的自动化单元测试
为了让Jenkins流水线起到质量预警的作用,必须在上面运行自动化测试,来检测每一次代码push是否有缺陷。让我们先从单元测试开始。
目前要测试的单元,是根据
id
号生成
Admin
对象。这段逻辑写在了
AdminController
类中,而这个设计是不好的。因为Controller类本来的用途,是起“传达室”的作用,即将用户的请求,分配给相应的服务来处理。所以良好的设计,应该是把这段逻辑交给
AdminService
来处理。而对这段逻辑的单元测试,也就是对
AdminService
的单元测试。
第一步,先把上述逻辑交给
AdminService
来处理
AdminproviderApplication.java
@Configurationclass AdminConfiguration { @Bean AdminService adminService() { return new AdminService(); }}class AdminService { public Admin retrieveAdmin(int id) { return new Admin("firstName [" + id + "]", "lastName [" + id + "]"); }}@RestControllerclass AdminController { @Autowired AdminService adminService; @GetMapping("/admin/{id}") Admin admin(@PathVariable int id) { return adminService.retrieveAdmin(id); }}
第二步,为
AdminService
编写单元测试
AdminServiceTest.java
class AdminServiceTest { @Test public void should_retrieve_an_admin_with_correct_names() { AdminService adminService = new AdminService(); Admin admin = adminService.retrieveAdmin(4); BDDAssertions.then(admin.getFirstName()).isEqualTo("firstName [4]"); BDDAssertions.then(admin.getLastName()).isEqualTo("lastName [4]"); }}
在IntelliJ IDEA中运行单元测试,应该运行通过
现在可以把上述代码push到码云中,以便后面操练中的Jenkins流水线读取代码来运行自动化测试
可以在码云自己的帐号中,创建一个名为
devops-katas-jenkins-pipeline-as-code-kata
的空的代码库。然后在代码根目录中,使用下述命令push代码
git initgit add .git commit -m "AdminService with a test"git remote add origin https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.gitgit push -u origin master
本文代码的码云地址为
https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git
下面的任务,就是要把上述单元测试,运行在Jenkins流水线上
在Jenkins界面上编写流水线脚本并运行流水线
虽然本操练的最终目标,是要用Jenkinsfile脚本来定义流水线,但为了调试脚本方便,所以先在Jenkins界面上把脚本调试好,然后再把这些脚本写入Jenkinsfile
创建文件夹
为方便管理操练内容,首先在Jenkins主页上创建jenkins-pipeline-as-code-kata文件夹,以后的操作都在该文件夹中
点击
New Item
Figure 6. 点击
New Item
创建文件夹
Figure 7. 创建文件夹
不需要配置,直接点
Save
Figure 8. 不需要配置,直接点
Save
文件夹创建完毕
Figure 9. 文件夹创建完毕
确认Maven与git都已经在Jenkins中配置好
因为运行流水线需要Maven和Git这两个工具,所以需要事先在Jenkins里配置好
进入
Global Tool Configuration
页面
Figure 10. 进入
Global Tool Configuration
页面
把Maven命名为M3
Figure 11. 把Maven命名为M3
把git命令在Jenkins容器里的路径设置为
/usr/bin/git
。这一点可以通过执行命令
docker container exec -it jenkins-katas bash
进入容器内部查看,查看有按
Ctrl + PQ
退出
Figure 12. 把git命令的路径设置为
/usr/bin/git
创建名为adminprovider的流水线
进入jenkins-pipeline-as-code-kata文件夹,点击
New Item
,创建名为
adminprovider
的流水线
Figure 13. 创建名为
adminprovider
的流水线
修改流水线的脚本
在流水线配置页面的底部,
script
输入框的右上角
try sample Pipeline…
,选择
GitHub + Maven
流水线样例脚本,作为修改的基础
Figure 14. 选择
GitHub + Maven
流水线样例脚本,作为修改的基础
将第13行的git代码库的地址改为本操练的代码库的地址
https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git
Figure 15. 将第13行的git代码库的地址改为本操练的代码库的地址
将第16行的mvn命令,改为
./mvnw clean package'。mvnw命令能够在没有安装maven的情况下,运行maven命令。之后,点击 `Save
按钮保存
Figure 16. 将第16行的mvn命令,改为 `./mvnw clean package’
点击
Build Now
手工触发流水线构建。点击左下角
#1
左侧的小圆点,能够跳转到控制台输出页面,观察运行结果。
Figure 17. 点击
Build Now
手工触发流水线构建
Figure 18. 点击左下角
#1
左侧的小圆点,能够跳转到控制台输出页面
如果一切正常,那么构建应该成功。这表明在界面上编写的脚本没有问题。下面可以把这些脚本写到
Jenkinsfile
文件中,以便让Jenkins读取该文件中的流水线配置信息。从而实现用Jenkinsfile脚本文件来定义流水线,减轻配置的工作量。
根据脚本创建Jenkinsfile,并配置Jenkins,使其读取Jenkinsfile来运行流水线
因为流水线脚本要从git版本库中读取,需要重新配置,所以现在创建一个名为adminprovider-from-scm新的流水线
Figure 19. 创建名为adminprovider-from-scm的流水线
准备好Jenkinsfile
在流水线配置页面的底部,
script
输入框的右上角
try sample Pipeline…
,选择
GitHub + Maven
流水线样例脚本,将其内容复制粘贴到代码根目录下新创建的Jenkinsfile文件中,并把其中的git版本库地址和maven命令如上所示更改过来。为了验证Jenkins确实从Jenkinsfile读取了流水线配置,在
steps
第一句增加了
echo 'hello from scm
。修改完Jenkinsfile后,就可以点击流水线配置页面底部的
Save
按钮,保存配置。
Jenkinsfile
pipeline { agent any tools { // Install the Maven version configured as "M3" and add it to the path. maven "M3" } stages { stage('Build') { steps { echo 'hello from scm' // Get some code from a GitHub repository git 'https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git' // Run Maven on a Unix agent. sh "./mvnw clean package" // To run Maven on a Windows agent, use // bat "mvn -Dmaven.test.failure.ignore=true clean package" } post { // If Maven was able to run the tests, even if some of the test // failed, record the test results and archive the jar file. success { junit '**/target/surefire-reports/TEST-*.xml' archiveArtifacts 'target/*.jar' } } } }}
使用以下命令,将代码push到git版本库
git add .git commit -m "add Jenkinsfile"git pull --rebasegit push -u origin master
配置Jenkins使其读取代码库中的Jenkinsfile来配置流水线
进入刚刚创建的流水线
adminprovider-from-scm
配置页面,在页面底部的
Pipeline
配置区域,点击
Definition
下拉框,选择
Pipeline script from SCM
Figure 20. 选择
Pipeline script from SCM
在
SCM
下拉框中,选择
Git
。在
Repository URL
中,填入Jenkinsfile所在的代码库的地址
https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git
。确保
Branch Specifier
中填写了
*/master
,
Script Path
中填写了
Jenkinsfile
。点击
Save
保存
Figure 21. 选择
Git
,填写代码库地址
点击
Build Now
手工触发流水线构建,让Jenkins读取代码库中的Jenkinsfile。
Figure 22. 点击
Build Now
手工触发流水线构建
点击左下角
#1
左侧的小圆点,能够跳转到控制台输出页面,观察运行结果中包含了上面添加的那句
hello from scm
。说明Jenkins确实读取了Jenkinsfile
Figure 23. 观察运行结果中包含了上面添加的那句
hello from scm
触发流水线
现在Jenkins能从代码库中读取Jenkinsfile了。这意味着流水线的配置,都可以用有版本控制的脚本来完成。但如果想让Jenkins定时轮询代码库,以便做到频繁小批地构建代码,从而尽早频繁小批地定位代码质量问题,更容易地修复问题,这该如何用脚本实现呢?(当然,使用web hook会比轮询更有优势——能实现代码库一旦有代码push上来,就能通知Jenkins进行构建,从而把频繁小批构建做到极致。有关web hook的操练,我们以后再做)
在jenkinsfile中配置轮询
为了验证Jenkins对代码库的轮询,确实来自Jenkinsfile,可以先打开流水线配置页面中的build trigger配置,确认没有任何选项被勾选了
Figure 24. 打开流水线配置页面中的build trigger配置,确认没有任何选项被勾选了
在Jenkinsfile中的
agent any
下面,添加五个星号的
cron
,表示Jenkins每隔1分钟就轮询一次代码库,无论是否有新代码,都会执行构建
triggers { cron('* * * * *')}
使用以下命令,将代码push到git版本库
git commit -am "add triggers with 5 stars into Jenkinsfile"git pull --rebasegit push -u origin master
点击
Build Now
手工触发流水线构建,让Jenkins读取代码库中的Jenkinsfile
确认流水线配置页面中的Build Triggers配置区域中,Build periodically已经被勾选,且五个星出现在
Schedule
输入框中。这表明Jenkins确实读取了Jenkinsfile
Figure 25. 确认流水线配置页面中的Build Triggers配置区域中,Build periodically已经被勾选,且五个星出现在
Schedule
>输入框中。
在流水线上引入一个编译错误,并revert来解决问题
现在操练一下当流水线遇到编译错误时,会报什么错
在测试代码中,加一句
abc();
,然后push代码到代码库
AdminServiceTest.java
class AdminServiceTest { @Test public void should_retrieve_an_admin_with_correct_names() { abc(); AdminService adminService = new AdminService(); Admin admin = adminService.retrieveAdmin(4); BDDAssertions.then(admin.getFirstName()).isEqualTo("firstName [4]"); BDDAssertions.then(admin.getLastName()).isEqualTo("lastName [4]"); }}
等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息。点击相应提交左边的小圆球,能看到具体的错误信息
Figure 26. 等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息
Figure 27. 点击相应提交左边的小圆球,能看到具体的错误信息
使用下述命令来查看上次提交的hash号,revert刚才引起流水线故障的提交
git loggit revert 131f54ebb5554aef43fc823d5d8d6fb7aaa8898cgit push
revert并且push,1分钟后,流水线自动构建,故障消失
Figure 28. revert并且push,1分钟后,流水线自动构建,故障消失
在流水线上引入一个自动化单元测试失败,并revert来解决问题
现在操练一下当流水线遇到测试失败时,会报什么错
在测试代码中,将断言中的
firstName [4]
改为
firstName [40]
,然后push代码到代码库
AdminServiceTest.java
class AdminServiceTest { @Test public void should_retrieve_an_admin_with_correct_names() { AdminService adminService = new AdminService(); Admin admin = adminService.retrieveAdmin(4); BDDAssertions.then(admin.getFirstName()).isEqualTo("firstName [40]"); BDDAssertions.then(admin.getLastName()).isEqualTo("lastName [4]"); }}
等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信 息。点击相应提交左边的小圆球,能看到具体的错误信息
Figure 29. 等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交>信息
Figure 30. 点击相应提交左边的小圆球,能看到具体的错误信息
可以使用上面提到的命令来查看上次提交的hash号,revert刚才引起流水线故障的提交
将Jenkinsfile中的cron改为不那么频繁地构建
每分钟构建一次十分耗费资源,所以可以把轮询次数改为工作时间每2小时构建一次
Jenkinsfile
pipeline { agent any triggers { cron('H H(8-15)/2 * * 1-5') }
push代码,1分钟后自动构建,Jenkins会把修改后的轮询配置自动更新到配置页面
作业
操练到此结束。现在该轮到你操练了。可以换一个业务场景操练一下。比如可以将根据id号获取管理员的业务场景,换成根据id号获取学生,从头到尾操练一遍。愿你有所收获
反馈
为了让下次DevOps编程操练让你更有收获,不妨花2分钟 填写4个问题