本文来自作者 邹毅 在 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 有开源免费版本也有收费企业版本,对于小团队用免费的就足够了。
我制定了两个任务:
为代码编写可执行的测试用例,任何人都可以执行测试用例并获取代码覆盖率报告,且上线(发布)前代码覆盖率必须达到 80% 以上。
部署一套公开透明的代码质量系统,能够实时得知项目还有什么待优化的部分,代码质量报告及待优化的代码位置任何人都可以获取。
为任务设定了详细计划:
step 1:所有新项目独立管理开发、测试及部署。
step 2:所有测试用例优先保障代码覆盖率,再增加测试结果验证。
step 3:静态代码分析对代码质量的标准应逐步提高
stage 1:PSR-2 代码规范;
stage 2:重复代码率不能高于 5%;
stage 3:执行 SCS(Symfony Coding Standards);
stage 4:…
step 4:依托代码质量系统对项目代码每季度做一次整体优化,并调整代码规范。
step 5:试行将 Sonar 中的项目代码质量评分纳入考评范围。
计划很好,我们可以通过 phpunit 做测试,通过 Sonar 做统计报告,但缺乏一个重要的持续集成能力,总不能人工维护这些结果吧。
其实很多时候是环境限制了我们的能力发挥,而没有环境的时候就需要 DevOps 去创造环境。搭建一套持续集成系统,能够在我们代码有提交时自动帮我们运行测试任务,并做静态代码分析后把报告上传到 Sonar,我选择 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 后会进入安装设置界面,全程按向导提示进行安装
war 文件路径:/usr/lib/jenkins/jenkins.war
日志路径: /var/log/jenkins/jenkins.log
端口 8080
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 集成和用于测试报告收集分析的插件
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 。
在新项目里实践技术债务偿还自然比直接动老代码是好的多。待此项目成功,为大家建立一个良好的标杆项目,就能更好的在公司推行此方案。
Sonar 中的项目数据是通过一个客户端跑完分析后上报到服务端来显示的,我们可以通过 Jenkins 插件来执行这个任务。
Gitlab 与 Jenkins 集成有两种方式:
每次 Gitlab 项目 push 时触发 webhook 通知 Jenkins 。
Jenkins 定时去扫描 Gitlab 项目的 Merge Request 列表,发现有待构建的变更则执行。
完整的工作流为 Gitlab -> Jenkins ( shell -> phpunit -> sonar-scanner ) -> Sonar
在 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。
# 项目 Key ,与 Sonar 里新建项目时定义的相同 sonar.projectKey=pay_center # 设定 Sonar 分析的代码目录,对于依赖库就不做分析了,既高效又精准 sonar.sources=./app/ # Sonar 可以直接读取 phpunit 生成的 clover 格式覆盖率报告文件并上传 sonar.php.coverage.reportPath=build/clover.xml sonar.php.tests.reportPath=build/report.xml # Sonar 的 web 服务地址 sonar.host.url=http://172.16.16.111:9009 sonar.login=88aa1142d56213a2bf21ba23e981de8a09bacb9f sonar.sourceEncoding=UTF-8 # 这里设置为 ISO-8859-1 是为了代码静态分析时忽略文件编码,兼容性更好 sonar.jproperties.sourceEncoding=ISO-8859-1
项目运行一段时间后的效果
通过一段时间的开发,代码覆盖率终于突破 80% ,可喜可贺。
我还把 Sonar 中项目的代码质量配置设置的更加严格,这样就可以暴露出更多问题,以便有针对性的逐一优化。
这个项目是我第一次在实际业务开发中实践 TDD,过程很愉快。Jenkins 帮我把各个环节给串联起来,我的代码可以在远端尽情的自动跑测试脚本。
而通过查看 Sonar ,我可以对整个项目及时发现代码中的代码规范问题、BUG、坏味道和重复代码,并定个小目标,始终让代码覆盖率不低于 80% 。
团队的技术负责人也可以借助 Sonar 来合理安排未来的项目规划。
最后总结回答文章开头抛出的问题:
DevOps 怎么给开发团队减负?
不断引入辅助工具提升开发效率、降低运维复杂度、提升自动化测试在项目的测试占比,这个工作必然是跨界的,你需要对开发、运维和测试都有一定程度的见解,发现问题,抛出自己的解决问题思路及预估效果,最后落地。
单一项目库(Mono-repo)似乎比多项目库(Multi-repo)拆分效果更佳?
我个人认为对于国内八成以上的开发团队,多项目库都更合适,尤其是因历史原因产生的单项目库,不叫 Mono-repo,应该尽早做考虑代码拆分,以免尾大不掉。
更严重的是一旦针对大项目库需要人人都负责的时候,那就会变成没有人对之负责。而朝微服务化演进路上,团队必然会更多更快的引入独立管理的项目。
小团队的开发如何让代码质量更靠谱?
首先要能判断出代码质量是什么水平,为此引入 Sonar ,然后再谈怎么提高代码质量,比如从提高自动化测试覆盖率、自动语法检查、代码规范制定及落地、代码架构调整等方面入手来提升。
TDD 开发应该用什么姿势落地?
在我的实践中,快速落地我选择了做 API 接口测试,这种测试保证了我对外交付的接口输入返回都能符合预期,虽然一开始比较粗糙,但可以快速实现更多代码的测试覆盖,而基于 TDD 开发过程中就是不断的测试 - 提高覆盖率 - 重构。
文章写完了,这篇主要是对我在开发团队中基础设施搭建的一个总结。在此给自己挖个坑,下次我讲讲业务开发另外一个比较关心的的话题——线上问题排查和监控报警,欢迎关注。
最后祝大家 2018 年少出 bug,多些时间陪伴家人。
1. Configure Jenkins CI Integration with Merge Requests:https://articles.assembla.com/using-assembla-integrations/configure-jenkins-ci-integration-with-merge-requests
2. JENKINS Documents:https://wiki.jenkins.io/display/JENKINS/Installing+Jenkins+on+Red+Hat+distributions
3. SONAR Documents:https://docs.sonarqube.org/display/SONAR/Requirements#Requirements-Linux
4. Mono-Repo VS Multi-Repo:https://medium.com/@patrickleet/mono-repo-or-multi-repo-why-choose-one-when-you-can-have-both-e9c77bd0c668
近期热文
《谈谈 Java 内存模型》
《Jenkins 与 GitLab 的自动化构建之旅》
《通往高级 Java 开发的必经之路》
《谈谈源码泄露 · WEB 安全》
《用 LINQ 编写 C# 都有哪些一招必杀的技巧?》
《机器学习面试干货精讲》
《深入浅出 JS 异步处理技术方案》
「阅读原文」看交流实录,你想知道的都在这里