SonarQube 代码质量门禁部署实战:让流水线拦住坏代码

代码评审能看逻辑,但覆盖率在下降、重复代码在蔓延、某个模块的复杂度悄悄失控,这类趋势靠人眼很难持续盯住。SonarQube 做的是把代码质量变成可度量、可拦截的流水线环节:每次提交自动扫描,达不到门禁就不让合并。本文走一遍从部署、接入扫描、设定门禁到与 GitLab CI 集成的完整流程,环境口径为 Docker Compose 部署 SonarQube 10.x 社区版 + 独立 PostgreSQL。

前置条件

  • 一台 Linux 服务器,建议 4 核 8G 起步(SonarQube 内置 Elasticsearch,内存给少了会频繁重启),Docker 与 Docker Compose 已安装。
  • 已调整系统参数 vm.max_map_count(下文第一步处理),这是官方硬性要求,不改服务起不来。
  • 一个用于验证的 Git 仓库与可用的 GitLab CI Runner,项目语言以 Java 或通用多语言项目为例(社区版对分支分析的支持有限,下文会说明口径)。
  • 规划好数据目录,SonarQube 的数据与 PostgreSQL 数据都要做持久化并纳入备份。

第一步:调整系统参数并部署

先把内核参数改到位并持久化,否则 Elasticsearch 启动即退出:

sysctl -w vm.max_map_count=524288
echo "vm.max_map_count=524288" >> /etc/sysctl.d/99-sonarqube.conf
ulimit -n 131072

Compose 编排里把 SonarQube 与 PostgreSQL 分开,后者用官方 postgres 镜像,密码用占位符,实际部署替换为强密码并收进密码管理工具:

services:
  sonarqube:
    image: sonarqube:10-community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: "请替换为强密码"
    ports:
      - "9000:9000"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: "请替换为强密码"
      POSTGRES_DB: sonar
    volumes:
      - postgresql_data:/var/lib/postgresql/data
volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql_data:
docker compose up -d
docker compose logs -f sonarqube

日志出现 "SonarQube is operational" 后访问 http://服务器:9000,默认账号 admin/admin,首次登录强制改密码。注意不要使用 SonarQube 自带的内嵌 H2 数据库跑正式环境,官方明确只供试用。

第二步:创建项目并接入扫描

在界面里手动创建项目拿到 projectKey,并生成一个分析专用 Token。扫描端有两种跑法。一种是在 CI 或开发机上装 sonar-scanner CLI,适合先快速验证。项目根目录放配置文件:

sonar.projectKey=my-service
sonar.projectName=my-service
sonar.sources=src
sonar.sourceEncoding=UTF-8
sonar.java.binaries=target/classes
sonar-scanner \
  -Dsonar.host.url=https://sonar.example.com \
  -Dsonar.token=请替换为分析Token

另一种是用官方 Docker 镜像跑扫描,CI 里更常用,不污染 Runner 环境。二进制产物类语言(如 Java)要注意:扫描需要编译后的 class 文件,流水线里扫描步骤必须排在构建之后。扫描完成后到项目页确认数据上报时间与代码行数对得上,再继续下一步,不要带着"扫描其实没扫到代码"的问题去配门禁。

第三步:设定质量门禁

门禁(Quality Gate)是"什么条件下不许过"的规则集合。新建一个自定义门禁并设为默认,指标建议如下,并讲清取舍:

  • 新代码覆盖率 ≥ 80%:只盯新增与改动的代码(New Code 口径),不对存量代码追责。老项目直接要求全量覆盖率达标,门禁永远红,团队第一周就会想办法绕过它。以新代码为口径,存量在每次改动中被逐步"顺路治理"。
  • 新代码重复率 ≤ 3%:复制粘贴是重复代码的主来源,这个指标对新代码卡住即可。
  • 新代码可靠性/安全性评级 = A,且无新增 Blocker 级问题:Blocker 和 Critical 级别的 Bug、漏洞不允许带入,低级别问题先以报告形式呈现,避免门禁过严导致没人理。
  • 可维护性评级与技术债务率:初期只观察不拦截,等团队适应 1–2 个月后再考虑纳入硬指标。

