部署 SonarQube 以及项目集成

Table Of Contents

工作中靠人去做 CodeReview 是很困难的,也容易漏掉问题,所以公司往往需要自动化的代码扫描来卡点,SonarQube 就是这样一个业界成熟使用的服务。

# Docker 方式安装

  1. 如果你还在用 JDK8 那么你只能安装 8.9 版本
  2. 免费的社区版本不支持多分支检测,幸运的是有开源的多分支插件
  3. 更多安装要求

注意修改下面 docker-compose.yml 里的 SONAR_JDBC_PASSWORDPOSTGRES_PASSWORD

version: "3"

services:
  sonarqube:
    image: mc1arke/sonarqube-with-community-branch-plugin:8.9-community
    depends_on:
      - db
    environment:
      TZ: Asia/Shanghai
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: password
      SONAR_WEB_JAVAOPTS: -Xmx1G -Xms512M
      SONAR_SEARCH_JAVAADDITIONALOPTS: -Xmx2G -Xms2G
      SONAR_CE_JAVAOPTS: -Xmx2G -Xms1G
      SONAR_WEB_SSO_ENABLE: true
      SONAR_WEB_SSO_LOGINHEADER: X-Forwarded-DingTalk-Login
      SONAR_WEB_SSO_NAMEHEADER: X-Forwarded-DingTalk-Name
      SONAR_WEB_SSO_EMAILHEADER: X-Forwarded-DingTalk-Email
      SONAR_WEB_SSO_GROUPSHEADER: X-Forwarded-DingTalk-Groups
      SONAR_WEB_SSO_REFRESHINTERVALINMINUTES: 30
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
  db:
    image: postgres:13
    environment:
      TZ: Asia/Shanghai
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: password
    volumes:
      - sonarqube_postgresql:/var/lib/postgresql
      - sonarqube_postgresql_data:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  sonarqube_postgresql:
  sonarqube_postgresql_data:

更多 Docker 环境变量参数参考 environment-variables

上面调整了 SonarQube 的内存参数(参考论坛) 和 SSO 认证(下面会讲这个是为了免密登录)

最后直接 docker compose up -d 启动整个服务

# 插件规则

SonarQube 里自带了 SonarWay 规则,我们也可以在插件市场里安装 FindBugs 规则,然后通过继承的方式创建自定义规则集合。需要注意的是,规则越多,扫描耗时越长。

另外不是所有的规则都是非常合理的,我们可以手动禁用不想要的规则。

可能我们还想集成阿里的 p3c 规则,但是 SonarQube 插件市场里没有现成的插件,网上的插件也很久没有更新了,其实 p3c 本质上是用的 PMD, 我们可以 Fork sonar-pmd 插件,然后自己集成 p3c-pmd 规则 进去。

新增 PmdP3CRulesDefinition.java 配置类
修改 PmdPlugin.java 引入 PmdP3CRulesDefinition.class
运行 sonar-pmd-plugin/src/test/java/org/sonar/plugins/pmd/p3c/P3cXmlBuilder.java 测试类生成 p3c 的配置文件
单独跑 sonar-pmd-plugin mvn install 打包
将打好的 Jar 包手动拷贝到 SonarQube 容器的插件目录下,然后重启

# 项目集成

在 pom.xml 中添加 sonar-maven-plugin 的插件

<plugin>
    <groupId>org.sonarsource.scanner.maven</groupId>
    <artifactId>sonar-maven-plugin</artifactId>
    <version>3.11.0.3922</version>
</plugin>

由于 sonar 支持自定义参数 analysis-parameters, 所以我们可以上报 git commit 等信息,后续通过 SonarQube 的 webhook 发送扫描报告结果就可以带上这些信息,生成一个内容丰富的卡片了。

这里我写了一个简单的脚本来生成 maven 的 sonar 编译参数,其中为了防止同一个 commit 重复扫描,会调用一个内部服务判断该 commit 是否已扫描,扫描结果通过 webhook 来维护。

#!/bin/bash

# 获取脚本传入的环境参数
while getopts ":e:" opt; do
  case ${opt} in
    e )
      env=$OPTARG
      ;;
    \? )
      echo "Usage: scriptname [-e env]"
      exit 1
      ;;
  esac
done

shift $((OPTIND -1))

if [ -z $env ]; then
    env=${SONAR_ENV:-}
fi

# 判断 commit 是否已经被扫描过了
check_exist_commit_id() {
    local commit_id="$1"

    local body_response
    body_response=$(curl -m 5 --connect-timeout 3 -s "http://x.x.x.x/devops/sonarqube/exist?commitId=$commit_id")

    if [[ "$body_response" == "true" ]]; then
        echo "true"
    else
        echo "false"
    fi
}

# 是否开启 sonar 扫描开关
sonar_switch=$NO_SONAR
sonar_host=${SONAR_HOST_URL:-}
sonar_token=${SONAR_LOGIN:-}

if [ "$sonar_switch" = "1" ] || [ -z "$sonar_host" ] || [ -z "$sonar_token" ]; then
    exit
fi

commit_id=$(git rev-parse HEAD)
if [ -z $commit_id ]; then
    exit
fi

exists_commit_id=$(check_exist_commit_id $commit_id)
if [[ "$exists_commit_id" == "true" ]]; then
    exit
fi

# 将项目文件夹名称作为 key
key=$(basename "$(pwd)")

branch=$(git rev-parse --abbrev-ref HEAD)
if [ -z "$branch" ]; then
    exit
fi

# 提交时间
time=$(git show -s --format='%ct')
author=$(git show -s --format=%an)
message=$(git show -s --format=%s | base64 -w 0)
analysis_id=$1
if [ -z "$uuid" ]; then
    analysis_id=$(uuidgen | sed 's/-//g')
fi

ip=$(hostname -i | awk '{if (NF > 1) print $NF; else print $1}')

analysis_properties="-Dsonar.analysis.author=$author -Dsonar.analysis.time=$time -Dsonar.analysis.message=$message -Dsonar.analysis.env=$env -Dsonar.analysis.ip=$ip"
echo " sonar:sonar -Dsonar.host.url=$sonar_host -Dsonar.projectKey=$key -Dsonar.login=$sonar_token -Dsonar.branch.name=$branch -Dsonar.buildString=$analysis_id $analysis_properties "

# WebHook

通过 webhook 我们可以拿到之前上报的信息,以及你配置的质量阈的扫描结果。

# 免密登录

每次收到机器人发送的扫描报告,我们点进去都需要登录 SonarQube 的账号(你也可以纯内网访问,放开 SonarQube 的安全认证,但是不太安全啊

查看文档发现,SonarQube 的认证方式有很多种 authentication

尤其是它支持 HTTP Header 的方式代理认证,这就是我们上文在 docker-compose.yml 里配置的几个 SSO 参数。

那我们要怎么实现点击链接,代理 Header 进去认证呢,肯定是需要一层 Nginx 代理的。为了方便,这里我直接起了一个 OpenResty 的 Docker 服务。

链接上带上签名参数,在 OpenResty 中校验签名,校验通过则添加相关 Header.

这里也许可以接入钉钉的认证,这种方式更好。