使用sonarQube做代码质量检测

  1. 1. 创建sonarqube服务
    1. 1.1. (可选)汉化配置
    2. 1.2. (可选)LDAP配置
  2. 2. sonar-scanner的使用
    1. 2.1. 特殊说明
    2. 2.2. 使用官方的sonar-scanner-cli容器
    3. 2.3. 使用jenkins来自动化代码扫描
      1. 2.3.1. 获取sonar的token
  3. 3. 参考文档

创建sonarqube服务

可以使用官方的sonarqube:lts-community长期支持版, 也可以自己写Dockerfile,此处我们用自己的dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FROM eclipse-temurin:17-alpine
ENV SONAR_VERSION=9.9.1.69595\
SONARQUBE_HOME=/opt/sonarqube \
# Database configuration
# Defaults to using H2
SONARQUBE_JDBC_USERNAME=sonar \
SONARQUBE_JDBC_PASSWORD=sonar \
SONARQUBE_JDBC_URL=
# Http port
EXPOSE 9000

RUN addgroup -S sonarqube && adduser -S -G sonarqube sonarqube

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN set -x \
&& apk add --no-cache unzip \
&& apk add --no-cache su-exec wget \
&& apk add --no-cache bash
RUN mkdir -p /opt \
&& cd /opt \
&& wget -O sonarqube.zip --no-verbose "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${SONAR_VERSION}.zip \
&& unzip sonarqube.zip \
&& mv sonarqube-$SONAR_VERSION sonarqube \
&& chown -R sonarqube:sonarqube sonarqube \
&& rm sonarqube.zip* \
&& rm -rf $SONARQUBE_HOME/bin/*

VOLUME "$SONARQUBE_HOME/data"

WORKDIR $SONARQUBE_HOME
COPY run.sh $SONARQUBE_HOME/bin/
RUN chmod +x $SONARQUBE_HOME/bin/run.sh

ENTRYPOINT ["./bin/run.sh"]

对应的run.sh(在github官方镜像可获取)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh

# https://raw.githubusercontent.com/SonarSource/docker-sonarqube/4d76dfe9fb4c5d41ba1852cd5072dbff15ca5a20/7.1-alpine/run.sh

set -e

if [ "${1:0:1}" != '-' ]; then
exec "$@"
fi

chown -R sonarqube:sonarqube $SONARQUBE_HOME
exec su-exec sonarqube \
java -jar lib/sonar-application-$SONAR_VERSION.jar \
-Dsonar.log.console=true \
-Dsonar.jdbc.username="$SONARQUBE_JDBC_USERNAME" \
-Dsonar.jdbc.password="$SONARQUBE_JDBC_PASSWORD" \
-Dsonar.jdbc.url="$SONARQUBE_JDBC_URL" \
-Dsonar.web.javaAdditionalOpts="$SONARQUBE_WEB_JVM_OPTS -Djava.security.egd=file:/dev/./urandom" \
"$@"

build镜像完成后, 通过可以通过docker或者kubernetes 启动该容器, 此处采用docker-compose的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
version: '3'
services:
sonarqube:
# 可使用我们自己的镜像
image: sonarqube:lts-community
ports:
- 9000:9000
- 9001:9001
- 9002:9092
environment:
# 配置pg连接地址
SONARQUBE_JDBC_USERNAME: sonar
SONARQUBE_JDBC_PASSWORD: sonar
SONARQUBE_JDBC_URL: 'jdbc:postgresql://db:5432/sonar'
JAVA_OPTS: '-Duser.timezone=Asia/Shanghai'
depends_on:
- db
volumes:
- ./data/conf:/opt/sonarqube/conf
- ./data/data:/opt/sonarqube/data
- ./data/extensions:/opt/sonarqube/extensions
- ./data/bundled-plugins:/opt/sonarqube/lib/bundled-plugins
networks:
- sonar-net
db:
image: postgres:10.4-alpine
ports:
- 5432:5432
environment:
- POSTGRES_USER=sonar
- POSTGRES_PASSWORD=sonar
volumes:
- ./data/postgresql:/var/lib/postgresql
- ./data/postgresql_data:/var/lib/postgresql/data
networks:
- sonar-net
networks:
sonar-net:

sonarqube的服务端口为9000, 我们通过docker-compose启动后可以通过9000端口访问。

sonarqube的默认管理员用户名密码为admin/admin

(可选)汉化配置

点击进入配置 –> 应用市场, 在插件上方的提示点击确认风险后,在搜索框中搜索chinese,可安装汉化插件, 安装完成后点击重启sonar即可汉化完成。

(可选)LDAP配置

在较高版本的sonarqube中, 已经不需要通过LDAP插件来实现LDAP功能,

我们在docker-compose.yml所在文件夹下的data/conf下,新建sonar.properties</font>文件(已有文件在原有基础上添加配置)

1
2
3
4
5
6
7
8
9
10
11
12
sonar.security.realm=LDAP
ldap.url=ldap://YOUR_LDAP_ADDRESS:389

# User Configuration
ldap.user.baseDn=LDAP_BASE_DN
ldap.user.request=(&(objectClass=inetOrgPerson)(uid={login}))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail

# Group Configuration
ldap.group.baseDn=LDAP_BASE_DN
ldap.group.request=(&(objectClass=posixGroup)(memberUid={uid}))

sonar-scanner的使用

特殊说明

我们当前的java版本是8, 而sonar上传插件支持的版本需要更高的java版本(目前为11 or 17),也可在通过maven运行扫描命令时指定对应的java版本, 考虑到目前插件通常在ide中运行,建议下载sonar-scanner

使用官方的sonar-scanner-cli容器

官方提供了sonarsource/sonar-scanner-cli的容器镜像, 使用起来也很方便:

1
2
3
4
5
6
docker run --rm \
-e SONAR_HOST_URL="${SONARQUBE_URL}" \
-e SONAR_SCANNER_OPTS="-Dsonar.projectKey=${PROJECTNAME} -Dsonar.scm.provider=git -Dsonar.java.binaries=target -Dsonar.verbose=true" \
-e SONAR_TOKEN="${TOKEN}" \
-v ${GITDIR}:/usr/src \
sonarsource/sonar-scanner-cli

按照官方给的示例,填入对应参数即可

特别说明:

如果代码未经过maven编译,在不加-Dsonar.java.binaries=target这一项配置时会报错,提取部分内容:

1
Your project contains .java files, please provide compiled classes with sonar.java.binaries property, or exclude them from the analysis with sonar.exclusions property.

如果不需要动态检查代码, 只需要静态扫描的话, 只需要新建一个空文件夹target(名字随意),并添加-Dsonar.java.binaries=target让扫描器扫描空文件夹即可。

使用jenkins来自动化代码扫描

直接上流水线代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def ApplicationMetadataAPI( GitUrl ) {
GitUrlName = GitUrl
// 接口地址,此处使用了根据gitlab地址获取应用名称的一个接口,也可使用gitlab项目名称 不使用此接口
ApplicationMetadataURL = "http://xxxx/xxx"
response = httpRequest contentType: 'APPLICATION_JSON',
httpMode: "GET",
requestBody: "",
url: "${ApplicationMetadataURL}?gitRepo=${GitUrlName}"

responseJson = readJSON text: response.content
return responseJson
}

// 定义使用特定节点运行 pipeline
// 需要从清单中获取构建参数, 从而确定
// 构建 agent 的 label
def SlaveLabel() {
// slave label 前缀
SlaveLabelPerfix = "jenkins-agent"
// 提前获取触发分支
GitSourceRepoURL = "${gitlabSourceRepoURL}"
// 调用函数, 根据 GitSourceRepoURL 获取其他元数据信息
ApplicationMetadataAPI = ApplicationMetadataAPI(GitSourceRepoURL)
script {
BuildSpec = "hfc6-xlarge"
AppName = "${ApplicationMetadataAPI.data.name}"
}
SlaveLabelString = "k8s-jenkins-agent-sonar-scanner-ecs-hfc6-xlarge-4c8g"
// 变量打印, 方便后续排查问题
echo "SlaveLabelString: ${SlaveLabelString}"

return SlaveLabelString
}

pipeline {
agent {
// 使用 kubernetes 关键字
kubernetes {
// 指定标签
inheritFrom SlaveLabel()
// 指定该 pipeline 的 stages 在哪个容器中执行
defaultContainer 'scanner'
}
}



// pipeline 配置参数, 和在jenkins job 配置有相同效果
options {
// 表示保留 500 次构建历史
// 保证后期排查
buildDiscarder(logRotator(numToKeepStr: '5000'))
// 不允许同时执行流水线, 被用来防止同时访问共享资源等
// disableConcurrentBuilds()
// 设置流水线运行的超时时间, 在此之后, Jenkins将中止流水线
timeout(time: 30, unit: 'MINUTES')
// 重试次数
retry(1)
// 生成的所有控制台输出时间
timestamps()
}

// 执行阶段
stages {
stage('Pull Repo for Application') {
steps {
git branch: "${gitlabTargetBranch}", credentialsId: "004", url: "${GitSourceRepoURL}"
}
}

stage('Scan Backend Project Code') {
steps {
script {
sh """#!/bin/bash
mkdir -p target
export SONAR_HOST_URL="http://SONAR_URL"
export SONAR_SCANNER_OPTS="-Dsonar.projectKey=${AppName} -Dsonar.scm.provider=git -Dsonar.language=java -Dsonar.java.binaries=target -Dsonar.verbose=true"
export SONAR_TOKEN="xxxxxxxx"
/usr/bin/entrypoint.sh
"""
}
}
}

}

}

获取sonar的token

为了让所有项目都能用同一个token,我们在sonarqube上生成一个永久的全局token, 用于上传扫描代码. 点击头像选中”我的账号”,点击”安全”

输入对应令牌名称, 类型选择全局,过期时间选择永久, 生成token后妥善保存。

随后我们只需要在gitlab中配置jenkins的webhook即可自动进行代码扫描

参考文档

https://docs.sonarsource.com/sonarqube/9.9/