设定口径的核心原则是:门禁拦的是"让代码变差的这次提交",不是历史欠账。SonarQube 里把项目的 New Code 定义设为"与上一版本对比"或"近 30 天",与发版节奏对齐;没有版本概念的项目用天数更稳妥。

第四步:与 GitLab CI 集成,门禁失败阻断合并

在项目的 .gitlab-ci.yml 中加一个扫描 job,关键点是让 job 等待 SonarQube 后台任务完成并拿到门禁结论(-Dsonar.qualitygate.wait=true),结论不通过时 job 失败:

sonarqube-check:
  stage: test
  image: sonarsource/sonar-scanner-cli:latest
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
  script:
    - sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
  allow_failure: false
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

其中 SONAR_HOST_URL 与 SONAR_TOKEN 配在 GitLab 的 CI/CD 变量里,Token 设为 Masked。阻断合并的最后一步在 GitLab 侧:到仓库的"设置 → 合并请求"里勾选"流水线成功才能合并",流水线红了 MR 就合不进去。关于分支与 MR 分析要说清版本口径:社区版只保留主分支分析,分支分析和合并请求装饰(把问题评论回 MR)是 Developer Edition 及以上的能力。社区版的务实做法是:MR 流水线里以 MR 源分支代码做整项目扫描并用 qualitygate.wait 卡结论,能拦住问题,但看不到"本 MR 引入了哪几条"的装饰评论。预算允许再升级版本,不要指望社区版有这个功能然后卡在选型上。

第五步:误报管理与基线

扫描结果里难免有不符合项目实际的问题:框架生成的代码被报重复、测试代码被报硬编码密码等。处理顺序是先在问题条目上标记 Won't Fix 或 False Positive 并写明理由,这类标记会留审计痕迹,比改规则干净;确实是规则不适用整个项目时,再到质量配置(Quality Profile)里停用该规则或调整其作用范围(如把测试目录排除出某些规则)。存量项目首次接入时问题数以千计,不要试图一次清零:以接入时点为基线,门禁只卡新代码,存量问题按模块排期消化,每次迭代在报告里看存量趋势是否下降即可。

验证

  • 故意在测试分支提交一段含明显问题的新代码(如硬编码密码、未关闭资源),MR 流水线的 sonarqube-check 应失败且 MR 无法合并。
  • 修复后重跑,门禁变绿,项目页的 New Code 指标与本次改动范围一致。
  • 重启 Compose 后项目数据、门禁配置仍在,确认持久化生效。

常见问题

分析特别慢。先看瓶颈在扫描端还是服务端:扫描端慢多与代码量、未排除目录有关,把 node_modules、构建产物、第三方代码加进 sonar.exclusions;服务端慢多是后台任务堆积,加大 SONAR_CE_JAVAOPTS 的内存(在 Compose 里给 sonarqube 服务加环境变量)并排查是否有多个大项目同时触发全量分析。

容器反复重启,日志报内存不足或 max virtual memory areas 过低。前者给足内存(8G 起步不是虚标),后者就是 vm.max_map_count 没改或改了没对 Docker 宿主机生效,注意在宿主机上执行 sysctl 而不是容器内。

升级版本后起不来。SonarQube 升级必须先备份 PostgreSQL,再按官方升级路径走 /setup 做数据迁移,跨大版本不能跳级;镜像 tag 直接拉最新版硬怼是数据损坏的高发姿势。升级前把当前版本号和数据库备份位置记进变更单。

Java 项目报 Please provide compiled classes。扫描时 target/classes 不存在或路径不对,确保 CI 里构建步骤在扫描之前且 binaries 路径与产物位置一致,Maven/Gradle 项目优先用各自的 Sonar 插件跑,路径问题会少很多。

小结

SonarQube 门禁能不能用起来,技术部署只占三成,七成在指标口径:只卡新代码、只拦高等级问题、先拦住再慢慢加严。按这个节奏接入,门禁会从"讨厌的红灯"变成团队默认信任的质量底线。

发表回复

后才能评论