扫码阅读
手机扫码阅读

DevOps编程操练:用Jenkins流水线建立代码质量预警机制

1745 2023-07-20

解决痛点

  • 代码上线故障多

  • 不知如何用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

@RestController
class 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

@Configuration
class AdminConfiguration {
@Bean
AdminService adminService() {
return new AdminService();
}
}

class AdminService {
public Admin retrieveAdmin(int id) {
return new Admin("firstName [" + id + "]", "lastName [" + id + "]");
}
}

@RestController
class 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 init
git add .
git commit -m "AdminService with a test"
git remote add origin https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git
git 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 --rebase
git 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 --rebase
git 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 log
git revert 131f54ebb5554aef43fc823d5d8d6fb7aaa8898c
git 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个问题

想要了解更多,点击 查看原文

用好企业软件系统稳定性与混沌工程相关技术和过程。

35 篇文章
浏览 13.5K
加入社区微信群
与行业大咖零距离交流学习
软件研发质量管理体系建设 白皮书上线