SonarQube 是代码质量"门禁"的事实标准(2007 年首发,2024 改名为 SonarQube Server);PowerJob 是阿里 2019 年开源的分布式任务调度平台(“取代 XXL-JOB” 的新秀)。两个工具配合:SonarQube 守"代码质量门禁",PowerJob 跑"分布式定时任务"。这篇文章把两者的 Docker 化、Jenkins 集成、PostgreSQL 后端实战一次性说清。
阅读对象:DevOps / 平台工程团队;Java 团队需要"代码扫描 + Jenkins 集成";需要"分布式定时任务 + 工作流引擎"的业务团队。
覆盖范围:SonarQube 26.x 容器化部署(PG 后端)、sysctl/limits.conf 调优、token 创建、sonar-scanner 集成、Jenkins pipeline;PowerJob 5.1.x Server 启动、Worker 接入、PG/MySQL 后端、序列与字段类型修复。
一、SonarQube 部署
1.1 前置要求
SonarQube 自带 Elasticsearch,对内核参数有要求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 内核参数(必须)
sysctl -w vm.max_map_count=524288
sysctl -w fs.file-max=131072
ulimit -n 131072
ulimit -u 8192
# 2. 永久生效
sudo sh -c 'cat >> /etc/sysctl.conf << EOF
vm.max_map_count=524288
fs.file-max=131072
EOF'
sudo sysctl -p
sudo sh -c 'cat >> /etc/security/limits.conf << EOF
* soft nofile 131072
* hard nofile 131072
* soft nproc 8192
* hard nproc 8192
EOF'
ulimit -n
|
不调会怎样? Elasticsearch 启动失败,日志报 max file descriptors [4096] for elasticsearch process is too low / max virtual memory areas vm.max_map_count [65530] is too low。
1.2 拉取与启动
1
| docker pull sonarqube:26.5.0.122743-community
|
26.x 是 2026+ 时代(社区版免费,企业版含 AI CodeFix、SAST、SCA)。早期用户常用 8.9.2 LTS / 9.x LTS。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| mkdir -p /home/docker/sonarqube/{data,logs,extensions}
sudo chown -R 1000:1000 /home/docker/sonarqube
docker run -d \
--name sonarqube \
--restart=always \
--net=host \
-e SONAR_JDBC_URL="jdbc:postgresql://localhost:5432/sonarqube?sslmode=disable" \
-e SONAR_JDBC_USERNAME=postgres \
-e SONAR_JDBC_PASSWORD={{REDACTED}} \
-v /home/docker/sonarqube/data:/opt/sonarqube/data \
-v /home/docker/sonarqube/logs:/opt/sonarqube/logs \
-v /home/docker/sonarqube/extensions:/opt/sonarqube/extensions \
sonarqube:26.5.0.122743-community
|
关键:
--net=host:避免 Elasticsearch 集群模式下容器间无法直接组网chown 1000:1000:SonarQube 以 UID 1000 跑- 必须配置外部数据库:默认 H2 不适合生产
1.3 首次登录
- 访问
http://<host>:9000 - 默认
admin / admin,强制修改密码(如 Admin#123456) - 创建 Token:
- 我的账号 → 安全 → 输入 Token 名称 → 类型选
user token → 过期时间 永不 - 记录 Token(如
squ_322a479dc2252a96aa930ec3f680e78fb216237f)
1.4 汉化插件
- 离线:github.com/xuhuisheng/sonar-l10n-zh 下载匹配版本
- 放到
extensions/downloads/ 或 extensions/plugins/ - 重启 SonarQube
也可以在 Marketplace 直接搜索 “Chinese Pack” 在线安装。
二、sonar-scanner + Jenkins 集成
2.1 准备 sonar-scanner
1
2
3
4
| # 下载 sonar-scanner CLI
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472.zip
unzip sonar-scanner-cli-4.6.2.2472.zip
mv sonar-scanner-4.6.2.2472 /opt/sonar-scanner
|
sonar-scanner 4.6.x 用 JDK 8,9.x+ 需 JDK 11+。SonarQube 9.x 之后 scanner 也要求 JDK 11+。
2.2 Jenkins 全局工具配置
Jenkins → 系统管理 → 全局工具配置 → SonarQube Scanner → 新增安装 → 选版本。
2.3 Jenkins 系统配置
Jenkins → 系统管理 → 系统配置 → SonarQube servers → Add SonarQube:
| 字段 | 值 |
|---|
| Name | SonarQube |
| Server URL | http://internal.example.com:9010 |
| Server authentication token | 粘贴 SonarQube 的 squ_... token |
2.4 Jenkinsfile pipeline
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
| pipeline {
agent any
stages {
stage('SonarQube Analysis') {
steps {
script {
def scannerHome = tool 'sonarqube';
sh """
${scannerHome}/bin/sonar-scanner \
-Dsonar.host.url=http://internal.example.com:9010 \
-Dsonar.login=admin \
-Dsonar.password={{REDACTED}} \
-Dsonar.sources=. \
-Dsonar.sourceEncoding=UTF-8 \
-Dsonar.projectVersion=${BUILD_NUMBER} \
-Dsonar.projectKey=${JOB_NAME} \
-Dsonar.projectName=${JOB_NAME} \
-Dsonar.language=java \
-Dsonar.java.binaries=${WORKSPACE} \
-Dsonar.exclusions=**/target/**,**/resources/**,**/*.xml
"""
}
}
}
}
}
|
2.5 Java 项目 sonar-project.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| sonar.sources=.
sonar.sourceEncoding=UTF-8
sonar.projectVersion=$BUILD_NUMBER
sonar.projectName=safe-backend-app
sonar.projectKey=safe-backend-app
sonar.host.url=http://internal.example.com:9010
sonar.login=admin
sonar.password={{REDACTED}}
sonar.verbose=true
sonar.analysisCache.enabled=false
sonar.log.level=INFO
sonar.language=java
sonar.java.binaries=$WORKSPACE
sonar.exclusions=**/target/**,**/resources/**,**/*.xml
|
2.6 JS / 前端项目
1
2
3
4
5
6
7
| sonar.projectKey=safe-frontend-dev
sonar.projectName=safe-frontend-dev
sonar.projectVersion=1.0
sonar.language=js
sonar.sourceEncoding=UTF-8
sonar.sources=$WORKSPACE
sonar.exclusions=**/node_modules/**
|
三、PowerJob 部署
PowerJob 是阿里 2019 年开源的分布式任务调度平台,比 XXL-JOB 更现代(支持 MapReduce、工作流、K8s、容器化 Worker)。
3.1 启动 Server(MySQL 后端)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| docker pull powerjob/powerjob-server:5.1.0
docker run -d \
--restart=always \
--name powerjob \
--network=host \
-e TZ="Asia/Shanghai" \
-e JVMOPTIONS="-Dpowerjob.network.local.address=10.xx.xx.xx -Dpowerjob.network.external.address=x.x.x.x -Dpowerjob.network.external.port.http=10010" \
-e PARAMS="--spring.profiles.active=product \
--spring.datasource.core.jdbc-url=jdbc:mysql://localhost:3306/powerjob?useUnicode=true&characterEncoding=UTF-8 \
--spring.datasource.core.username=root \
--spring.datasource.core.password={{REDACTED}} \
--oms.http.port=10010 --server.port=7700 --oms.akka.port=10086" \
-v /home/docker/powerjob-server:/root/powerjob/server \
internal.example.com/base/powerjob/powerjob-server:5.1.0
|
三个端口:
7700:Web UI10010:HTTP API(Worker 注册 + 任务调度)10086:Akka 端口(Worker 通信)
访问 http://<host>:7700/#/powerjobLogin,默认 ADMIN / powerjob_admin。
3.2 PostgreSQL 后端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| docker run -d \
--restart=always \
--name powerjob \
--network=host \
-e TZ="Asia/Shanghai" \
-e JVMOPTIONS="" \
-e PARAMS="--spring.profiles.active=product \
--spring.datasource.core.driver-class-name=org.postgresql.Driver \
--spring.datasource.core.jdbc-url=jdbc:postgresql://pg.internal.example.com:5432/chemistry_test?currentSchema=powerjob&stringtype=unspecified \
--spring.datasource.core.username=postgres \
--spring.datasource.core.password={{REDACTED}} \
--spring.datasource.remote.hibernate.properties.hibernate.dialect=tech.powerjob.server.persistence.config.dialect.PowerJobPGDialect \
--oms.http.port=10010 --server.port=7700 --oms.akka.port=10086" \
-v /home/docker/powerjob-server:/root/powerjob/server \
internal.example.com/base/powerjob/powerjob-server:5.1.2
|
关键:PG 模式必须显式指定 driver-class-name=org.postgresql.Driver + PowerJobPGDialect。
3.3 K8s 部署
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
| kind: ConfigMap
apiVersion: v1
metadata:
name: powerjob-config
namespace: <ns>
data:
application-product.properties: |-
oms.env=PRODUCT
spring.datasource.core.driver-class-name=org.postgresql.Driver
spring.datasource.core.jdbc-url=jdbc:postgresql://pg-host:5432/db?currentSchema=powerjob&stringtype=unspecified
spring.datasource.core.username=postgres
spring.datasource.core.password={{REDACTED}}
spring.datasource.remote.hibernate.properties.hibernate.dialect=tech.powerjob.server.persistence.config.dialect.PowerJobPGDialect
oms.instanceinfo.retention=7
oms.container.retention.local=7
oms.container.retention.remote=-1
oms.instance.metadata.cache.size=2048
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: powerjob
namespace: <ns>
spec:
replicas: 1
selector:
matchLabels:
app: powerjob
template:
metadata:
labels:
app: powerjob
spec:
containers:
- name: powerjob
image: internal.example.com/base/powerjob/powerjob-server:5.1.2
env:
- name: JVMOPTIONS
value: -Dpowerjob.network.external.address=<external_ip> -Dpowerjob.network.external.port.http=30772
- name: PARAMS
value: --spring.profiles.active=product --server.port=30770 --oms.akka.port=30771 --oms.http.port=30772
resources:
limits: { cpu: 2, memory: 4Gi }
requests: { cpu: 0.5, memory: 1000Mi }
---
kind: Service
apiVersion: v1
metadata:
name: powerjob
namespace: <ns>
spec:
type: NodePort
ports:
- name: port30770 { port: 30770, targetPort: 30770, nodePort: 30770 }
- name: port30771 { port: 30771, targetPort: 30771, nodePort: 30771 }
- name: port30772 { port: 30772, targetPort: 30772, nodePort: 30772 }
selector:
app: powerjob
|
3.4 Worker 接入(Java 应用)
1
2
3
4
5
| <dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-worker</artifactId>
<version>5.1.2</version>
</dependency>
|
1
2
3
4
5
6
7
8
9
10
11
12
| @Configuration
public class PowerJobWorkerConfig {
@Bean
public PowerJobWorker initWorker() throws Exception {
PowerJobWorker worker = new PowerJobWorker();
worker.setAppName("my-app");
worker.setServerAddress("http://<powerjob_host>:10010");
worker.setPort(28888); // Worker 端口
worker.init();
return worker;
}
}
|
1
2
3
4
5
6
7
8
| @Component
public class MyJob implements BasicProcessor {
@Override
public ProcessResult process(TaskContext ctx) {
System.out.println("PowerJob run: " + ctx.getJobId());
return new ProcessResult(true, "ok");
}
}
|
JobProcessor 类型:
BasicProcessor:单任务处理MapReduceProcessor:MapReduce 分布式计算Workflow:工作流(多个 Job 串/并联)
3.5 字段类型修复(PG 模式)
PowerJob 的 instance_params / result 等字段默认用 PG 的 varchar(2000),存大 JSON 会爆。迁移后执行:
1
2
3
4
5
6
7
8
9
10
| ALTER TABLE powerjob.instance_info ALTER COLUMN instance_params TYPE text USING instance_params::text;
ALTER TABLE powerjob.instance_info ALTER COLUMN "result" TYPE text USING "result"::text;
ALTER TABLE powerjob.job_info ALTER COLUMN job_params TYPE text USING job_params::text;
ALTER TABLE powerjob.workflow_info ALTER COLUMN pedag TYPE text USING pedag::text;
ALTER TABLE powerjob.workflow_instance_info ALTER COLUMN dag TYPE text USING dag::text;
ALTER TABLE powerjob.workflow_instance_info ALTER COLUMN "result" TYPE text USING "result"::text;
ALTER TABLE powerjob.workflow_instance_info ALTER COLUMN wf_context TYPE text USING wf_context::text;
ALTER TABLE powerjob.workflow_instance_info ALTER COLUMN wf_init_params TYPE text USING wf_init_params::text;
ALTER TABLE powerjob.workflow_node_info ALTER COLUMN extra TYPE text USING extra::text;
ALTER TABLE powerjob.workflow_node_info ALTER COLUMN node_params TYPE text USING node_params::text;
|
3.6 序列问题(MySQL → PG 迁移后)
MySQL 模式有 app_info 表的主键自增,迁到 PG 后序列不会自动绑定。修复:
1
2
3
4
5
6
7
8
9
10
11
| -- 1. 创建序列
CREATE SEQUENCE app_info_id_seq START WITH 1000 INCREMENT BY 1;
-- 2. 绑定到字段
ALTER TABLE app_info ALTER COLUMN id SET DEFAULT nextval('app_info_id_seq');
ALTER SEQUENCE app_info_id_seq OWNED BY app_info.id;
-- 3. 验证
SELECT column_name, column_default
FROM information_schema.columns
WHERE table_name = 'app_info' AND column_name = 'id';
|
3.7 升级
1
2
3
4
5
6
7
8
9
10
| # 1. 拉新镜像
docker pull powerjob/powerjob-server:5.1.2
docker save powerjob/powerjob-server:5.1.2 > p.tar
# 2. 推送到内网仓库
docker tag powerjob/powerjob-server:5.1.2 internal.example.com/base/powerjob/powerjob-server:5.1.2
docker push internal.example.com/base/powerjob/powerjob-server:5.1.2
# 3. 滚动升级(K8s)
kubectl rollout restart deploy/powerjob -n <ns>
|
四、SonarQube vs PowerJob:选型对比
| 维度 | SonarQube | PowerJob |
|---|
| 起源 | 2007(SonarSource) | 2019(阿里) |
| 类型 | 代码质量平台 | 分布式任务调度 |
| 核心能力 | 静态扫描、安全漏洞、代码异味 | 定时任务、Workflow、MapReduce |
| 后端 | PostgreSQL / Oracle | MySQL / PostgreSQL |
| 部署 | Docker / k8s | Docker / k8s |
| 触发 | Jenkins / GitLab CI / GitHub Actions | Cron / API / 上游任务完成 |
| 适合 | 持续集成门禁 | 分布式定时任务 |
经验法则:
- 需要"代码扫描 + 阻断" → SonarQube
- 需要"分布式定时任务 + 工作流" → PowerJob(比 XXL-JOB 强,但用户量不如 XXL-JOB 多)
五、2024+ 视角补充
本文写于 2025-12,2026+ 期间 SonarQube / PowerJob 关键演进:
- SonarQube 26.x LTS(2026):AI CodeFix(基于 LLM 自动修复漏洞)从商业版下放到 Developer Edition;SAST(静态应用安全测试)从商业版下沉;SCA(软件成分分析)覆盖 SCA 1.5+ 漏洞库
- SonarQube Server 改名定型(原 SonarQube 9.x → 10.x → 2025.x → 26.x 系列),企业版 26.x 强化 AI 安全 / 机密检测(Secret Detection 2.0)
- PowerJob 6.x(2025-Q3):内置 K8s 原生 Worker(不再依赖固定 IP 注册)、分布式工作流(DAG)3.0、MapReduce 性能优化(10x+)、Prometheus metrics 内置暴露(不需要 v2ray-exporter 那种外挂)
- PowerJob vs XXL-JOB vs DolphinScheduler(2025 视角):DolphinScheduler(Apache 顶级项目)补齐了"工作流 + 数据任务"短板,2025 起在大数据 / 离线数仓场景超越 PowerJob
- SonarQube 替代品(2025+):SonarCloud(SaaS)、CodeQL(GitHub 原生)、Snyk Code、Codacy——AI 时代静态扫描工具百花齐放
实战建议(2026 视角):
- 传统 Java 微服务 → SonarQube 26.x + PowerJob 6.x 仍是稳态组合
- AI / LLM 项目 → 静态扫描外,Prompt 注入检测 / 模型输出合规 类工具还在快速演化,未到稳态
- 大数据 / 离线数仓 → DolphinScheduler 优于 PowerJob(数据源集成更丰富)
- CI/CD 集成 → SonarQube 26 与 GitHub Actions / GitLab CI 集成已经非常成熟
六、扩展阅读