本文来自作者
邹毅
在
GitChat
上分享 「微服务化小团队:让 GitLab、Jenkins 与 Sonar 碰撞出火花」,
「
阅读原文
」
查看交流实录。
「
文末高能
」
编辑 | 哈比
大风起兮云飞扬,安得猛士兮走四方,BUG,任何时候都要剿,不剿不行,你们想想,你带着老婆出了城,吃着火锅还唱着歌,突然就被叫去改 BUG 了!所以,没有 BUG 的日子,才是好日子!
在阅读本文之前先提出几个问题:
-
DevOps 怎么给开发团队减负?
-
单一项目库似乎比多项目库拆分效果更佳?
-
小团队的开发如何让代码质量更靠谱?
-
TDD 开发应该用什么姿势落地?
本文主要面向开发人员,文章是以 PHP 开发测试工作实践为例展开,对其他开发语言也有借鉴作用。
如果你有什么问题,都可以在文章底部的评论框中留言,参与讨论。
一、背了多少债
三个月前我从飞凡离开,加入了影合众,这是一家由传统的影院线下票务软件朝 B2B2C 模式电商平台转型的企业,我们团队的业务是为影院开发线上票务平台。
团队的技术状况与很多国内中小企业的业务研发团队类似,有一套历久弥新的庞大代码库,团队成员说不清里面有多少功能但都愉快的一起在里面写代码改 bug 。
一个 Git 项目下包含了所有不同功能层次的代码,APP API 、WAP API 、后台管理系统、通用 API 等。好处不言而喻,开发管理简单统一,不同模块间较紧密的耦合,降低了整体开发难度,项目部署可以一次性全量更新。
而坏处同样显而易见。随着代码库越来越庞大,出现了不合理的强耦合代码;公共库越来越庞大,且无法做版本管理;项目上线只能做全量上线,遇到故障无法回滚,只能硬着头皮修复等。
为此我曾试图说服技术主管把项目库做拆分,不过他不以为意,认为团队太小,代码库在一起比较利于开发时对全局修改,拆分后管理大量代码库的弊端也很明显,框架选型、开发规范、测试管理、部署配置全部要重新考虑。
Mono-repo(单个代码库)是 Google 内部使用的一种代码管理方式,只要你是开发者,你就能访问和修改公司的所有代码。
Multi-repo(多个独立代码库)则是微服务实践中自然的选择,每一个小项目的维护者对代码库有管理员权限,可以独立的做测试、持续集成、部署上线。
而团队里的单个代码库并不是 Mono-repo ,而是大杂烩,有时为了代码复用,开发者会从 A 项目的目录里把代码挪到 public 目录,B 项目再去 public 目录引用代码。这样的程序极有可能是说崩就崩了。
你一张过去的 CD 看看那时我们的程序 有时会突然忘了 bug 还在那里
再写不出那样的程序 听到都会红着脸躲避 虽然会经常忘了 bug 还在继续
我曾经多年开发工作流引擎( Workflow Engine ),我们在工作中特别讲求两个字:
闭环
。通俗易懂的说法就是你做什么事都得有始有终,不管过程怎样,不管成功失败,我们都考虑的面面俱到。
然而实际开发中开发完的代码要部署到其他环境上,运行时也不像开发时那样只会走到成功请求的分支。有没有什么工具能评估我们的代码是否闭环呢?
由于团队人员较少,所有规范全靠大家自觉执行,如果谁做的不好就会被喷。团队主要是做 PHP 开发,按照 Gitflow 做版本控制,本地配置 Git hook ,当提交时触发 PSR-2 规范及语法检查,master 分支受保护,向主干合并需要代码库负责人之一做 Code Review 。
不过还不够好,比如遇到一个特性改动量很大时,代码合并的 Code Review 已经很难阅读了,为了项赶目工期,人为的把控自然会有松动,久而久之这样的代码又放之任之了。
因此我们需要一套公开透明的代码质量系统,能随时查看有问题的代码,在团队闲暇之时方便做重构计划。
二、制订分期还款计划
通过实际执行评估代码是否闭环:单元测试工具 Unit Test Suite + 覆盖率报告 Coverage Report。不同语言可以使用不同的测试框架,对于 PHP,我们使用 phpunit 。
通过静态分析评估代码质量情况:代码静态分析工具 SonarQube Scanner + 代码质量管理 SonarQube Web。Sonar 有开源免费版本也有收费企业版本,对于小团队用免费的就足够了。
我制定了两个任务:
为任务设定了详细计划:
-
step 1:所有新项目独立管理开发、测试及部署。
-
step 2:所有测试用例优先保障代码覆盖率,再增加测试结果验证。
-
step 3:静态代码分析对代码质量的标准应逐步提高
-
step 4:依托代码质量系统对项目代码每季度做一次整体优化,并调整代码规范。
-
step 5:试行将 Sonar 中的项目代码质量评分纳入考评范围。
三、提升还债能力
计划很好,我们可以通过 phpunit 做测试,通过 Sonar 做统计报告,但缺乏一个重要的持续集成能力,总不能人工维护这些结果吧。
其实很多时候是环境限制了我们的能力发挥,而没有环境的时候就需要 DevOps 去创造环境。搭建一套持续集成系统,能够在我们代码有提交时自动帮我们运行测试任务,并做静态代码分析后把报告上传到 Sonar,我选择 Jenkins 。
1. Jenkins 安装
虽说现在各种开源软件都能支持 Docker 安装了,但为了方便调整,我们还是在虚拟机里操作。
Jenkins 在 Centos 里安装的教程:https://wiki.jenkins.io/display/JENKINS/Installing+Jenkins+on+Red+Hat+distributions。
sudo yum install java
sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum install jenkins
sudo service jenkins start/stop/restart
sudo chkconfig jenkins on
#java 版本
java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
Jenkins 没有数据库依赖,升级也十分方便只用更新一个 war 文件。
配置 Jenkins 服务脚本,我们可以编辑设置 Java 参数:
vim /etc/init.d/jenkins
找到
JAVA_CMD
修改为:
JAVA_CMD="$JENKINS_JAVA_CMD $JENKINS_JAVA_OPTIONS -Xms1024m -Xmx1024m -DJENKINS_HOME=$JENKINS_HOME -jar $JENKINS_WAR"
这样可以避免 Jenkins 把内存都吃了。启动 Jenkins 后会进入安装设置界面,全程按向导提示进行安装
2. 安装 Jenkins 插件
-
Checkstyle Plug-in 风格检查插件
-
Clover PHP plugin PHP 覆盖率插件
-
DRY Plug-in
-
Git plugin 用于从代码库获取最新代码
-
Gitlab Authentication plugin
-
Gitlab Hook Plugin 用于与 Gitlab 的 Webhook 对接
-
Gitlab Merge Request Builder 用于对 Gitlab 的 MR 做持续集成
-
GitLab Plugin 伪装为 Gitlab 的 CI 服务器
-
JDepend Plugin
-
Mailer Plugin 发邮件
-
Plot plugin 报表插件
-
PMD Plug-in
-
SonarQube Scanner for Jenkins Sonar 扫描器客户端
-
SSH Slaves plugin
-
Static Analysis Collector Plug-in
-
xUnit plugin 自动化测试报告处理插件,支持 phpunit
Jenkins 的插件非常丰富,安装了一堆跟 Gitlab 、Sonar 集成和用于测试报告收集分析的插件
3. Sonar 安装
Sonar 安装教程:https://docs.sonarqube.org/display/SONAR/Installing+the+Server,这里我们采用非官方打包的 Sonar yum 源。
sudo wget -O /etc/yum.repos.d/sonar.repo http://downloads.sourceforge.net/project/sonar-pkg/rpm/sonar.repo
sudo yum install sonar
需要注意的是 Sonar 对 Linux 内核参数有要求,用 root 执行:
sysctl -w vm.max_map_count=262144
sysctl -w fs.file-max=65536
ulimit -n 65536
ulimit -u 2048
安装 MySQL 后创建数据库、创建 Sonar 用户:
CREATE DATABASE sonar;
CREATE USER 'sonar'@'%' IDENTIFIED BY 'Sonarpassword1234567890!@#$';
GRANT ALL PRIVILEGES ON sonar.* TO 'sonar'@'%' WITH GRANT OPTION;
编辑 Sonar 配置文件设置数据库连接信息
/opt/sonar/conf/sonar.properties
后启动 Sonar。
service sonar start
启动失败可以到
/opt/sonar/log
目录下查看日志。
这两套系统的搭建对未来团队的代码管理会起到越来越多的正向影响,并为持续集成打下了较好的基础。
四、先还本金再还利息
这次我负责开发一个全新的独立的服务端项目,对外提供 API 接口。
由于前不久参加了一个 Code Retreat 活动,我对 TDD (测试驱动开发 Test Driven Development )产生了浓厚的兴趣,以前只是不断听到却未曾实践,那借此机会,我们把持续集成系统、代码质量管理系统搭建起来,并利用这些系统实践 TDD 。
在新项目里实践技术债务偿还自然比直接动老代码是好的多。待此项目成功,为大家建立一个良好的标杆项目,就能更好的在公司推行此方案。
1. Gitlab - Jenkins - Sonar 工作流
Sonar 中的项目数据是通过一个客户端跑完分析后上报到服务端来显示的,我们可以通过 Jenkins 插件来执行这个任务。
Gitlab 与 Jenkins 集成有两种方式:
-
每次 Gitlab 项目 push 时触发 webhook 通知 Jenkins 。
-
Jenkins 定时去扫描 Gitlab 项目的 Merge Request 列表,发现有待构建的变更则执行。
完整的工作流为 Gitlab -> Jenkins ( shell -> phpunit -> sonar-scanner ) -> Sonar
2. 新建任务
在 Jenkins 中
系统管理
-
管理节点
中
新建节点
来单独跑任务,这里我配置了一个通过 ssh 密钥无密码 SSH 连接到本机的一个用户下在指定目录
/data0/jenkins/agent1
下执行任务,这个节点最大可以同时支持 3 个任务并发执行。
在这个节点机器上我们还安装了 phpunit、composer 等程序以便执行我们的测试任务。回到 Jenkins 首页,
新建
-
构建一个自由风格的软件项目,
输入一个任务名称
-
确定
在
构建触发器
中勾选
Build when a change is pushed to GitLab. GitLab CI Service URL: http://172.16.16.110:8080/project/sonar_pay_center
这里的 URL 就是给 Gitlab 的 webhook 地址,勾选
Enabled Gitlab Triggers
-
Push Events
。
这样我们只处理 Gitlab 发过来的 push 请求,另外还需要点击
高级
找到
Secret Token
,点击右侧
generate
生成一个 Token 给 Gitlab 。
为此在 Gitlab 中找到要设置的项目,这个项目需要你的 Gitlab 账户有 master 及以上权限,在项目配置中找到
webhooks
填入 Jenkins 中生成的 URL 和 Secret Token ,点击
Add Webhook
就在 Gitlab 里设置好了。
回到 Jenkins ,添加
构建
步骤,我们采用
Execute Shell
命令,主要是项目构建、测试库及数据重新生成、运行 PHPunit 并生成测试报告和覆盖率报告。
# 项目依赖安装及配置部署
composer install
rm -rf .env
mv .env.autotest .env
# 数据库重建、数据重建
./artisan migrate:refresh && ./artisan db:seed
# 执行 phpunit 并生成覆盖率报告
phpunit --log-junit build/report.xml --coverage-clover build/clover.xml
添加一个
构建
步骤,选择
Process xUnit test result report
执行 xUnit 测试报告分析,这样我们就可以直接在 Jenkins 里看到每次测试结果生成的趋势图。
登录 Sonar ,找到
Administration 配置
-
Marketplace 系统市场
搜索并安装
Chinese Pack
,重启 Sonar 即可看到中文界面。
配置
-
项目
-
创建项目
我的账号
-
安全
-
令牌
-
填写令牌名称
-
生成
添加一个
构建
步骤,
Execute SonarQube Scanner
配置 Sonar Scanner。