# devops-kata-jenkins-pipeline-as-code **Repository Path**: zhangqw7-seven/devops-kata-jenkins-pipeline-as-code ## Basic Information - **Project Name**: devops-kata-jenkins-pipeline-as-code - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 11 - **Created**: 2020-11-26 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README = DevOps编程操练:用Docker、Jenkins和GitLab操练代码质量预警机制 :toc: == 解决痛点 * 代码上线故障多 * 不知如何用docker搭建Jenkins操练环境 * 不知如何开始为Java代码编写自动化单元测试 * 不知如何将单元测试运行在Jenkins流水线上 * 不知如何将繁琐的手工Jenkins流水线配置,简化为编写一个Jenkinsfile脚本,并进行版本控制 * 当流水线出现故障后,不知如何revert导致故障的代码提交,来解决故障 == 用spring boot编写一个web应用程序并手工测试 === 从 http://start.spring.io[start.spring.io] 下载web空白应用 下载前的选项,参见下面的列表。其中Dependencies添加Web * Group: devops.katas * Artifact: adminprovider * Name: adminprovider * Description: Demo project for Jenkins pipeline as code * Dependencies: Web, Lombok === 编写adminprovider的Web应用,可以按id号一次返回一位管理员 因为把maven中的Java版本设置为11后,在Jenkins里运行会报错,所以需要把pom.xml文件中的 `java.version` 从 `11` 改为 `1.8` 将刚才下载的adminprovider.zip解压,用IntelliJ IDEA打开该Maven项目,开始编写一个Web应用 为方便起见,本操练所创建的类,都写在AdminproviderAppication类中 首先创建 `AdminController` 类 .AdminproviderApplication.java [source,java] ---- @RestController class AdminController { @GetMapping("/admin/{id}") Admin admin(@PathVariable int id) { return new Admin("First" + id, "Last" + id); } } ---- 然后创建 `Admin` 类。 .AdminproviderApplication.java [source,java] ---- @Data @AllArgsConstructor class Admin { private String firstName; private String lastName; } ---- 最后在 `application.properties` 文件中,添加该Web应用启动的端口号 `8765` .application.properties ---- server.port=8765 ---- 此时,在Intellij IDEA中运行 `AdminproviderApplication` 类。然后用浏览器或 https://httpie.org/[HTTPie]工具来访问地址 `localhost:8765/admin/1` 。应该能得到1号管理员的姓和名,参见下图 .用HTTPie工具访问 image::images/httpie-8765-admin-1.png[] == 编写AdminService的自动化单元测试 为了让Jenkins流水线起到质量预警的作用,必须在上面运行自动化测试,来检测每一次代码push是否有缺陷。让我们先从单元测试开始。 目前要测试的单元,是根据 `id` 号生成 `Admin` 对象。这段逻辑写在了 `AdminController` 类中,而这个设计是不好的。因为Controller类本来的用途,是起“传达室”的作用,即将用户的请求,分配给相应的服务来处理。所以良好的设计,应该是把这段逻辑交给 `AdminService` 来处理。而对这段逻辑的单元测试,也就是对 `AdminService` 的单元测试。 第一步,先把上述逻辑交给 `AdminService` 来处理 .AdminproviderApplication.java [source,java] ---- @Configuration class AdminConfiguration { @Bean AdminService adminService() { return new AdminService(); } } class AdminService { public Admin retrieveAdmin(int id) { return new Admin("First" + id, "Last" + id); } } @RestController class AdminController { @Autowired AdminService adminService; @GetMapping("/admin/{id}") Admin admin(@PathVariable int id) { return adminService.retrieveAdmin(id); } } ---- 第二步,为 `AdminService` 编写单元测试 .AdminServiceTest.java [source,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("First4"); BDDAssertions.then(admin.getLastName()).isEqualTo("Last4"); } } ---- 在IntelliJ IDEA中运行单元测试,应该运行通过 在命令行上运行命令 `mvn clean test` ,单元测试也应该运行通过 == 使用docker安装Jenkins 当然也可以不用docker,直接在本机安装Jenkins。但对于操练DevOps技能来说,Docker是一个必修项目。所以本操练使用docker来搭建操练环境 本操练是 https://www.jianshu.com/p/b790436f53b3[从“CI搭建兽”到“流水线即代码”] 的升级版,除了使用docker来运行GitLab和Jenkins之外,还将 `Jenkinsfile` 的写法,从原来的脚本式(以 `node` 开头),升级为声明式(以 `pipeline` 开头) 实测表明,如果将GitLab和Jenkins同时运行在同一台电脑的两个docker容器里,无论电脑的内存是8G还是16G,两者都会运行很慢。所以推荐把Jenkins和GitLab分别运行在两台电脑的docker容器中,且每台电脑的内存至少8G 本操练使用ubuntu和mac这两台电脑,其中 * ubuntu电脑(ip地址:192.168.71.244)的容器中安装了GitLab * mac电脑(ip地址:192.168.71.243)的容器中安装了Jenkins 下面以Ubuntu 20.04及Mac为例进行操练,其他操作系统操练步骤类同 === 安装docker 参见 https://ocs.docker.com/engine/install/[Install Docker Engine] ,在两台电脑上安装Docker === 安装Kitematic Kitematic是一个为了方便使用docker而精心设计的图形化工具。参见 https://github.com/docker/kitematic/releases[Kitematic发布页面] ,在两台电脑上安装Kitematic === 安装Jenkins 安装好docker后,在mac电脑的命令行运行以下命令,就能下载并启动jenkins容器。其中 `~/docker-volumes/jenkins` 文件夹是我的个人目录,需要替换成你的本机某个目录。 `Bins-MacBook-Pro.local` 是我的电脑的主机名,也需要换成你的主机名 ---- docker run --detach --hostname Bins-MacBook-Pro.local --publish 8080:8080 --publish 50000:50000 --name jenkins -v ~/docker-volumes/jenkins:/var/jenkins_home jenkins/jenkins ---- 打开Kitematic,查看jenkins是否已经运行起来。如果已经运行起来,就可以在浏览器中访问 `http://localhost:8080/` ,进入Jenkins安装页面,安装Jenkins。安装第一步所需要的admin管理员密码,能在Kitematic的 `Home` 签中的log内容中找到。安装Jenkins插件时,选择默认的即可。 == 使用docker安装并配置GitLab 由于GitLab运行所需要的80端口已经被ubuntu上的apache2占用了,所以需要运行命令 `sudo service apache2 stop` 先把apache2停掉 在ubuntu电脑的命令行运行以下命令,就能下载并启动GitLab容器。其中以 `~/docker-volumes/gitlab/` 开头的文件夹是我的个人目录,需要替换成你的本机某个目录。 `ben-ZenBook-UX393EA-UX3000EA` 是我的电脑的主机名,也需要换成你的主机名 ---- docker run --detach --hostname ben-ZenBook-UX393EA-UX3000EA --publish 443:443 --publish 80:80 --publish 22:22 --name gitlab -v ~/docker-volumes/gitlab/etc:/etc/gitlab -v ~/docker-volumes/gitlab/var-log:/var/log/gitlab -v ~/docker-volumes/gitlab/var-opt:/var/opt/gitlab gitlab/gitlab-ce:latest ---- 安装完成后,在浏览器中访问 `http://localhost:80/` ,就能进入GitLab页面,设置 root用户密码。然后注册一个用户,并以该用户身份登录GitLab 登录GitLab后,进入用户的 `Settings` 页面,创建 `Access Token` ,并记录下来,以便后面设置webhook时使用。下面是我创建Access Token时使用的参数: * Name: token-for-asus-3 * expires at: 2023-11-30 * Scopes: (全部勾选5个scope) 点击 `Create personal access token` 进行创建,然后把页面顶部 `Your new personal access token` 下面的值记录下来,以备后用。我的access token是 `pHt2XifyorWQeFTHs8e-` 现在可以在GitLab中,创建一个名为 `devops-kata-jenkins-pipeline-as-code` 的空的项目,然后把前面编写的代码push到该项目中,以便后面操练中的Jenkins流水线读取代码来运行自动化测试。下面是我创建该项目时填的参数: * Project Name: devops-kata-jenkins-pipeline-as-code * Visibility Level: Public 点击 `Create Project` 按钮,创建代码库 要将代码push到GitLab,需要把你的代码所在的电脑的ssh key配置到GitLab中,以便push代码 点击 `Add SSH key` 按钮,进入 `SSH Keys` 页面 此时,在命令行窗口运行命令 `ls ~/.ssh/` 查看电脑上的ssh key。如果没有发现 `id_ed25519.pub` 或 `id_rsa.pub` 文件,那么推荐优先用以下命令创建ED25519 ssh key (比下面的RSA ssh key更安全高效),把其中的 `email@company.com` 换成你的email ---- ssh-keygen -t ed25519 -C "email@company.com>" ---- 也可以用以下命令创建RSA ssh key,把其中的 `email@company.com` 换成你的email ---- ssh-keygen -t rsa -b 2048 -C "email@company.com>" ---- 再次在命令行窗口运行命令 `ls ~/.ssh/` 查看电脑上的ssh key,此时就能看到 `id_ed25519.pub` 或 `id_rsa.pub` 文件了 现在可以在 `SSH Keys` 页面,创建ssh key了。下面是我创建时使用的参数 * Key: (先运行 `cat ~/.ssh/id_ed25519.pub` ,然后把该命令的输出内容复制粘贴到此次) * Title: For TW-MacBookPro-3 * Expires at: 2023/11/30 点击 `Add Key` 按钮创建ssh key 下面push代码的命令中的 `192.168.71.244` 是我的ubuntu电脑ip地址,需要换成你的电脑ip [source,bash] ---- git init git add . git commit -m "AdminService with a test" git remote add origin git@192.168.71.244:wuzhenben/devops-kata-jenkins-pipeline-as-code.git git push -u origin master ---- 下面的任务,就是要把GitLab中的单元测试,运行在Jenkins流水线上 == 在Jenkins界面上编写流水线脚本并运行流水线 虽然本操练的一个目标,是要用保存在GitLab中进行版本化管理的Jenkinsfile脚本来定义流水线,但为了调试脚本方便,所以先在Jenkins界面上把脚本调试好,然后再把这些脚本写入Jenkinsfile === 创建文件夹 为方便管理操练内容,首先在Jenkins主页上创建 `jenkins-pipeline-as-code-kata` 文件夹,以后的操作都在该文件夹中 点击 `New Item` .点击 `New Item` image::images/jenkins-new-item.png[] 创建文件夹 .创建文件夹 image::images/jenkins-folder.png[] 不需要配置,直接点 `Save` 文件夹创建完毕 === 确认Maven与git都已经在Jenkins中配置好 因为运行流水线需要Maven和Git这两个工具,所以需要事先在Jenkins里配置好 进入 `Global Tool Configuration` 页面 .进入 `Global Tool Configuration` 页面 image::images/jenkins-tool.png[] 把Maven命名为M3 .把Maven命名为M3 image::images/jenkins-tool-maven.png[] 把git命令在Jenkins容器里的路径设置为 `/usr/bin/git` 。这一点可以通过执行命令 `docker container exec -it jenkins bash` 进入容器内部查看,查看有按 `Ctrl + PQ` 退出 .把git命令的路径设置为 `/usr/bin/git` image::images/jenkins-tool-git.png[] === 创建名为adminprovider的流水线 进入jenkins-pipeline-as-code-kata文件夹,点击 `New Item` ,创建名为 `adminprovider` 的流水线 .创建名为 `adminprovider` 的流水线 image::images/jenkins-pipeline-create.png[] === 修改流水线的脚本 在流水线配置页面的底部, `script` 输入框的右上角 `try sample Pipeline...` ,选择 `GitHub + Maven` 流水线样例脚本,作为修改的基础 .选择 `GitHub + Maven` 流水线样例脚本,作为修改的基础 image::images/jenkins-pipeline-sample.png[] 将第13行的git代码库的地址改为本操练的代码库的地址 `http://username:pHt2XifyorWQeFTHs8e-@192.168.71.244/wuzhenben/devops-kata-jenkins-pipeline-as-code.git` 其中, `192.168.71.244` 是我的ubuntu电脑ip地址,需要替换成你的电脑地址 将第16行的mvn命令,改为 `./mvnw -s /var/jenkins_home/maven/settings.xml clean package` 。 mvnw命令能够在没有安装maven的情况下,运行maven命令。之后,点击 `Save` 按钮保存 `/var/jenkins_home/` 对应mac电脑的 `~/docker-volumes/jenkins/` 目录,这已经在上面在mac电脑上所运行的docker run命令中的 `-v` 参数设置好了。这种volume的设置,能在你的电脑上设置一个文件夹,供docker和你的电脑操作系统共同访问,方便使用 `/maven/settings.xml` 其实是把电脑本地的带有阿里云maven镜像的settings.xml文件,复制过来,加快构建的速度 而 `./mvnw -s` 命令,就能读取后面跟随的settings.xml文件 点击 `Build Now` 手工触发流水线构建。点击左下角 `#1` 左侧的小圆点,能够跳转到控制台输出页面,观察运行结果。 .点击 `Build Now` 手工触发流水线构建 image::images/jenkins-pipeline-build-now.png[] .点击左下角 `#1` 左侧的小圆点,能够跳转到控制台输出页面 image::images/jenkins-pipeline-console-output.png[] 如果一切正常,那么构建应该成功。这表明在界面上编写的脚本没有问题。下面可以把这些脚本写到 `Jenkinsfile` 文件中,以便让Jenkins读取该文件中的流水线配置信息。从而实现用Jenkinsfile脚本文件来定义流水线,减轻配置的工作量。 == 根据脚本创建Jenkinsfile,并配置Jenkins,使其读取Jenkinsfile来运行流水线 因为流水线脚本要从git版本库中读取,需要重新配置,所以现在创建一个名为 `adminprovider-from-scm` 新的流水线 .创建名为adminprovider-from-scm的流水线 image::images/jenkins-pipeline-create-new.png[] === 准备好Jenkinsfile 在流水线配置页面的底部, `script` 输入框的右上角 `try sample Pipeline...` ,选择 `GitHub + Maven` 流水线样例脚本,将其内容复制粘贴到代码根目录下新创建的Jenkinsfile文件中,并把其中的git版本库地址和maven命令如上所示更改过来。为了验证Jenkins确实从Jenkinsfile读取了流水线配置,在 `steps` 第一句增加了 `echo 'hello from scm` 。修改完Jenkinsfile后,就可以点击流水线配置页面底部的 `Save` 按钮,保存配置。 .Jenkinsfile [source,groovy] ---- 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 'http://username:pHt2XifyorWQeFTHs8e-@192.168.71.244/wuzhenben/devops-kata-jenkins-pipeline-as-code.git' // Run Maven on a Unix agent. sh "./mvnw -s /var/jenkins_home/maven/settings.xml 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' } } } } } ---- 上面代码中的 `http://username:pHt2XifyorWQeFTHs8e-@` 包含了前面配置的GitLab的 access token。如果没有这个token,那么在Jenkins里构建时会报 `Authentication failed` 错误 使用以下命令,将代码push到git版本库 [source,bash] ---- 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` image::images/jenkins-pipeline-script-from-SCM.png[] 在 `SCM` 下拉框中,选择 `Git`。在 `Repository URL` 中,填入Jenkinsfile所在的代码库的地址 `http://192.168.71.244/wuzhenben/devops-kata-jenkins-pipeline-as-code.git`。确保 `Branch Specifier` 中填写了 `*/master`, `Script Path` 中填写了 `Jenkinsfile` 。点击 `Save` 保存 .选择 `Git` ,填写代码库地址 image::images/jenkins-pipeline-script-from-SCM-Git.png[] 点击 `Build Now` 手工触发流水线构建,让Jenkins读取代码库中的Jenkinsfile。 .点击 `Build Now` 手工触发流水线构建 image::images/jenkins-pipeline-script-from-SCM-build-now.png[] 点击左下角 `#1` 左侧的小圆点,能够跳转到控制台输出页面,观察运行结果中包含了上面添加的那句 `hello from scm` 。说明Jenkins确实读取了Jenkinsfile .观察运行结果中包含了上面添加的那句 `hello from scm` image::images/jenkins-pipeline-script-from-SCM-hello-from-scm.png[] == 配置web hook触发流水线 现在Jenkins能从代码库中读取Jenkinsfile了。这意味着流水线的配置,都可以用有版本控制的脚本来完成。但如何让流水线的构建自动进行,从而尽早频繁小批地发现代码集成的问题,以便修复呢? 一种方法是使用cron来每隔一段时间来轮询。但这样作无论代码是否更新,轮询总是会触发构建,比较耗费资源。另一种更有优势的方法是在GitLab里配置web hook。这样代码库一旦有代码push上来, GitLab 就能通过 web hook 通知Jenkins进行构建,从而把频繁小批构建做到极致。 ==== 在Jenkins里配置GitLab 首先确认Jenkins安装了GitLab插件和Git插件 在Jenkins的 `Configure System` ,找到GitLab的配置区域,把 `Enable authentication for /project end-point` 的勾选取消掉。如不取消勾选,则会在web hook测试时,报403 forbidden error 下面是我配置的两个参数: * Connection name: gitlab on ausu3 * Gitlab host URL: http://192.168.71.244 在下面的Credentials里,把GitLab的api access token粘贴进去。点击 `Add` 按钮,选择 `Jenkins` 选项,出现Jenkins Credentials Provider: Jenkins对话框。下面是我输入的一些参数: * Kind: GitLab API token * API token: pHt2XifyorWQeFTHs8e- * ID: token-for-asus-3 * Description: token for asus 3 点击 `Add` 创建credential 在Jenkins的System Configuration里,确保GitLab中的Credentials下拉菜单选择了上面配置的名为 `token for asus 3` 的GitLab API token 由于Jenkinsfile的triggers声明暂不支持gitlab的web hook,没法在Jenkinsfile里编写脚本。所以下面用直接在Jenkins界面里写脚本的adminprovider流水线来实验web hook 进入adminprovider流水线的配置页面,在 `Build Triggers` 区域,勾选 `Build when a change is pushed to GitLab. GitLab webhook URL: http://192.168.71.243:8080/project/jenkins-pipeline-as-code-kata/adminprovider` ,并勾选下面两个选项: * Accepted Merge Request Events * Closed Merge Request Events 注意,这个选项已经提示你GitLab webhook URL了 === 在GitLab里配置web hook 进入GitLab里刚刚创建的代码库项目 `devops-kata-jenkins-pipeline-as-code`,点击左下角的 `Settings` ,再点击 `Web hooks`,下面是我配置的一些参数: * URL: http://192.168.71.243:8080/project/jenkins-pipeline-as-code-kata/adminprovider * Secret Token: pHt2XifyorWQeFTHs8e- * 确保勾选 `Push events` 点击 `Add webhook` 按钮。然后在页面底部,点击 `Test` 按钮,并选择 `Push events`。如果一切顺利,会在页面顶部出现 `Hook executed successfully: HTTP 200` 成功信息 == 在流水线上引入一个编译错误,并revert来解决问题 现在操练一下当流水线遇到编译错误时,会报什么错 在测试代码中,加一句 `abc();` ,然后push代码到代码库 .AdminServiceTest.java [source,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分钟后,流水线被轮询程序自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交信息 image::images/jenkins-pipeline-script-from-SCM-compilation-error-committer.png[] .点击相应提交左边的小圆球,能看到具体的错误信息 image::images/jenkins-pipeline-script-from-SCM-compilation-error-logs.png[] 使用下述命令来查看上次提交的hash号,revert刚才引起流水线故障的提交 [source,bash] ---- git log git revert 131f54ebb5554aef43fc823d5d8d6fb7aaa8898c git push ---- revert并且push,流水线自动构建,故障消失 .revert并且push,流水线自动构建,故障消失 image::images/jenkins-pipeline-script-from-SCM-compilation-error-reverted.png[] == 在流水线上引入一个自动化单元测试失败,并revert来解决问题 现在操练一下当流水线遇到测试失败时,会报什么错 在测试代码中,将断言中的 `firstName [4]` 改为 `firstName [40]` ,然后push代码到代码库 .AdminServiceTest.java [source,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]"); } } ---- 流水线被自动触发。把鼠标放到有提交的出错构建处,能看到导致这次构建失败的提交信 息。点击相应提交左边的小圆球,能看到具体的错误信息 可以使用上面提到的命令来查看上次提交的hash号,revert刚才引起流水线故障的提交 == 作业 操练到此结束。现在该轮到你操练了。可以换一个业务场景操练一下。比如可以将根据id号获取管理员的业务场景,换成根据id号获取学生,从头到尾操练一遍。愿你有所收获 == 反馈 为了让下次DevOps编程操练让你更有收获,不妨花2分钟 https://wj.qq.com/s2/7322298/7802/[填写4个问题]