解决痛点
- 不知如何用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
按钮下载镜像,并启动容器。参见下图
点击 CREATE
按钮下载镜像,并启动容器
点击左上角 jenkins
容器,然后点击右上角 Settings
页签,将容器改名为jenkins-kata
,参见下图
将容器改名为 jenkins-kata
点击右上角 Home
页签,浏览容器的log,等待jenkins重启
在本机创建文件夹~/OOR/docker-volumes/jenkins-kata
,并将其配置为docker的volume,以便保存Jenkins运行后的输出文件,且能同时被docker和本机访问。参见下图
设置docker的volume
点击右上角 Home
页签,浏览容器的log,等待jenkins重启
点击右上角 Settings
页签,再点击下面左侧的 Hostname/Ports
页签,记下页面左侧中间第一个带有 localhost
的端口号,如下图所示的localhost:32769
,然后打开浏览器,在地址栏中访问这个地址和端口号,就能进入Jenkins安装页面,安装Jenkins。安装第一步所需要的admin管理员密码,能在Home
页签中的log内容中找到。安装Jenkins插件时,选择默认的即可。参见下图
查看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.
代码语言:javascript复制@RestController
class AdminController {
@GetMapping("/admin/{id}")
Admin admin(@PathVariable int id) {
return new Admin("firstName [" id "]", "lastName [" id "]");
}
}
然后创建 Admin
类。其中的两个getter是必须的,否则在运行时会报HttpMessageNotWritableException
AdminproviderApplication.java.
代码语言:javascript复制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.
代码语言:javascript复制server.port=8765
此时,在Intellij IDEA中运行 AdminproviderApplication
类。然后用浏览器或 HTTPie工具来访问地址localhost:8765/admin/1
。应该能得到1号管理员的姓和名,参见下图
用HTTPie工具访问
编写AdminService的自动化单元测试
为了让Jenkins流水线起到质量预警的作用,必须在上面运行自动化测试,来检测每一次代码push是否有缺陷。让我们先从单元测试开始。
目前要测试的单元,是根据 id
号生成 Admin
对象。这段逻辑写在了AdminController
类中,而这个设计是不好的。因为Controller类本来的用途,是起“传达室”的作用,即将用户的请求,分配给相应的服务来处理。所以良好的设计,应该是把这段逻辑交给AdminService
来处理。而对这段逻辑的单元测试,也就是对 AdminService
的单元测试。
第一步,先把上述逻辑交给 AdminService
来处理
AdminproviderApplication.java.
代码语言:javascript复制@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.
代码语言:javascript复制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代码
代码语言:javascript复制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
点击 New Item
创建文件夹
创建文件夹
不需要配置,直接点 Save
不需要配置,直接点 Save
文件夹创建完毕
文件夹创建完毕
确认Maven与git都已经在Jenkins中配置好
因为运行流水线需要Maven和Git这两个工具,所以需要事先在Jenkins里配置好
进入 Global Tool Configuration
页面
进入 Global Tool Configuration
页面
把Maven命名为M3
把Maven命名为M3
把git命令在Jenkins容器里的路径设置为 /usr/bin/git
。这一点可以通过执行命令 docker container exec -it jenkins-katas bash
进入容器内部查看,查看有按 Ctrl PQ
退出
把git命令的路径设置为 /usr/bin/git
创建名为adminprovider的流水线
进入jenkins-pipeline-as-code-kata文件夹,点击 New Item
,创建名为adminprovider
的流水线
创建名为 adminprovider
的流水线
修改流水线的脚本
在流水线配置页面的底部, script
输入框的右上角try sample Pipeline...
,选择 GitHub Maven
流水线样例脚本,作为修改的基础
选择 GitHub Maven
流水线样例脚本,作为修改的基础
将第13行的git代码库的地址改为本操练的代码库的地址 https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git
将第13行的git代码库的地址改为本操练的代码库的地址
将第16行的mvn命令,改为./mvnw clean package'。mvnw命令能够在没有安装maven的情况下,运行maven命令。之后,点击 `Save
按钮保存
将第16行的mvn命令,改为 ‘./mvnw clean package’
点击 Build Now
手工触发流水线构建。点击左下角 #1
左侧的小圆点,能够跳转到控制台输出页面,观察运行结果。
点击 Build Now
手工触发流水线构建
点击左下角 #1
左侧的小圆点,能够跳转到控制台输出页面
如果一切正常,那么构建应该成功。这表明在界面上编写的脚本没有问题。下面可以把这些脚本写到
Jenkinsfile
文件中,以便让Jenkins读取该文件中的流水线配置信息。从而实现用Jenkinsfile脚本文件来定义流水线,减轻配置的工作量。
根据脚本创建Jenkinsfile,并配置Jenkins,使其读取Jenkinsfile来运行流水线
因为流水线脚本要从git版本库中读取,需要重新配置,所以现在创建一个名为adminprovider-from-scm新的流水线
创建名为adminprovider-from-scm的流水线
准备好Jenkinsfile
在流水线配置页面的底部, script
输入框的右上角 try sample Pipeline...
,选择 GitHub Maven
流水线样例脚本,将其内容复制粘贴到代码根目录下新创建的Jenkinsfile文件中,并把其中的git版本库地址和maven命令如上所示更改过来。为了验证Jenkins确实从Jenkinsfile读取了流水线配置,在 steps
第一句增加了 echo 'hello from scm
。修改完Jenkinsfile后,就可以点击流水线配置页面底部的 Save
按钮,保存配置。
Jenkinsfile.
代码语言:javascript复制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版本库
代码语言:javascript复制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
选择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
保存
选择 Git
,填写代码库地址
点击 Build Now
手工触发流水线构建,让Jenkins读取代码库中的Jenkinsfile。
点击 Build Now
手工触发流水线构建
点击左下角 #1
左侧的小圆点,能够跳转到控制台输出页面,观察运行结果中包含了上面添加的那句 hello from scm
。说明Jenkins确实读取了Jenkinsfile
观察运行结果中包含了上面添加的那句 hello from scm
触发流水线
现在Jenkins能从代码库中读取Jenkinsfile了。这意味着流水线的配置,都可以用有版本控制的脚本来完成。但如果想让Jenkins定时轮询代码库,以便做到频繁小批地构建代码,从而尽早频繁小批地定位代码质量问题,更容易地修复问题,这该如何用脚本实现呢?(当然,使用web hook会比轮询更有优势——能实现代码库一旦有代码push上来,就能通知Jenkins进行构建,从而把频繁小批构建做到极致。有关web hook的操练,我们以后再做)
在jenkinsfile中配置轮询
为了验证Jenkins对代码库的轮询,确实来自Jenkinsfile,可以先打开流水线配置页面中的build
trigger配置,确认没有任何选项被勾选了
打开流水线配置页面中的build trigger配置,确认没有任何选项被勾选了
在Jenkinsfile中的 agent any
下面,添加五个星号的 cron
,表示Jenkins每隔1分钟就轮询一次代码库,无论是否有新代码,都会执行构建
triggers {
cron('* * * * *')
}
使用以下命令,将代码push到git版本库
代码语言:javascript复制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
确认流水线配置页面中的Build Triggers配置区域中,Build periodically已经被勾选,且五个星出现在 Schedule
输入框中
在流水线上引入一个编译错误,并revert来解决问题
现在操练一下当流水线遇到编译错误时,会报什么错
在测试代码中,加一句 abc();
,然后push代码到代码库
AdminServiceTest.java.
代码语言:javascript复制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分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息。点击相应提交左边的小圆球,能看到具体的错误信息
等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息
点击相应提交左边的小圆球,能看到具体的错误信息
使用下述命令来查看上次提交的hash号,revert刚才引起流水线故障的提交
代码语言:javascript复制git log
git revert 131f54ebb5554aef43fc823d5d8d6fb7aaa8898c
git push
revert并且push,1分钟后,流水线自动构建,故障消失
revert并且push,1分钟后,流水线自动构建,故障消失
在流水线上引入一个自动化单元测试失败,并revert来解决问题
现在操练一下当流水线遇到测试失败时,会报什么错
在测试代码中,将断言中的 firstName [4]
改为 firstName [40]
,然后push代码到代码库
AdminServiceTest.java.
代码语言:javascript复制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分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息。点击相应提交左边的小圆球,能看到具体的错误信息
等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息
点击相应提交左边的小圆球,能看到具体的错误信息
可以使用上面提到的命令来查看上次提交的hash号,revert刚才引起流水线故障的提交
将Jenkinsfile中的cron改为不那么频繁地构建
每分钟构建一次十分耗费资源,所以可以把轮询次数改为工作时间每2小时构建一次
Jenkinsfile.
代码语言:javascript复制pipeline {
agent any
triggers {
cron('H H(8-15)/2 * * 1-5')
}
push代码,1分钟后自动构建,Jenkins会把修改后的轮询配置自动更新到配置页面
作业
操练到此结束。现在该轮到你操练了。可以换一个业务场景操练一下。比如可以将根据id号获取管理员的业务场景,换成根据id号获取学生,从头到尾操练一遍。愿你有所收获
反馈
为了让下次DevOps编程操练让你更有收获,不妨花2分钟填写4个问题