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

解决痛点

  • 不知如何用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-kata-jenkins-pipeline-as-code

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号管理员的姓和名,参见下图

用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

点击 `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.

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

选择`Pipeline script from SCM`

SCM 下拉框中,选择 Git。在 Repository URL中,填入Jenkinsfile所在的代码库的地址
https://gitee.com/wubin28/devops-katas-jenkins-pipeline-as-code-kata.git。确保Branch Specifier 中填写了 */masterScript 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版本库

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.

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刚才引起流水线故障的提交

git log
git revert 131f54ebb5554aef43fc823d5d8d6fb7aaa8898c
git push

revert并且push,1分钟后,流水线自动构建,故障消失

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分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息。点击相应提交左边的小圆球,能看到具体的错误信息

等1分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交人和提交信息
点击相应提交左边的小圆球,能看到具体的错误信息

可以使用上面提到的命令来查看上次提交的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个问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258