尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

SonarQube 7.4 部署、扫描与质量门禁实战

SonarQube 7.4 部署、扫描与质量门禁实战 简介SonarQube 7.4 是面向开发者和测试团队的开源代码质量管理工具安装包适合需要快速获取稳定版本、规避官网下载缓慢问题的用户。它可用于 Java、Python、C#、JavaScript 等项目的静态分析帮助定位漏洞、代码异味与复杂度问题并支持与 Jenkins、GitLab CI/CD 等持续集成流程衔接便于团队建立自动化质量检查。压缩包为 rar 格式约 161.38MB文件总数与类型明细上游暂未提供包内通常包含启动脚本、配置、插件、运行库及 Web 界面等完整运行组件可按目录结构取用。已有 643 人学习下载适合希望本地部署 SonarQube、扩展分析规则或做代码质量评测的开发者参考。1. 接手老项目时我为什么先看 sonarqube-7.4接手一个跑了三年的老项目时最让我头疼的往往不是代码写得差而是没人说得清它到底差在哪里。sonarqube-7.4 这个包解决的正是这个问题它是一套可以自行部署的代码质量平台会把重复率、坏味道、潜在 bug、单元测试覆盖率、安全漏洞这些指标一次性扫进数据库再用 Web 界面和质量门禁把「这次改动能不能合并」变成一条可执行的规则。7.4 这个版本号意味着它不是玩具服务端、扫描器、规则引擎和内嵌的 Elasticsearch 都收在一个压缩包里解压、配库、启动三步就能在内网跑起来。它适合代码不能出内网、又需要一个统一质量标准的团队也适合想在自己机器上把静态分析链路完整走一遍的开发者。2. 部署前先算清环境条件JDK 8、MySQL 5.7 与三个系统参数SonarQube 7.4 这类老包最容易出问题的地方往往在启动脚本之前就已经决定了。很多人拿到压缩包后直接执行sonar.sh start看到 Web 端口没起来就开始怀疑包坏了实际情况是百分之八十的启动失败都能追溯到 JDK 版本、数据库版本或内核参数跟代码质量分析本身关系不大。这一章先把环境条件算清楚后面所有章节才站得住脚。2.1 为什么 7.4 的运行时边界比你想的窄服务端运行 JDK 8 是最稳的选择没有必要去追新。7.4 的内嵌 Elasticsearch、打包方式、字节码版本都在 JDK 8 这一代定型我见过有人用 JDK 11 启动日志里 ELasticsearch 还没进入 bootstrap 阶段就退出了或者sonar.sh console打印到一半停在Starting SonarQube...。扫描端则要分开看sonar-scanner本身跑在 JDK 8 上最保险但如果要分析 Java 11 编译的产物得确认sonar-java插件的版本足够新否则规则一大片不生效你还会以为是扫描器没工作。数据库的坑更隐蔽。7.4 的年代 MySQL 8.0 还不在支持列表里MySQL 5.6 / 5.7 是常规选择PostgreSQL 9.6 / 10 更省心。有人图省事把库建到 MySQL 8.0 上建表阶段就崩在默认字符集和认证插件上有人用 5.7 但没处理ONLY_FULL_GROUP_BY分析跑到一半报 SQL 语法错误。下面这个表是我自己部署前会对照一遍的清单组件可用版本常见误用后果JDK服务端811 / 17ES 起不来服务卡在启动阶段MySQL5.6 / 5.78.0建表失败、驱动不兼容PostgreSQL9.6 / 1012 及以上JDBC 行为差异升级脚本报错文件句柄≥ 65536默认 1024分析大项目时 ES 报 too many open filesvm.max_map_count≥ 262144默认 65530ES 直接拒绝启动这些数字不是玄学是我在一次部署里被 ES 的max virtual memory areas vm.max_map_count [65530] is too low卡了半个下午之后写进部署脚本的固定动作。2.2 环境检查与系统参数落地我习惯把环境检查写成一个脚本每次换机器先跑一遍比事后翻日志省事得多。下面这段是我常用的版本里面有注释按需改路径#!/bin/bash # 1. 确认服务端 JDK 是 8 java -version 21 | grep -q 1.8 || echo WARN: 服务端 JDK 不是 8后续可能起不来 # 2. ES 必须的内核参数重启后仍生效 grep -q vm.max_map_count /etc/sysctl.conf || \ echo vm.max_map_count262144 | sudo tee -a /etc/sysctl.conf sudo sysctl -p # 3. 文件句柄服务端用户级别 ulimit -n 65536 ulimit -n # 回显确认低于 65536 就要改 /etc/security/limits.conf # 4. 端口占用检查9000 是 Web9001 是内嵌 ES ss -lntp | grep -E :(9000|9001)\b echo 端口被占用改 sonar.web.port # 5. 目录权限别用 root 直接跑 id sonar 2/dev/null || sudo useradd -m -s /bin/bash sonar脚本里第 1 步只是告警不拦截因为有些机器上同时装了多个 JDK真正的JAVA_HOME在sonar.sh里由wrapper.conf或环境变量决定。第 4 步的 9001 端口很多人不知道7.4 的内嵌 Elasticsearch 默认绑在 9001Web 是 9000两个都被占了才会出现「改了端口还是起不来」的怪事。第 5 步单独建用户也不是洁癖SonarQube 的 data 目录和临时目录用 root 跑过之后换普通用户启动会直接报权限拒绝而且logs/里的文件属主混着排查时很干扰判断。执行完这个脚本还不算完sonar.sh里读取的是conf/sonar.properties数据库和 JVM 参数要在那里落。下一步才是建库。2.3 建库建用户与 sonar.properties 的关键项数据库这一段我建议用命令行把库和用户一次建好别用图形工具点因为字符集和排序规则点错了不报错等到扫描中文注释乱码才发现。MySQL 5.7 下的建库语句-- 字符集必须是 utf8排序规则用 general_ci7.4 的建表脚本按这个来的 CREATE DATABASE sonar CHARACTER SET utf8 COLLATE utf8_general_ci; -- 单独建账号不要用 root 连方便后面审计和限权 CREATE USER sonar% IDENTIFIED BY Sonar2018; GRANT ALL PRIVILEGES ON sonar.* TO sonar%; FLUSH PRIVILEGES; -- 确认 sql_mode 里没有 ONLY_FULL_GROUP_BY有就临时去掉 SELECT sql_mode;这段 SQL 的逻辑很直白库给 sonar 专用账号只授权这一个库避免扫描数据和其他业务混在一起。第三句SELECT sql_mode是重点7.4 的部分统计查询用了非标准的分组写法ONLY_FULL_GROUP_BY一开就会在分析阶段报Expression #N of SELECT list is not in GROUP BY clause。解决办法有两个改全局 sql_mode或者在 JDBC 连接串里用sessionVariables覆盖。我一般选后者不动数据库全局配置影响面小。然后是conf/sonar.properties这个文件里大多数行是注释真正要改的就那几条# 数据库连接注意 5.7 用 com.mysql.jdbc.Driver 对应的 URL 格式 sonar.jdbc.usernamesonar sonar.jdbc.passwordSonar2018 sonar.jdbc.urljdbc:mysql://127.0.0.1:3306/sonar?useUnicodetruecharacterEncodingutf8rewriteBatchedStatementstrueuseConfigsmaxPerformancesessionVariablessql_mode # Web 监听内网访问就把 host 放开 sonar.web.host0.0.0.0 sonar.web.port9000 sonar.web.context/sonar # 三块 JVM 内存分开配Web、计算引擎、ES sonar.web.javaOpts-Xmx1g -Xms256m -XX:HeapDumpOnOutOfMemoryError sonar.ce.javaOpts-Xmx1g -Xms128m sonar.search.javaOpts-Xmx1g -Xms1g -XX:HeapDumpOnOutOfMemoryError参数说明要展开一下。rewriteBatchedStatementstrue是给分析结果批量入库用的不加的话大项目写库会慢到怀疑人生useConfigsmaxPerformance是 MySQL 驱动自带的性能配置集sessionVariablessql_mode就是前面说的绕开分组校验。sonar.web.context/sonar表示访问路径变成http://ip:9000/sonar如果反向代理已经占了子路径这里要一致否则登录后静态资源 404页面白屏。内存三项是另一个高频翻车点7.4 把 Web、计算引擎CE和 Elasticsearch 拆成三个进程-Xmx1g只是最低配。项目多、分支多的时候CE 排队会越来越长日志里出现Queue size持续上涨那就得把sonar.ce.javaOpts加到 2g 以上。三块加起来要小于机器物理内存见过有人三项都写 4g、机器只有 8g结果被 OOM killer 挑中日志里连异常都来不及打成了黑匣子。提示sonar.properties是 properties 格式连接串里的不需要转义如果同一份配置要写进 XML 或 shell 变量记得处理转义别直接复制。3. 启动、扫描、接 CI把 sonar-scanner 跑通的三条路径环境配好之后真正决定这套东西好不好用的是「扫描链路」能不能稳定跑起来。7.4 提供了三种接入方式直接调sonar-scanner命令行、用 Maven 插件、或者在 CI 里用包装脚本。三条路径的分析内核是同一个差别在参数从哪来、工作目录在哪、失败时看哪份日志。这一章按从手工到自动的顺序走一遍每一步都给出可以直接复制的配置。3.1 启动脚本与三个必看的日志启动只有一条命令但要分清start和console的区别前者后台跑日志进文件后者前台跑适合第一次部署时盯着看# 前台启动CtrlC 结束适合首次验证 ./bin/linux-x86-64/sonar.sh console # 后台启动与状态查询 ./bin/linux-x86-64/sonar.sh start ./bin/linux-x86-64/sonar.sh status # 三个日志分开看别只 tail sonar.log tail -f logs/sonar.log # 总入口能看到各组件启停顺序 tail -f logs/web.log # Web 层登录 500、插件加载失败在这 tail -f logs/es.log # 内嵌 ES内核参数和内存问题在这观察点有三个。第一sonar.log里出现SonarQube is up才算真正可用在这之前 Web 端口可能已经监听但访问会 503别急着报 bug。第二es.log里如果出现max virtual memory areas或initial heap size字样回到 2.2 改内核参数和sonar.search.javaOpts。第三启动完成后logs/下会多出ce.log计算引擎的任务队列和失败原因都在里面扫描结果显示「Pending」很久不动就是 CE 卡住了看这个文件比看 Web 界面有用。第一次登录用默认账号进去第一件事就是改密码并把「强制认证」打开。7.4 的权限模型是「项目 → 权限模板」两级不设模板的话新建项目默认公开内网也不合适。3.2 sonar-scanner 命令行属性文件写对才算跑通下载对应版本的sonar-scanner解压后把bin加进 PATH。项目根目录放一个sonar-project.properties这是命令行方式的核心# 项目唯一标识建议用 组ID:项目名 的格式避免多团队重名 sonar.projectKeycom.example:order-service sonar.projectNameorder-service sonar.projectVersion1.4.2 # 源码与测试目录多个目录用逗号分隔 sonar.sourcessrc/main/java sonar.testssrc/test/java # Java 分析必须指定编译产物否则大量规则不执行 sonar.java.binariestarget/classes sonar.java.test.binariestarget/test-classes # 编码不写就是平台默认中文注释会变乱码 sonar.sourceEncodingUTF-8 # 排除自动生成代码和 DTO减少噪音 sonar.exclusions**/generated/**,**/*DTO.java sonar.coverage.exclusions**/config/**,**/*Application.java然后执行# 先编译出 class再扫描顺序反了 sonar.java.binaries 就是空的 mvn -q clean compile # 指向服务端token 用登录后「我的账号 → 安全」里生成的那个 sonar-scanner \ -Dsonar.host.urlhttp://192.168.1.10:9000/sonar \ -Dsonar.login8f3c1a2b9d4e7f6a5c8b3d2e1f0a9b8c7d6e5f40这段配置里最容易忽略的是sonar.java.binaries。很多人只写sonar.sources就跑结果规则集里跟类型推断相关的几百条全不生效报告看上去「很干净」其实是半残的分析。原因在于sonar-java插件要靠字节码做符号解析没有 class 文件就只能做语法级检查。我第一次遇到时还以为规则没同步翻了半天插件日志才反应过来。第二个要注意的是路径基准。sonar-scanner以当前工作目录为基准解析sonar.sources在 CI 里如果先cd到子模块再执行属性文件里的相对路径就全错了表现是「扫描成功但 0 个文件」。稳妥做法是始终在项目根目录执行属性文件也放根目录。3.3 Maven 项目接入与多模块聚合Java 项目我更推荐 Maven 插件少维护一个属性文件参数跟着 pom 走。在父 pom 的build/plugins里加plugin groupIdorg.sonarsource.scanner.maven/groupId artifactIdsonar-maven-plugin/artifactId version3.4.1.1004/version /plugin执行时通过-D传服务端地址和 token避免把凭据写进 pom# verify 会顺带跑测试并生成覆盖率报告jacoco 的 xml 路径在属性里配 mvn clean verify sonar:sonar \ -Dsonar.host.urlhttp://192.168.1.10:9000/sonar \ -Dsonar.login${SONAR_TOKEN} \ -Dsonar.coverage.jacoco.xmlReportPathstarget/site/jacoco/jacoco.xml多模块项目这里有个细节默认 Maven 插件会把每个子模块当成独立项目推上去项目列表里一下子多出十几个条目看着很乱。想聚合成一个项目就在根 pom 里加sonar.modules显式列出模块名或者干脆在根目录用sonar-scanner加sonar.sources指向所有模块目录。我一般选聚合因为质量门禁按项目算拆太碎等于没有门禁。CI 里还有一点token 必须走环境变量或凭据管理别写进命令行历史。sonar.login传 token 之后sonar.password留空即可7.4 支持这种「token 当用户名」的认证方式。3.4 第一次扫描后先看三张表扫描完成的提示只是「执行成功」不代表结果可信。我习惯进去先看三处项目的「代码行数」是否和预期接近差距大就是路径或排除规则写错了「覆盖率」是否为 0为 0 多半是 jacoco 报告路径没对上「重复率」是否异常高超过 20% 通常是sonar.exclusions漏掉了生成代码目录。这三项对不上后面的质量门禁全是假的。4. 规则与质量门禁让扫描结果真正卡住合并扫描能跑通只是第一步真正决定这套平台有没有价值的是规则集和门禁阈值。默认规则开了几百条全量跑出来几千个问题开发看一眼就关掉页面这种部署等于白装。我见过太多团队在这一步放弃然后把 SonarQube 当成「领导要看的时候打开一下」的摆设。这一章讲怎么把噪音压下去让门禁只盯新增代码。4.1 Quality Profile 的继承与「别一上来全开」7.4 的规则组织方式是 Quality Profile质量配置一个项目绑定一个 ProfileProfile 可以继承内置的Sonar way再增删规则。我的做法是复制一份Sonar way命名成团队名然后做三件事第一步把和团队技术栈无关的规则关掉。比如项目里没有 SQL 拼接、没有反射调用相关的安全规则留着只会产生误报。第二步把「代码风格」类规则降级为「信息」而不是「严重」风格问题不该阻断合并。第三步把团队真正在意的规则提上来比如空指针风险、资源未关闭、异常吞掉这几类在线上出过事的一律设为阻断。规则调整后有个生效问题Profile 改了不会自动重扫历史项目只有下一次分析才按新规则算。想让历史数据也更新得在项目上手动触发一次全量分析或者用后台任务重算。这一点在界面上没有明显提示容易让人误以为配置没保存。4.2 质量门禁阈值只卡新增代码7.4 的门禁概念里有一个「泄漏期」Leak Period后来的版本改叫 New Code。它的意义是把「历史债务」和「这次改动」分开历史问题几万个不奇怪但这次提交不能新增。我配门禁时基本只用泄漏期指标条件阈值说明新增代码覆盖率≥ 70%低于这个数说明测试没跟上新增代码重复率≤ 5%复制粘贴是债务增长最快的来源新增 bug 0阻断级必须修新增漏洞 0阻断级安全类不妥协新增代码坏味道≤ 10给重构留一点缓冲不设 0阈值不是拍脑袋定的要按团队现状调。新团队第一周可以只卡「新增 bug 0」两周后加覆盖率一个月后加坏味道。一次性全开的结果一定是有人绕过门禁比如把分析从 CI 里摘掉那比不设门禁更糟。门禁绑在项目上之后CI 里的sonar-scanner会在结束时返回非零退出码流水线就能拦住合并。这里有个细节sonar-scanner默认不等待后台分析完成要加-Dsonar.analysis.mode相关的等待参数或者改成调用 Web API 轮询任务状态。我一般用后者下面这段脚本放在扫描之后#!/bin/bash # 轮询计算引擎任务状态直到分析完成或超时 TASK_ID$(curl -s -u ${SONAR_TOKEN}: \ http://192.168.1.10:9000/sonar/api/ce/task?id${REPORT_TASK_ID} | grep -o status:[A-Z_]*) for i in $(seq 1 60); do STATUS$(curl -s -u ${SONAR_TOKEN}: \ http://192.168.1.10:9000/sonar/api/ce/activity?ps1 \ | grep -o status:[A-Z_]* | head -1) [ $STATUS status:SUCCESS ] break [ $STATUS status:FAILED ] exit 1 sleep 5 done # 再查门禁结果非 OK 就让流水线失败 GATE$(curl -s -u ${SONAR_TOKEN}: \ http://192.168.1.10:9000/sonar/api/qualitygates/project_status?projectKeycom.example:order-service) echo $GATE | grep -q status:OK || exit 1这段逻辑分两段先等分析任务跑完再查门禁状态。直接查门禁可能拿到上一次的结果是个典型的时序坑尤其在 CI 里扫描刚提交就查询的时候。4.3 用 Web API 把指标拉出来做二次处理Web 界面适合人看不适合做趋势。想把每天的质量数据存下来用/api/measures/component拉指标import requests SONAR http://192.168.1.10:9000/sonar TOKEN 8f3c1a2b9d4e7f6a5c8b3d2e1f0a9b8c7d6e5f40 PROJECT com.example:order-service resp requests.get( f{SONAR}/api/measures/component, params{ component: PROJECT, metricKeys: bugs,vulnerabilities,code_smells,coverage, duplicated_lines_density,ncloc, additionalFields: metrics, # 带上指标元信息方便存库 }, auth(TOKEN, ), # 7.4 用 token 当用户名密码留空 timeout10, ) resp.raise_for_status() data resp.json() # 拍平成 指标名 - 值 的字典直接入库 measures {m[metric]: m.get(value) for m in data[component][measures]} print(measures)参数说明metricKeys里的指标名要和 7.4 的指标列表对得上写错一个不会报错只是返回里少一项所以第一次调用建议把additionalFieldsmetrics带上核对一下返回的元信息。auth(TOKEN, )这种写法是 requests 的 basic auth用户名位置放 token是 7.4 支持的认证方式之一比在 URL 里拼?login安全。这段代码实际跑起来还会遇到权限问题token 所属账号必须对该项目有「浏览」权限否则返回 403 但 body 是 HTML 登录页resp.json()直接抛解析异常。我的习惯是先判断resp.headers[Content-Type]不是 JSON 就打印状态码退出比看堆栈快。注意Web API 在 7.4 上区分/api/...和/api/....的路径大小写脚本里建议把服务端地址做成配置项别在多个文件里硬编码。5. 避坑与排查五个用日志换来的排查经验下面这五条每条都是先看到异常现象、再翻日志定位、最后改配置解决的。写出来不是为了列清单而是让你在遇到同样报错时能直接跳到原因那一步。5.1 启动到一半 ES 退出Web 端口一直 503现象sonar.sh start显示启动成功sonar.log刷到Starting Elasticsearch...就停住访问 9000 返回 503es.log里有max virtual memory areas vm.max_map_count [65530] is too low。原因内嵌 Elasticsearch 启动时要检查内核的 mmap 上限默认 65530 不够这个检查在 7.x 里是硬性的不通过直接退出而且不会把错误冒泡到 Web 层。解决按 2.2 的脚本设置vm.max_map_count262144并sysctl -p然后确认sonar.search.javaOpts的-Xmx不超过物理内存的一半。改完必须完整重启只重启 Web 没用。5.2 扫描报 Unsupported major.minor version 或 NoSuchMethodError现象命令行执行sonar-scanner立刻报Unsupported major.minor version 52.0或者跑起来后抛java.lang.NoSuchMethodError。原因前者是用了 JDK 7 及以下跑扫描器52.0 对应的是 Java 8 字节码后者多半是JAVA_HOME指向了一个混装多个 JDK 的目录扫描器加载了旧版类库。解决echo $JAVA_HOME和java -version对一遍确保都是 8。CI 镜像里常见多个 JDK 并存显式在流水线里export JAVA_HOME/usr/lib/jvm/java-8-openjdk别依赖默认值。5.3 分析成功但 Java 规则大面积不生效现象扫描显示成功代码行数也对但类型相关的规则一条都不报报告干净得不正常。原因sonar.java.binaries没配或指向了不存在的目录sonar-java拿不到字节码只能做语法级检查。解决先mvn clean compile确保target/classes存在再检查属性文件里的路径是否相对当前工作目录。CI 里如果先cd到子目录执行路径基准就变了这是最常见的诱因。5.4 MySQL 报分组语法错误或连接串参数失效现象分析进行到统计阶段失败日志里是Expression #1 of SELECT list is not in GROUP BY clause或者连接串里明明加了sessionVariables却不生效。原因MySQL 5.7 默认开启ONLY_FULL_GROUP_BY连接串参数写错位置比如写在sonar.jdbc.url之外的属性里会被静默忽略。解决先SELECT sql_mode确认再检查sonar.jdbc.url里参数是否完整、前后有没有空格。参数写在 URL 里不要另起一行写成独立属性。5.5 projectKey 重名或编码不一致导致数据对不上现象两个项目的数据混在一起或者同一个项目每次扫描都新建一条记录历史趋势断掉。原因sonar.projectKey重复或者大小写不一致Order-Service和order-service在平台上是两个项目中文注释乱码则是sonar.sourceEncoding没配。解决projectKey统一用「组ID:项目名」的小写格式写进模板文件不让人手填。编码一行sonar.sourceEncodingUTF-8必须写不要指望平台默认值。6. 进阶把每日质量数据做成一张能看的趋势表平台自带的历史趋势图够用但跨项目汇总、按周对比、给非技术同事看就得自己拉数据。我现在的做法是一个小脚本每天定时跑一次把bugs / vulnerabilities / coverage / duplicated_lines_density四个指标写进一张宽表再用最简单的方式画折线。6.1 定时任务与结果落库脚本基于 4.3 的 API 调用扩成批量项目import requests, sqlite3, datetime SONAR http://192.168.1.10:9000/sonar TOKEN 8f3c1a2b9d4e7f6a5c8b3d2e1f0a9b8c7d6e5f40 PROJECTS [com.example:order-service, com.example:user-center] conn sqlite3.connect(/data/sonar_metrics.db) conn.execute(CREATE TABLE IF NOT EXISTS daily( day TEXT, project TEXT, bugs INT, vuln INT, coverage REAL, dup REAL, PRIMARY KEY(day, project))) today datetime.date.today().isoformat() for p in PROJECTS: r requests.get(f{SONAR}/api/measures/component, params{component: p, metricKeys: bugs,vulnerabilities,coverage,duplicated_lines_density}, auth(TOKEN, ), timeout10) m {x[metric]: x.get(value) for x in r.json()[component][measures]} conn.execute(INSERT OR REPLACE INTO daily VALUES (?,?,?,?,?,?), (today, p, int(m.get(bugs, 0)), int(m.get(vulnerabilities, 0)), float(m.get(coverage, 0)), float(m.get(duplicated_lines_density, 0)))) conn.commit()配一条 cron 每天凌晨跑注意脚本里的服务端地址和 token 从环境变量读别硬编码。落库用 SQLite 就够数据量一年也就几千行没必要上重型存储。6.2 我这几年养成的两个习惯第一个习惯是每次升级或改规则之后先拿一个非关键项目跑一次全量分析对比指标变化幅度。规则一改历史数据就不连续了图上会出现一个断崖不标注的话后面的人会以为是代码质量突然恶化。我一般会在库里加一列note把规则变更日期记上。第二个习惯是门禁阈值只减不增。团队熟悉了再加新条件比一次性收紧再回退要好。血泪经验是有一回直接把覆盖率门禁从 60% 提到 80%结果连续三天有人把测试类注释掉来绕过反而把覆盖率的真实性搞没了。从那以后我每次调整门禁都先在群里贴一次前后对比数据让人知道为什么改再改。这套东西跑顺之后日常维护其实就剩看ce.log和每周扫一眼趋势表。希望帮到你。本文还有配套的精品资源点击获取
返回列表