1. 删除之前先弄明白:一个 Sonar 项目到底由哪些东西构成
后台和群里被问得最多的一类问题就是“SonarQube 里怎么把一个项目删掉”。听起来是个按钮级别的小事,但我见过的实际情况是,十个动手的人里至少有六七个会卡住:要么菜单找不到,要么点了没反应,要么删掉之后第二天扫描一跑又原封不动冒出来。这些坑基本都源于同一个原因——把 SonarQube 里的“项目”当成了一个文件夹,以为右键删除就完事,其实它是projectKey作为主键串起来的一整组互相引用的数据。这篇就把 SonarQube(下称 Sonar)删除项目的完整链路拆开讲:一是弄清删除动作的真实语义,二是把权限和版本差异这两道门槛说明白,三是给出图形界面、批量清理、Web API 三条可复现的路径,四是把删除之后的连锁反应和常见故障整理成速查表。不管你是刚接手 Sonar 服务的新人,还是要在几十上百个历史项目里做一次清理的老手,这里面的细节应该都能直接用上。
1.1 项目在 Sonar 里究竟是哪些数据的集合
先建立一个准确的认知模型。你在 Sonar 界面上看到的那个项目条目,背后对应的是一组表:项目主体记录(保存 key、名称、可见性、组织归属)、分析快照与分支记录、指标度量数据、问题清单(issue)、质量配置与质量门的绑定关系、权限组与授权关系、项目级 Webhook、项目级令牌、用户订阅的通知设置,以及后台任务队列里跟它相关的计算引擎(Compute Engine,简称 CE)任务。除此之外,还有一份同步到搜索引擎里的索引数据,用于支撑看板检索和问题筛选。
这里有一个非常容易被误解的点,值得单独强调:Sonar 从来不托管你的源码。它只保存分析结果,也就是规则命中情况、覆盖率数字、重复率统计这类派生数据,源码本身始终在你的代码仓库里。所以“删除 Sonar 项目”这个动作,从头到尾都不会碰到你的 Git 仓库、不会删掉你的代码分支、不会影响你的构建产物。我见过有同事因为担心误删代码而不敢操作,其实完全没必要——真正需要担心的是历史质量数据的不可恢复性,而不是代码资产。
反过来说,正因为 Sonar 只存派生数据,删除操作也就意味着这一段质量历史彻底断了。项目重新创建之后,看板上那条趋势线会从零开始,之前的覆盖率变化、技术债曲线、问题收敛过程全部清零,并且没有办法通过重新扫描找回。这一点在清理生产环境项目时必须有清醒预期。
1.2 Disable、Delete、Purge、停扫描,四件事别混为一谈
实操中出现的很多“删不掉”,本质是把几个不同语义的动作混在了一起。先把它们分清楚:
| 动作 | 真实含义 | 数据是否保留 | 典型使用场景 |
|---|---|---|---|
| 停止扫描 | 从 CI 流水线里去掉分析步骤,或加跳过开关 | 全部保留,只是不再更新 | 项目还在,但暂时不做质量卡点 |
| 删除项目 | 从 Sonar 中移除项目及其全部关联数据 | 基本不可恢复 | 项目下线、仓库废弃、key 规划错误 |
| 清理历史 | 按保留策略清掉过期快照,项目还在 | 项目保留,历史缩短 | 数据库膨胀,想减小存储占用 |
| 取消授权 | 收回项目权限,但项目仍在 | 全部保留 | 交接、权限收敛、安全审计 |
“停止扫描”这一项经常被忽略。如果你的诉求只是“这个项目不要再出现在看板上、不要再触发质量门拦截”,最轻的做法是在流水线里给分析步骤加一个显式跳过条件,比如在 Maven 或 Gradle 参数里传-Dsonar.skip=true,或者在 CI 的任务配置里把分析步骤设为条件执行。这种做法零风险、可回滚,而且不需要动 Sonar 服务端。只有当项目确定彻底废弃、或者 key 命名本身规划错了必须重来时,才轮到真正的删除。
至于“清理历史”,它对应的是 Sonar 内置的保留策略机制(Housekeeping),可以在全局配置里设定快照保留天数、按时间窗口保留等规则,让后台定期收缩数据量。这跟删除项目完全是两回事,但效果上都能缓解存储压力,所以经常被拿来当替代方案。
1.3 为什么不建议绕过服务直接动数据库
总有人图省事,想直接连上 Sonar 的数据库,DELETE FROM projects WHERE kee = 'xxx'一把梭。我的态度很明确:不要这么干。原因有三层。
第一层是级联关系不可控。项目相关的数据分散在十几张表里,外键约束、级联删除规则在不同大版本之间会调整,手工删主表极可能留下一堆孤儿记录,这些孤儿记录会在后续的报表统计、项目计数、权限校验里制造莫名其妙的偏差,而且很难定位。
第二层是搜索引擎索引不同步。Sonar 的检索能力依赖一份独立的索引数据,数据库删了但索引没重建,界面上依然能搜到那个项目,点进去一片空白或者直接 500。要恢复只能触发整体重新索引,而这个动作在大规模实例上耗时相当可观。
第三层是缓存与权限。服务端有内存缓存,删库之后不重启,缓存里的项目信息还会存活一段时间,表现出来的现象就是“删了但还在”。所以正确做法永远是走官方提供的界面入口或 Web API,让服务自己完成级联清理和索引更新。数据库层面的操作最多只用于事后核查有没有残留,绝不能作为删除手段。
2. 权限与版本:决定你能不能点下那个按钮的两道门槛
把数据模型搞清楚之后,下一个拦路虎就是权限和版本。我统计过自己处理过的求助,超过一半的“找不到删除入口”,根因不是操作错误,而是账号权限不够或者版本路径不同。这两件事必须先确认,否则后面所有步骤都是空谈。
2.1 权限链路拆解:你到底需要哪一级授权
Sonar 的权限模型是分层的,大致可以理解为:系统级管理员权限管全局配置和所有项目;项目级管理员权限只管被授权的那个项目;普通用户只能看和改自己有权访问的内容。删除项目属于破坏性操作,通常要求你具备系统级管理权限,或者至少对目标项目持有管理权限。不同版本对这个判断的严格程度有细微差别,稳妥做法是直接用系统管理员账号操作,不要在这上面省事。
实际操作中还有一个非常隐蔽的坑:用令牌调接口时的权限与网页登录的权限是两套。你在浏览器里能看到的删除按钮,不代表你手上的那个分析令牌也有删除权限。分析令牌默认只用于提交扫描结果,不具备管理能力;拿它去调删除接口,返回的会是权限不足。这一点我在自动化脚本里踩过一次,排查了小半天才发现是令牌类型选错了。
另一个常见问题是外部身份认证对接。如果 Sonar 接入了统一登录,账号的权限往往来自用户组映射,改动权限要回到身份源那边调整,而不是在 Sonar 里点几下就行。遇到“明明在组里但按钮还是灰的”,先确认组映射是否生效、是否需要重新登录刷新会话。
2.2 版本差异导致入口路径完全不同
Sonar 的版本迭代对界面结构调整相当频繁,删除项目的入口在各版本里位置不一样。下面这张对照表是我按实际接触过的版本整理的,入口名称可能有细微出入,但大方向是准的:
| 版本区间 | 典型入口路径 | 批量删除支持 |
|---|---|---|
| 6.x 及更早 | 系统管理 → 项目 → 管理 | 支持勾选多个后批量删 |
| 7.x | 系统管理 → 项目 → 管理 | 支持勾选批量删 |
| 8.x | 系统管理 → 项目,单项目右侧操作菜单里有删除项 | 单项目为主,批量靠接口 |
| 9.x | 系统管理 → 项目 → 管理,选中后点删除 | 支持 |
| 10.x 及更新 | 系统管理 → 项目 → 管理;也可进入项目自身的管理页找到删除入口 | 支持 |
如果你的实例经过多次升级,界面元素可能是混合的,别死记路径,先记住一个判断方法:只要能进到“系统管理 → 项目”这一级,看到项目列表,删除入口就一定在这个列表的某个操作菜单里。实在找不到,就用下面的接口自查,返回结果里能看到项目 key,再回头在界面上按 key 搜索。
curl -s -u "$SONAR_TOKEN:" \ "http://your-sonar-host:9000/api/projects/search?ps=100" \ | head -c 2000云端版本的路径与自建版本不同,一般在组织下的项目列表里选中项目,进入其管理页后才有删除选项,而且权限由组织角色控制,这一点在跨环境操作时容易搞混。
2.3 动手前的核对清单
删除是不可逆操作,我习惯在动手前跑一遍固定清单,这个习惯帮我避免过至少两次误删。
- 确认项目 key:不要按项目显示名判断,显示名可以重名,key 才是唯一标识。先把 key 复制出来核对一遍。
- 确认最近分析时间:如果这个项目上周还有扫描提交,说明还有人在用,先联系确认再删。
- 确认是否被质量门当作卡点:有些流水线会把特定项目作为准入依据,删掉会导致构建失败。
- 确认是否有项目级 Webhook 对接了外部系统:删除后外部系统会收不到通知,可能引发数据断流。
- 有条件的话先做一次数据库备份或整实例快照,这是唯一的后悔药。
- 先在测试实例上完整走一遍流程,确认版本路径和权限都没问题。
3. 实操:三条路径把一个 Sonar 项目删干净
准备工作做完,进入正题。我把删除方式分成三种:图形界面单删、界面批量清理、接口脚本化。前两种适合偶尔操作,第三种适合站点维护和定期清理。三条路径的底层调用是同一套服务端逻辑,效果一致,选哪个取决于你要删几个。
3.1 图形界面删单个项目
这是最直观的路径,适合一次删一两个的情况。用具备管理权限的账号登录,进入系统管理,找到项目列表页,用项目 key 搜索定位到目标项目,打开它的操作菜单,选择删除。界面通常会弹一个二次确认,要求你手动输入项目 key 或者点确认按钮,这是防误触设计,别嫌麻烦。
删除请求提交后,服务端不是瞬间完成的。它会先把项目标记为删除状态,然后把关联数据的清理任务丢进后台队列。这时候你刷新列表可能还看不到变化,属于正常现象。判断是否真的完成,有两个观察点:一是项目列表里搜不到了,二是后台任务页面里相关的清理任务已经跑完且状态为成功。
这里有个实操细节值得说:如果目标项目存在正在进行中的分析任务,删除会被拒绝或者排队等待。遇到提示“有任务正在执行”,正确做法是等它跑完,或者在后台任务页里把这个项目的待处理任务先取消掉,再重新发起删除。硬等也是一种办法,但高峰期可能等很久。
3.2 界面批量清理历史项目
站点运行两三年之后,废弃项目会积累成一堆,一个一个删能删到怀疑人生。这时候用批量入口效率高得多。在项目列表页一般支持按名称、key、最近分析时间、可见性等条件筛选,筛出目标集合后勾选,一次性提交删除。
批量操作有两个必须注意的地方。第一是筛选条件一定要复核,尤其是按“最近分析时间早于某天”这种模糊条件筛选时,很容易把还在用但很久没扫的项目一起圈进去。我的做法是先筛出来,把 key 列表导出到文本里人工过一遍,确认无误再提交。第二是批量提交的数量建议分批,一次几十个比一次几百个稳,服务端压力小,失败了也容易定位是哪一批出的问题。
如果需要更精细的筛选,界面能力可能不够用,比如按项目前缀前缀匹配、按组织归属过滤。这时候直接上接口会更省事,可以先用查询接口把目标 key 拉成列表,人工核对后再喂给批量删除接口。
3.3 用 Web API 把删除动作脚本化
日常维护里我用得最多的还是接口,因为可复现、可审计、可以塞进定期清理任务。核心接口是项目删除接口,参数就是项目 key。
# 删除单个项目 curl -s -u "$SONAR_TOKEN:" -X POST \ "http://your-sonar-host:9000/api/projects/delete" \ -d "project=my-service-api"批量删除接口在较新版本里可用,接受逗号分隔的 key 列表,或者配合筛选参数使用:
# 按 key 列表批量删除 curl -s -u "$SONAR_TOKEN:" -X POST \ "http://your-sonar-host:9000/api/projects/bulk_delete" \ -d "projects=svc-legacy-a,svc-legacy-b,svc-legacy-c" # 按条件筛选后批量删除(示例:分析时间早于指定日期且从未被扫描过的项目) curl -s -u "$SONAR_TOKEN:" -X POST \ "http://your-sonar-host:9000/api/projects/bulk_delete" \ -d "analyzedBefore=2021-01-01" \ -d "onProvisionedOnly=true"写脚本时有几个工程化要点,都是实际踩出来的。一是结果校验:接口返回成功不代表任务跑完,要再查一次项目列表确认 key 已经消失,或者去后台任务接口看清理任务状态。二是失败重试要谨慎:网络抖动导致超时的时候,请求可能已经提交成功了,无脑重试可能报“项目不存在”,这不代表操作失败,要先查状态再决定。三是令牌权限:前面提过,这里再强调一次,用于删除的令牌必须是管理类令牌,分析令牌不行。
下面这个是我常用的清理脚本骨架,逻辑是先查、再核、后删、最后验:
#!/usr/bin/env bash set -euo pipefail HOST="http://your-sonar-host:9000" TOKEN="${SONAR_TOKEN:?需要先导出管理令牌}" PREFIX="legacy-" # 1. 拉取候选项目 curl -s -u "$TOKEN:" "$HOST/api/projects/search?ps=500" \ | python3 -c " import sys, json data = json.load(sys.stdin) for p in data.get('components', []): if p['key'].startswith('$PREFIX'): print(p['key']) " > candidates.txt # 2. 人工核对 candidates.txt 后再继续 # 3. 逐个删除并记录结果 while read -r key; do [ -z "$key" ] && continue code=$(curl -s -o /dev/null -w "%{http_code}" -u "$TOKEN:" \ -X POST "$HOST/api/projects/delete" -d "project=$key") echo "$key,$code" >> delete-result.csv done < candidates.txt # 4. 复查 curl -s -u "$TOKEN:" "$HOST/api/projects/search?ps=500" \ | grep -c "\"key\":\"$PREFIX" || echo "候选已清空"把候选生成和实际删除分成两步、中间留一个人工核对环节,这一步看起来笨,但它是防止批量误删的唯一有效手段。我见过不止一次脚本写得太顺手,条件一改直接把整个业务线的项目全删了的案例。
4. 删除之后的连锁反应:哪些会跟着消失,哪些不会
删完不是终点。真正让新手困惑的往往是删除之后的各种“异常”:外部系统告警了、链接打不开了、看板上某个数字对不上了。提前知道哪些会受牵连,可以把这些困惑消灭在事前。
4.1 会随项目一起失效的东西
项目被删除后,下面这些会直接失效,需要同步通知相关方:
- 项目级 Webhook:外部系统再也收不到这个项目的分析结果推送,如果对方依赖这个数据做报表或卡点,会出现数据断流。
- 项目级令牌:如果之前给某个项目单独签发过分析令牌,删除后该令牌失效,对应的流水线会在下一次扫描时报认证失败。
- 徽章链接:README 里嵌的质量徽章会变成无效图片,虽然不影响构建,但观感很差,建议同步清理。
- 通知订阅:用户对该项目的邮件或站内通知订阅一并消失,不会残留。
- 质量门绑定:项目与质量门的关联关系消失,但质量门本身是全局配置,不受影响。
- 权限授权:项目级的用户和组授权关系清空,重新创建同名项目需要重新授权。
4.2 不会跟着消失、但容易让人误判的东西
有几样东西看着像是应该被删掉,实际上不会,被误解的频率很高:
第一是扫描器配置。项目在 CI 里的sonar.projectKey、sonar.projectName、覆盖率报告路径这些配置属于代码仓库,Sonar 服务端完全不知道。删完项目后,流水线里的分析步骤还在,下一次构建跑起来,Sonar 检测到这个 key 不存在且提交者有创建权限,就会自动把项目建回来。这就是“删了又复活”的头号原因,第四章会专门讲怎么处理。
第二是全局质量配置。语言级别的质量配置、规则集、质量门都是全局资源,删除项目不会动它们。如果这个项目当时是某个质量配置的唯一样本,删掉之后配置还在,只是没有项目在用。
第三是历史构建产物里的报告。流水线里生成的分析报告文件、覆盖率 XML 之类,属于 CI 侧的产物,Sonar 不管。要清理得去流水线产物那边处理。
第四是外部系统里已经落库的数据。如果之前通过 Webhook 把数据同步到了某个看板或数据仓库,Sonar 侧删除不会回溯清理对方的数据。这类数据要不要清、怎么清,得跟对方系统的人一起定。
5. 常见问题与排查实录
这一节是我把这些年遇到的真实故障按现象归了类,做成可以直接对照的速查表,后面再展开几个高频问题的具体排查思路。
| 现象 | 最可能的原因 | 处理方向 |
|---|---|---|
| 界面里找不到删除入口 | 权限不足或版本路径不同 | 换管理员账号;按版本查入口;用接口兜底 |
| 删除按钮灰色不可点 | 项目有进行中的分析任务 | 等待任务完成或先取消任务 |
| 接口返回权限错误 | 用了分析令牌而非管理令牌 | 重新签发管理类令牌 |
| 删除后项目又出现 | 流水线仍在跑扫描,自动创建 | 同步修改或停用 CI 中的分析步骤 |
| 删除成功但列表还能搜到 | 索引尚未刷新 | 等待后台任务完成,必要时触发索引重建 |
| 删除后报表数字对不上 | 外部系统数据未同步清理 | 与下游系统协同清理 |
| 批量删除了不该删的项目 | 筛选条件过宽 | 立刻从备份恢复,这是唯一有效手段 |
5.1 “删完第二天又回来了”——自动创建的机制与对策
这是被问得最多的一个。现象是昨天明明删干净了,今天早上看板上又出现了,数据从零开始。原因不复杂:流水线里的分析步骤照常在跑,Sonar 在接收分析结果时,如果发现提交的 key 不存在,且提交账号具备创建项目的权限,就会按默认配置自动创建项目。这是设计上的便利特性,但对清理场景来说就是噩梦。
处理思路有两条,选一条执行。第一条是从源头掐断,在 CI 配置里把 Sonar 分析步骤移除,或者加一个显式的跳过条件,比如在 Maven 项目里传-Dsonar.skip=true,在 Gradle 里加-x sonar之类的跳过参数,在流水线配置里直接把这一步注释掉。第二条是收紧自动创建权限,把普通分析账号的“创建项目”权限收回,只有管理员能新建。后者更适合大团队,因为它同时能防止 key 拼错导致的重复项目问题,副作用是新人接入时需要管理员协助创建。
我一般建议两条一起上:先收权限止血,再改流水线彻底断根。只改流水线不收权限的话,别的仓库哪天抄了旧模板,同样的坑还会再踩一次。
顺带说一个相关的坑:改项目 key 等于新建项目。有人想“把 key 从old-name改成new-name”,直接在 CI 里改了参数重新扫描,结果 Sonar 里多了一个新项目,旧项目还挂在那儿。正确的做法是先确认旧项目的数据是否要保留,如果要保留就走改名相关的能力(部分版本支持,需要管理员操作),如果不需要保留就直接删旧建新,别用改参数的方式蒙混。
5.2 删除卡住不动、后台任务异常
另一种高频故障是删除请求提交了但没反应。排查顺序我固定按这个走:
第一步看后台任务页面,找这个项目的清理任务,看状态是排队中、执行中还是失败。排队中说明前面有任务在堵,等;执行中说明在处理,也等;失败的话看错误信息。
第二步看是不是有并发的分析任务。如果同一个项目正好在跑扫描,删除任务可能会等待或冲突。处理方式是先取消待处理的分析任务,再重新发起删除。
第三步看服务端资源。清理任务涉及大量数据删除和索引更新,如果服务端内存或磁盘紧张,任务会卡住或者反复重试。这时候去查看服务日志里有没有超时、连接池耗尽之类的报错,必要时重启服务让队列重新调度。
第四步才是怀疑数据一致性。如果日志里出现索引相关的异常,说明索引和数据库可能不同步了,需要触发索引重建。这个动作在大实例上耗时较长,建议放到低峰期做,并且提前通知使用方。
5.3 删除操作的可审计性怎么保证
删除是不可逆的,所以留痕很重要。我的做法是在脚本里强制记录三样东西:执行时间、执行人、被删项目的 key 列表和接口返回码,统一落到一个 CSV 或者日志文件里,按月份归档。这样做有两个好处,一是事后追溯“某个项目是谁什么时候删的”能立刻查出来,二是批量操作出问题时能精确定位影响范围。
如果团队规模较大,还可以把删除动作做成审批流:业务方提申请,管理员在低峰期集中执行,执行完回执。这套流程听起来重,但比出事后恢复数据要轻得多。毕竟前面说过,Sonar 侧的数据删了基本没有恢复手段,唯一的保底方案是数据库备份或者实例快照,而备份的恢复粒度是整实例,为了一个项目回滚整个实例的代价非常大。
6. 几条从踩坑里换来的建议
先说一个我早期犯过的错,可能很多人都正在犯:把项目 key 当成可以随便改的东西。当时为了统一命名规范,我把一批项目的 key 在 CI 里批量改了前缀重新扫,结果 Sonar 上多出一倍的项目,旧的删、新的建,历史趋势图全部断档,还被业务方问了好几次为什么覆盖率突然归零。后来我把规则定死:项目 key 一旦创建就不再变更,命名规范在创建前就确定好,宁可创建时多花五分钟评审,也不要上线后改。
再一条是关于“清理”这件事本身的节奏。我的建议是不要攒着,攒到几百个项目一起清的时候,风险和成本都会成倍上升。比较舒服的做法是每个季度花半小时过一遍项目列表,按最近分析时间排序,把超过半年没动的项目列出来跟业务方确认一遍,确认废弃的当月就删掉。这样每次的处理量都很小,每一笔都能说清楚为什么删,也不会出现“不知道这个项目是谁的所以不敢删”的僵局。
还有个小技巧分享一下。如果你不确定某个项目到底还有没有人用,别急着删,先做一步降级观察:把项目的可见性收窄,或者在 README 里把徽章撤掉,观察一两个月有没有人来问。没人问,说明确实废弃了,再删也不迟。这比直接删掉之后被人追着要数据要友好得多,也给自己留了缓冲。
最后提一句存储。项目删了之后,数据库文件的物理占用不会立刻下降,因为数据是标记删除加后台回收的,而且索引文件也需要时间整理。如果你是为了腾空间才做清理,别指望删完立刻看到磁盘曲线掉下来,通常要等后台任务跑完、索引刷新之后才见效,大的实例可能要等上几个小时到一天。真要压缩空间,还得配合数据库层面的常规维护动作,这个属于另一摊事,就不在这里展开了。